// 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(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 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 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 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 RawInputDataFetcher::EnumerateDevices() { std::vector valid_controllers; ClearControllers(); UINT count = 0; UINT result = GetRawInputDeviceList(NULL, &count, sizeof(RAWINPUTDEVICELIST)); if (result == static_cast(-1)) { PLOG(ERROR) << "GetRawInputDeviceList() failed"; return valid_controllers; } DCHECK_EQ(0u, result); scoped_ptr device_list(new RAWINPUTDEVICELIST[count]); result = GetRawInputDeviceList(device_list.get(), &count, sizeof(RAWINPUTDEVICELIST)); if (result == static_cast(-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::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(-1)) { PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; return NULL; } DCHECK_EQ(0u, result); scoped_ptr di_buffer(new uint8[size]); RID_DEVICE_INFO* device_info = reinterpret_cast(di_buffer.get()); result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICEINFO, di_buffer.get(), &size); if (result == static_cast(-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 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(-1)) { PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; return NULL; } DCHECK_EQ(0u, result); scoped_ptr name_buffer(new wchar_t[size]); result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, name_buffer.get(), &size); if (result == static_cast(-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(-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(gamepad_info->ppd_buffer.get()); result = GetRawInputDeviceInfo(hDevice, RIDI_PREPARSEDDATA, gamepad_info->ppd_buffer.get(), &size); if (result == static_cast(-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 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(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 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(input->data.hid.bRawData), input->data.hid.dwSizeHid); scoped_ptr 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(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(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(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(-1)) { PLOG(ERROR) << "GetRawInputData() failed"; return 0; } DCHECK_EQ(0u, result); // Retrieve the input record. scoped_ptr buffer(new uint8[size]); RAWINPUT* input = reinterpret_cast(buffer.get()); result = GetRawInputData( input_handle, RID_INPUT, buffer.get(), &size, sizeof(RAWINPUTHEADER)); if (result == static_cast(-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(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( hid_dll_.GetFunctionPointer("HidP_GetCaps")); if (!hidp_get_caps_) return false; hidp_get_button_caps_ = reinterpret_cast( hid_dll_.GetFunctionPointer("HidP_GetButtonCaps")); if (!hidp_get_button_caps_) return false; hidp_get_value_caps_ = reinterpret_cast( hid_dll_.GetFunctionPointer("HidP_GetValueCaps")); if (!hidp_get_value_caps_) return false; hidp_get_usages_ex_ = reinterpret_cast( hid_dll_.GetFunctionPointer("HidP_GetUsagesEx")); if (!hidp_get_usages_ex_) return false; hidp_get_usage_value_ = reinterpret_cast( hid_dll_.GetFunctionPointer("HidP_GetUsageValue")); if (!hidp_get_usage_value_) return false; hidp_get_scaled_usage_value_ = reinterpret_cast( hid_dll_.GetFunctionPointer("HidP_GetScaledUsageValue")); if (!hidp_get_scaled_usage_value_) return false; hidd_get_product_string_ = reinterpret_cast( hid_dll_.GetFunctionPointer("HidD_GetProductString")); if (!hidd_get_product_string_) return false; return true; } } // namespace content