| 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
|
|
|