diff options
Diffstat (limited to 'chromium/content/browser/gamepad/raw_input_data_fetcher_win.cc')
-rw-r--r-- | chromium/content/browser/gamepad/raw_input_data_fetcher_win.cc | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/chromium/content/browser/gamepad/raw_input_data_fetcher_win.cc b/chromium/content/browser/gamepad/raw_input_data_fetcher_win.cc new file mode 100644 index 00000000000..05748216b27 --- /dev/null +++ b/chromium/content/browser/gamepad/raw_input_data_fetcher_win.cc @@ -0,0 +1,505 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/raw_input_data_fetcher_win.h" + +#include "base/debug/trace_event.h" +#include "content/common/gamepad_hardware_buffer.h" +#include "content/common/gamepad_messages.h" + +namespace content { + +using namespace blink; + +namespace { + +float NormalizeAxis(long value, long min, long max) { + return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f; +} + +// From the HID Usage Tables specification. +USHORT DeviceUsages[] = { + 0x04, // Joysticks + 0x05, // Gamepads + 0x08, // Multi Axis +}; + +const uint32_t kAxisMinimumUsageNumber = 0x30; +const uint32_t kGameControlsUsagePage = 0x05; + +} // namespace + +RawInputDataFetcher::RawInputDataFetcher() + : hid_dll_(base::FilePath(FILE_PATH_LITERAL("hid.dll"))) + , rawinput_available_(GetHidDllFunctions()) + , filter_xinput_(true) + , events_monitored_(false) {} + +RawInputDataFetcher::~RawInputDataFetcher() { + DCHECK(!window_); + DCHECK(!events_monitored_); +} + +void RawInputDataFetcher::WillDestroyCurrentMessageLoop() { + StopMonitor(); +} + +RAWINPUTDEVICE* RawInputDataFetcher::GetRawInputDevices(DWORD flags) { + int usage_count = arraysize(DeviceUsages); + scoped_ptr<RAWINPUTDEVICE[]> devices(new RAWINPUTDEVICE[usage_count]); + for (int i = 0; i < usage_count; ++i) { + devices[i].dwFlags = flags; + devices[i].usUsagePage = 1; + devices[i].usUsage = DeviceUsages[i]; + devices[i].hwndTarget = window_->hwnd(); + } + return devices.release(); +} + +void RawInputDataFetcher::StartMonitor() { + if (!rawinput_available_ || events_monitored_) + return; + + if (!window_) { + window_.reset(new base::win::MessageWindow()); + if (!window_->Create(base::Bind(&RawInputDataFetcher::HandleMessage, + base::Unretained(this)))) { + PLOG(ERROR) << "Failed to create the raw input window"; + window_.reset(); + return; + } + } + + // Register to receive raw HID input. + scoped_ptr<RAWINPUTDEVICE[]> devices(GetRawInputDevices(RIDEV_INPUTSINK)); + if (!RegisterRawInputDevices(devices.get(), arraysize(DeviceUsages), + sizeof(RAWINPUTDEVICE))) { + PLOG(ERROR) << "RegisterRawInputDevices() failed for RIDEV_INPUTSINK"; + window_.reset(); + return; + } + + // Start observing message loop destruction if we start monitoring the first + // event. + if (!events_monitored_) + base::MessageLoop::current()->AddDestructionObserver(this); + + events_monitored_ = true; +} + +void RawInputDataFetcher::StopMonitor() { + if (!rawinput_available_ || !events_monitored_) + return; + + // Stop receiving raw input. + DCHECK(window_); + scoped_ptr<RAWINPUTDEVICE[]> devices(GetRawInputDevices(RIDEV_REMOVE)); + + if (!RegisterRawInputDevices(devices.get(), arraysize(DeviceUsages), + sizeof(RAWINPUTDEVICE))) { + PLOG(INFO) << "RegisterRawInputDevices() failed for RIDEV_REMOVE"; + } + + events_monitored_ = false; + window_.reset(); + ClearControllers(); + + // Stop observing message loop destruction if no event is being monitored. + base::MessageLoop::current()->RemoveDestructionObserver(this); +} + +void RawInputDataFetcher::ClearControllers() { + while (!controllers_.empty()) { + RawGamepadInfo* gamepad_info = controllers_.begin()->second; + controllers_.erase(gamepad_info->handle); + delete gamepad_info; + } +} + +std::vector<RawGamepadInfo*> RawInputDataFetcher::EnumerateDevices() { + std::vector<RawGamepadInfo*> valid_controllers; + + ClearControllers(); + + UINT count = 0; + UINT result = GetRawInputDeviceList(NULL, &count, sizeof(RAWINPUTDEVICELIST)); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputDeviceList() failed"; + return valid_controllers; + } + DCHECK_EQ(0u, result); + + scoped_ptr<RAWINPUTDEVICELIST[]> device_list(new RAWINPUTDEVICELIST[count]); + result = GetRawInputDeviceList(device_list.get(), &count, + sizeof(RAWINPUTDEVICELIST)); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputDeviceList() failed"; + return valid_controllers; + } + DCHECK_EQ(count, result); + + for (UINT i = 0; i < count; ++i) { + if (device_list[i].dwType == RIM_TYPEHID) { + HANDLE device_handle = device_list[i].hDevice; + RawGamepadInfo* gamepad_info = ParseGamepadInfo(device_handle); + if (gamepad_info) { + controllers_[device_handle] = gamepad_info; + valid_controllers.push_back(gamepad_info); + } + } + } + return valid_controllers; +} + +RawGamepadInfo* RawInputDataFetcher::GetGamepadInfo(HANDLE handle) { + std::map<HANDLE, RawGamepadInfo*>::iterator it = controllers_.find(handle); + if (it != controllers_.end()) + return it->second; + + return NULL; +} + +RawGamepadInfo* RawInputDataFetcher::ParseGamepadInfo(HANDLE hDevice) { + UINT size = 0; + + // Do we already have this device in the map? + if (GetGamepadInfo(hDevice)) + return NULL; + + // Query basic device info. + UINT result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICEINFO, + NULL, &size); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; + return NULL; + } + DCHECK_EQ(0u, result); + + scoped_ptr<uint8[]> di_buffer(new uint8[size]); + RID_DEVICE_INFO* device_info = + reinterpret_cast<RID_DEVICE_INFO*>(di_buffer.get()); + result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICEINFO, + di_buffer.get(), &size); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; + return NULL; + } + DCHECK_EQ(size, result); + + // Make sure this device is of a type that we want to observe. + bool valid_type = false; + for (int i = 0; i < arraysize(DeviceUsages); ++i) { + if (device_info->hid.usUsage == DeviceUsages[i]) { + valid_type = true; + break; + } + } + + if (!valid_type) + return NULL; + + scoped_ptr<RawGamepadInfo> gamepad_info(new RawGamepadInfo); + gamepad_info->handle = hDevice; + gamepad_info->report_id = 0; + gamepad_info->vendor_id = device_info->hid.dwVendorId; + gamepad_info->product_id = device_info->hid.dwProductId; + gamepad_info->buttons_length = 0; + ZeroMemory(gamepad_info->buttons, sizeof(gamepad_info->buttons)); + gamepad_info->axes_length = 0; + ZeroMemory(gamepad_info->axes, sizeof(gamepad_info->axes)); + + // Query device identifier + result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, + NULL, &size); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; + return NULL; + } + DCHECK_EQ(0u, result); + + scoped_ptr<wchar_t[]> name_buffer(new wchar_t[size]); + result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, + name_buffer.get(), &size); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; + return NULL; + } + DCHECK_EQ(size, result); + + // The presence of "IG_" in the device name indicates that this is an XInput + // Gamepad. Skip enumerating these devices and let the XInput path handle it. + // http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx + if (filter_xinput_ && wcsstr( name_buffer.get(), L"IG_" ) ) + return NULL; + + // Get a friendly device name + BOOLEAN got_product_string = FALSE; + HANDLE hid_handle = CreateFile(name_buffer.get(), GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); + if (hid_handle) { + got_product_string = hidd_get_product_string_(hid_handle, gamepad_info->id, + sizeof(gamepad_info->id)); + CloseHandle(hid_handle); + } + + if (!got_product_string) + swprintf(gamepad_info->id, WebGamepad::idLengthCap, L"Unknown Gamepad"); + + // Query device capabilities. + result = GetRawInputDeviceInfo(hDevice, RIDI_PREPARSEDDATA, + NULL, &size); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; + return NULL; + } + DCHECK_EQ(0u, result); + + gamepad_info->ppd_buffer.reset(new uint8[size]); + gamepad_info->preparsed_data = + reinterpret_cast<PHIDP_PREPARSED_DATA>(gamepad_info->ppd_buffer.get()); + result = GetRawInputDeviceInfo(hDevice, RIDI_PREPARSEDDATA, + gamepad_info->ppd_buffer.get(), &size); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; + return NULL; + } + DCHECK_EQ(size, result); + + HIDP_CAPS caps; + NTSTATUS status = hidp_get_caps_(gamepad_info->preparsed_data, &caps); + DCHECK_EQ(HIDP_STATUS_SUCCESS, status); + + // Query button information. + USHORT count = caps.NumberInputButtonCaps; + if (count > 0) { + scoped_ptr<HIDP_BUTTON_CAPS[]> button_caps(new HIDP_BUTTON_CAPS[count]); + status = hidp_get_button_caps_( + HidP_Input, button_caps.get(), &count, gamepad_info->preparsed_data); + DCHECK_EQ(HIDP_STATUS_SUCCESS, status); + + for (uint32_t i = 0; i < count; ++i) { + if (button_caps[i].Range.UsageMin <= WebGamepad::buttonsLengthCap) { + uint32_t max_index = + std::min(WebGamepad::buttonsLengthCap, + static_cast<size_t>(button_caps[i].Range.UsageMax)); + gamepad_info->buttons_length = std::max( + gamepad_info->buttons_length, max_index); + } + } + } + + // Query axis information. + count = caps.NumberInputValueCaps; + scoped_ptr<HIDP_VALUE_CAPS[]> axes_caps(new HIDP_VALUE_CAPS[count]); + status = hidp_get_value_caps_(HidP_Input, axes_caps.get(), &count, + gamepad_info->preparsed_data); + + bool mapped_all_axes = true; + + for (UINT i = 0; i < count; i++) { + uint32_t axis_index = axes_caps[i].Range.UsageMin - kAxisMinimumUsageNumber; + if (axis_index < WebGamepad::axesLengthCap) { + gamepad_info->axes[axis_index].caps = axes_caps[i]; + gamepad_info->axes[axis_index].value = 0; + gamepad_info->axes[axis_index].active = true; + gamepad_info->axes_length = + std::max(gamepad_info->axes_length, axis_index + 1); + } else { + mapped_all_axes = false; + } + } + + if (!mapped_all_axes) { + // For axes who's usage puts them outside the standard axesLengthCap range. + uint32_t next_index = 0; + for (UINT i = 0; i < count; i++) { + uint32_t usage = axes_caps[i].Range.UsageMin - kAxisMinimumUsageNumber; + if (usage >= WebGamepad::axesLengthCap && + axes_caps[i].UsagePage <= kGameControlsUsagePage) { + + for (; next_index < WebGamepad::axesLengthCap; ++next_index) { + if (!gamepad_info->axes[next_index].active) + break; + } + if (next_index < WebGamepad::axesLengthCap) { + gamepad_info->axes[next_index].caps = axes_caps[i]; + gamepad_info->axes[next_index].value = 0; + gamepad_info->axes[next_index].active = true; + gamepad_info->axes_length = + std::max(gamepad_info->axes_length, next_index + 1); + } + } + + if (next_index >= WebGamepad::axesLengthCap) + break; + } + } + + return gamepad_info.release(); +} + +void RawInputDataFetcher::UpdateGamepad( + RAWINPUT* input, + RawGamepadInfo* gamepad_info) { + NTSTATUS status; + + gamepad_info->report_id++; + + // Query button state. + if (gamepad_info->buttons_length) { + // Clear the button state + ZeroMemory(gamepad_info->buttons, sizeof(gamepad_info->buttons)); + ULONG buttons_length = 0; + + hidp_get_usages_ex_(HidP_Input, + 0, + NULL, + &buttons_length, + gamepad_info->preparsed_data, + reinterpret_cast<PCHAR>(input->data.hid.bRawData), + input->data.hid.dwSizeHid); + + scoped_ptr<USAGE_AND_PAGE[]> usages(new USAGE_AND_PAGE[buttons_length]); + status = + hidp_get_usages_ex_(HidP_Input, + 0, + usages.get(), + &buttons_length, + gamepad_info->preparsed_data, + reinterpret_cast<PCHAR>(input->data.hid.bRawData), + input->data.hid.dwSizeHid); + + if (status == HIDP_STATUS_SUCCESS) { + // Set each reported button to true. + for (uint32_t j = 0; j < buttons_length; j++) { + int32_t button_index = usages[j].Usage - 1; + if (button_index >= 0 && + button_index < blink::WebGamepad::buttonsLengthCap) + gamepad_info->buttons[button_index] = true; + } + } + } + + // Query axis state. + ULONG axis_value = 0; + LONG scaled_axis_value = 0; + for (uint32_t i = 0; i < gamepad_info->axes_length; i++) { + RawGamepadAxis* axis = &gamepad_info->axes[i]; + + // If the min is < 0 we have to query the scaled value, otherwise we need + // the normal unscaled value. + if (axis->caps.LogicalMin < 0) { + status = hidp_get_scaled_usage_value_(HidP_Input, axis->caps.UsagePage, 0, + axis->caps.Range.UsageMin, &scaled_axis_value, + gamepad_info->preparsed_data, + reinterpret_cast<PCHAR>(input->data.hid.bRawData), + input->data.hid.dwSizeHid); + if (status == HIDP_STATUS_SUCCESS) { + axis->value = NormalizeAxis(scaled_axis_value, + axis->caps.LogicalMin, axis->caps.LogicalMax); + } + } else { + status = hidp_get_usage_value_(HidP_Input, axis->caps.UsagePage, 0, + axis->caps.Range.UsageMin, &axis_value, + gamepad_info->preparsed_data, + reinterpret_cast<PCHAR>(input->data.hid.bRawData), + input->data.hid.dwSizeHid); + if (status == HIDP_STATUS_SUCCESS) { + axis->value = NormalizeAxis(axis_value, + axis->caps.LogicalMin, axis->caps.LogicalMax); + } + } + } +} + +LRESULT RawInputDataFetcher::OnInput(HRAWINPUT input_handle) { + // Get the size of the input record. + UINT size = 0; + UINT result = GetRawInputData( + input_handle, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputData() failed"; + return 0; + } + DCHECK_EQ(0u, result); + + // Retrieve the input record. + scoped_ptr<uint8[]> buffer(new uint8[size]); + RAWINPUT* input = reinterpret_cast<RAWINPUT*>(buffer.get()); + result = GetRawInputData( + input_handle, RID_INPUT, buffer.get(), &size, sizeof(RAWINPUTHEADER)); + if (result == static_cast<UINT>(-1)) { + PLOG(ERROR) << "GetRawInputData() failed"; + return 0; + } + DCHECK_EQ(size, result); + + // Notify the observer about events generated locally. + if (input->header.dwType == RIM_TYPEHID && input->header.hDevice != NULL) { + RawGamepadInfo* gamepad = GetGamepadInfo(input->header.hDevice); + if (gamepad) + UpdateGamepad(input, gamepad); + } + + return DefRawInputProc(&input, 1, sizeof(RAWINPUTHEADER)); +} + +bool RawInputDataFetcher::HandleMessage(UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + switch (message) { + case WM_INPUT: + *result = OnInput(reinterpret_cast<HRAWINPUT>(lparam)); + return true; + + default: + return false; + } +} + +bool RawInputDataFetcher::GetHidDllFunctions() { + hidp_get_caps_ = NULL; + hidp_get_button_caps_ = NULL; + hidp_get_value_caps_ = NULL; + hidp_get_usages_ex_ = NULL; + hidp_get_usage_value_ = NULL; + hidp_get_scaled_usage_value_ = NULL; + hidd_get_product_string_ = NULL; + + if (!hid_dll_.is_valid()) return false; + + hidp_get_caps_ = reinterpret_cast<HidPGetCapsFunc>( + hid_dll_.GetFunctionPointer("HidP_GetCaps")); + if (!hidp_get_caps_) + return false; + hidp_get_button_caps_ = reinterpret_cast<HidPGetButtonCapsFunc>( + hid_dll_.GetFunctionPointer("HidP_GetButtonCaps")); + if (!hidp_get_button_caps_) + return false; + hidp_get_value_caps_ = reinterpret_cast<HidPGetValueCapsFunc>( + hid_dll_.GetFunctionPointer("HidP_GetValueCaps")); + if (!hidp_get_value_caps_) + return false; + hidp_get_usages_ex_ = reinterpret_cast<HidPGetUsagesExFunc>( + hid_dll_.GetFunctionPointer("HidP_GetUsagesEx")); + if (!hidp_get_usages_ex_) + return false; + hidp_get_usage_value_ = reinterpret_cast<HidPGetUsageValueFunc>( + hid_dll_.GetFunctionPointer("HidP_GetUsageValue")); + if (!hidp_get_usage_value_) + return false; + hidp_get_scaled_usage_value_ = reinterpret_cast<HidPGetScaledUsageValueFunc>( + hid_dll_.GetFunctionPointer("HidP_GetScaledUsageValue")); + if (!hidp_get_scaled_usage_value_) + return false; + hidd_get_product_string_ = reinterpret_cast<HidDGetStringFunc>( + hid_dll_.GetFunctionPointer("HidD_GetProductString")); + if (!hidd_get_product_string_) + return false; + + return true; +} + +} // namespace content |