Index: content/browser/gamepad/raw_input_data_fetcher_win.cc |
diff --git a/content/browser/gamepad/raw_input_data_fetcher_win.cc b/content/browser/gamepad/raw_input_data_fetcher_win.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..feb7eb0aeb9920718ccb1bdc1122ba4677d78c3c |
--- /dev/null |
+++ b/content/browser/gamepad/raw_input_data_fetcher_win.cc |
@@ -0,0 +1,471 @@ |
+// 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; |
+ |
+} // 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)))) { |
+ LOG_GETLASTERROR(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))) { |
+ LOG_GETLASTERROR(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))) { |
+ LOG_GETLASTERROR(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)) { |
+ LOG_GETLASTERROR(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)) { |
+ LOG_GETLASTERROR(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)) { |
+ LOG_GETLASTERROR(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)) { |
+ LOG_GETLASTERROR(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->button_caps_length = 0; |
+ 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)) { |
+ LOG_GETLASTERROR(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)) { |
+ LOG_GETLASTERROR(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)) { |
+ LOG_GETLASTERROR(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)) { |
+ LOG_GETLASTERROR(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. |
+ gamepad_info->button_caps_length = caps.NumberInputButtonCaps; |
+ USHORT count = gamepad_info->button_caps_length; |
+ if (count > 0) { |
+ gamepad_info->button_caps.reset(new HIDP_BUTTON_CAPS[count]); |
+ status = hidp_get_button_caps_(HidP_Input, gamepad_info->button_caps.get(), |
+ &count, gamepad_info->preparsed_data); |
+ DCHECK_EQ(HIDP_STATUS_SUCCESS, status); |
+ |
+ for (uint32_t i = 0; i < count; ++i) { |
+ HIDP_BUTTON_CAPS* button_caps = &gamepad_info->button_caps[i]; |
+ if (button_caps->Range.UsageMin <= WebGamepad::buttonsLengthCap) { |
+ uint32_t max_index = std::min(WebGamepad::buttonsLengthCap, |
+ static_cast<size_t>(button_caps->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); |
+ |
+ 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_length = |
+ std::max(gamepad_info->axes_length, axis_index + 1); |
+ } |
+ } |
+ |
+ 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)); |
+ for (uint32_t i = 0; i < gamepad_info->button_caps_length; ++i) { |
+ HIDP_BUTTON_CAPS* button_caps = &gamepad_info->button_caps[i]; |
+ if (button_caps->Range.UsageMin <= WebGamepad::buttonsLengthCap) { |
+ ULONG buttons_length = button_caps->Range.UsageMax - |
+ button_caps->Range.UsageMin + 1; |
+ scoped_ptr<USAGE[]> usages(new USAGE[buttons_length]); |
+ status = hidp_get_usages_(HidP_Input, |
+ button_caps->UsagePage, 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] - 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)) { |
+ LOG_GETLASTERROR(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)) { |
+ LOG_GETLASTERROR(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_ = 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_ = reinterpret_cast<HidPGetUsagesFunc>( |
+ hid_dll_.GetFunctionPointer("HidP_GetUsages")); |
+ if (!hidp_get_usages_) |
+ 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 |