Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(294)

Side by Side Diff: content/browser/gamepad/raw_input_data_fetcher_win.cc

Issue 135523006: Implemented Gamepad support via Raw Input on Windows (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressing more feedback Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « content/browser/gamepad/raw_input_data_fetcher_win.h ('k') | content/content_browser.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/gamepad/raw_input_data_fetcher_win.h"
6
7 #include "base/debug/trace_event.h"
8 #include "content/common/gamepad_hardware_buffer.h"
9 #include "content/common/gamepad_messages.h"
10
11 namespace content {
12
13 using namespace blink;
14
15 namespace {
16
17 float NormalizeAxis(long value, long min, long max) {
18 return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
19 }
20
21 // From the HID Usage Tables specification.
22 USHORT DeviceUsages[] = {
23 0x04, // Joysticks
24 0x05, // Gamepads
25 0x08, // Multi Axis
26 };
27
28 const uint32_t kAxisMinimumUsageNumber = 0x30;
29
30 } // namespace
31
32 RawInputDataFetcher::RawInputDataFetcher()
33 : hid_dll_(base::FilePath(FILE_PATH_LITERAL("hid.dll")))
34 , rawinput_available_(GetHidDllFunctions())
35 , filter_xinput_(true)
36 , events_monitored_(false) {}
37
38 RawInputDataFetcher::~RawInputDataFetcher() {
39 DCHECK(!window_);
40 DCHECK(!events_monitored_);
41 }
42
43 void RawInputDataFetcher::WillDestroyCurrentMessageLoop() {
44 StopMonitor();
45 }
46
47 RAWINPUTDEVICE* RawInputDataFetcher::GetRawInputDevices(DWORD flags) {
48 int usage_count = arraysize(DeviceUsages);
49 scoped_ptr<RAWINPUTDEVICE[]> devices(new RAWINPUTDEVICE[usage_count]);
50 for (int i = 0; i < usage_count; ++i) {
51 devices[i].dwFlags = flags;
52 devices[i].usUsagePage = 1;
53 devices[i].usUsage = DeviceUsages[i];
54 devices[i].hwndTarget = window_->hwnd();
55 }
56 return devices.release();
57 }
58
59 void RawInputDataFetcher::StartMonitor() {
60 if (!rawinput_available_ || events_monitored_)
61 return;
62
63 if (!window_) {
64 window_.reset(new base::win::MessageWindow());
65 if (!window_->Create(base::Bind(&RawInputDataFetcher::HandleMessage,
66 base::Unretained(this)))) {
67 LOG_GETLASTERROR(ERROR) << "Failed to create the raw input window";
68 window_.reset();
69 return;
70 }
71 }
72
73 // Register to receive raw HID input.
74 scoped_ptr<RAWINPUTDEVICE[]> devices(GetRawInputDevices(RIDEV_INPUTSINK));
75 if (!RegisterRawInputDevices(devices.get(), arraysize(DeviceUsages),
76 sizeof(RAWINPUTDEVICE))) {
77 LOG_GETLASTERROR(ERROR)
78 << "RegisterRawInputDevices() failed for RIDEV_INPUTSINK";
79 window_.reset();
80 return;
81 }
82
83 // Start observing message loop destruction if we start monitoring the first
84 // event.
85 if (!events_monitored_)
86 base::MessageLoop::current()->AddDestructionObserver(this);
87
88 events_monitored_ = true;
89 }
90
91 void RawInputDataFetcher::StopMonitor() {
92 if (!rawinput_available_ || !events_monitored_)
93 return;
94
95 // Stop receiving raw input.
96 DCHECK(window_);
97 scoped_ptr<RAWINPUTDEVICE[]> devices(GetRawInputDevices(RIDEV_REMOVE));
98
99 if (!RegisterRawInputDevices(devices.get(), arraysize(DeviceUsages),
100 sizeof(RAWINPUTDEVICE))) {
101 LOG_GETLASTERROR(INFO)
102 << "RegisterRawInputDevices() failed for RIDEV_REMOVE";
103 }
104
105 events_monitored_ = false;
106 window_.reset();
107 ClearControllers();
108
109 // Stop observing message loop destruction if no event is being monitored.
110 base::MessageLoop::current()->RemoveDestructionObserver(this);
111 }
112
113 void RawInputDataFetcher::ClearControllers() {
114 while (!controllers_.empty()) {
115 RawGamepadInfo* gamepad_info = controllers_.begin()->second;
116 controllers_.erase(gamepad_info->handle);
117 delete gamepad_info;
118 }
119 }
120
121 std::vector<RawGamepadInfo*> RawInputDataFetcher::EnumerateDevices() {
122 std::vector<RawGamepadInfo*> valid_controllers;
123
124 ClearControllers();
125
126 UINT count = 0;
127 UINT result = GetRawInputDeviceList(NULL, &count, sizeof(RAWINPUTDEVICELIST));
128 if (result == static_cast<UINT>(-1)) {
129 LOG_GETLASTERROR(ERROR) << "GetRawInputDeviceList() failed";
130 return valid_controllers;
131 }
132 DCHECK_EQ(0u, result);
133
134 scoped_ptr<RAWINPUTDEVICELIST[]> device_list(new RAWINPUTDEVICELIST[count]);
135 result = GetRawInputDeviceList(device_list.get(), &count,
136 sizeof(RAWINPUTDEVICELIST));
137 if (result == static_cast<UINT>(-1)) {
138 LOG_GETLASTERROR(ERROR) << "GetRawInputDeviceList() failed";
139 return valid_controllers;
140 }
141 DCHECK_EQ(count, result);
142
143 for (UINT i = 0; i < count; ++i) {
144 if (device_list[i].dwType == RIM_TYPEHID) {
145 HANDLE device_handle = device_list[i].hDevice;
146 RawGamepadInfo* gamepad_info = ParseGamepadInfo(device_handle);
147 if (gamepad_info) {
148 controllers_[device_handle] = gamepad_info;
149 valid_controllers.push_back(gamepad_info);
150 }
151 }
152 }
153 return valid_controllers;
154 }
155
156 RawGamepadInfo* RawInputDataFetcher::GetGamepadInfo(HANDLE handle) {
157 std::map<HANDLE, RawGamepadInfo*>::iterator it = controllers_.find(handle);
158 if (it != controllers_.end())
159 return it->second;
160
161 return NULL;
162 }
163
164 RawGamepadInfo* RawInputDataFetcher::ParseGamepadInfo(HANDLE hDevice) {
165 UINT size = 0;
166
167 // Do we already have this device in the map?
168 if (GetGamepadInfo(hDevice))
169 return NULL;
170
171 // Query basic device info.
172 UINT result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICEINFO,
173 NULL, &size);
174 if (result == static_cast<UINT>(-1)) {
175 LOG_GETLASTERROR(ERROR) << "GetRawInputDeviceInfo() failed";
176 return NULL;
177 }
178 DCHECK_EQ(0u, result);
179
180 scoped_ptr<uint8[]> di_buffer(new uint8[size]);
181 RID_DEVICE_INFO* device_info =
182 reinterpret_cast<RID_DEVICE_INFO*>(di_buffer.get());
183 result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICEINFO,
184 di_buffer.get(), &size);
185 if (result == static_cast<UINT>(-1)) {
186 LOG_GETLASTERROR(ERROR) << "GetRawInputDeviceInfo() failed";
187 return NULL;
188 }
189 DCHECK_EQ(size, result);
190
191 // Make sure this device is of a type that we want to observe.
192 bool valid_type = false;
193 for (int i = 0; i < arraysize(DeviceUsages); ++i) {
194 if (device_info->hid.usUsage == DeviceUsages[i]) {
195 valid_type = true;
196 break;
197 }
198 }
199
200 if (!valid_type)
201 return NULL;
202
203 scoped_ptr<RawGamepadInfo> gamepad_info(new RawGamepadInfo);
204 gamepad_info->handle = hDevice;
205 gamepad_info->report_id = 0;
206 gamepad_info->vendor_id = device_info->hid.dwVendorId;
207 gamepad_info->product_id = device_info->hid.dwProductId;
208 gamepad_info->button_caps_length = 0;
209 gamepad_info->buttons_length = 0;
210 ZeroMemory(gamepad_info->buttons, sizeof(gamepad_info->buttons));
211 gamepad_info->axes_length = 0;
212 ZeroMemory(gamepad_info->axes, sizeof(gamepad_info->axes));
213
214 // Query device identifier
215 result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME,
216 NULL, &size);
217 if (result == static_cast<UINT>(-1)) {
218 LOG_GETLASTERROR(ERROR) << "GetRawInputDeviceInfo() failed";
219 return NULL;
220 }
221 DCHECK_EQ(0u, result);
222
223 scoped_ptr<wchar_t[]> name_buffer(new wchar_t[size]);
224 result = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME,
225 name_buffer.get(), &size);
226 if (result == static_cast<UINT>(-1)) {
227 LOG_GETLASTERROR(ERROR) << "GetRawInputDeviceInfo() failed";
228 return NULL;
229 }
230 DCHECK_EQ(size, result);
231
232 // The presence of "IG_" in the device name indicates that this is an XInput
233 // Gamepad. Skip enumerating these devices and let the XInput path handle it.
234 // http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx
235 if (filter_xinput_ && wcsstr( name_buffer.get(), L"IG_" ) )
236 return NULL;
237
238 // Get a friendly device name
239 BOOLEAN got_product_string = FALSE;
240 HANDLE hid_handle = CreateFile(name_buffer.get(), GENERIC_READ|GENERIC_WRITE,
241 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
242 if (hid_handle) {
243 got_product_string = hidd_get_product_string_(hid_handle, gamepad_info->id,
244 sizeof(gamepad_info->id));
245 CloseHandle(hid_handle);
246 }
247
248 if (!got_product_string)
249 swprintf(gamepad_info->id, WebGamepad::idLengthCap, L"Unknown Gamepad");
250
251 // Query device capabilities.
252 result = GetRawInputDeviceInfo(hDevice, RIDI_PREPARSEDDATA,
253 NULL, &size);
254 if (result == static_cast<UINT>(-1)) {
255 LOG_GETLASTERROR(ERROR) << "GetRawInputDeviceInfo() failed";
256 return NULL;
257 }
258 DCHECK_EQ(0u, result);
259
260 gamepad_info->ppd_buffer.reset(new uint8[size]);
261 gamepad_info->preparsed_data =
262 reinterpret_cast<PHIDP_PREPARSED_DATA>(gamepad_info->ppd_buffer.get());
263 result = GetRawInputDeviceInfo(hDevice, RIDI_PREPARSEDDATA,
264 gamepad_info->ppd_buffer.get(), &size);
265 if (result == static_cast<UINT>(-1)) {
266 LOG_GETLASTERROR(ERROR) << "GetRawInputDeviceInfo() failed";
267 return NULL;
268 }
269 DCHECK_EQ(size, result);
270
271 HIDP_CAPS caps;
272 NTSTATUS status = hidp_get_caps_(gamepad_info->preparsed_data, &caps);
273 DCHECK_EQ(HIDP_STATUS_SUCCESS, status);
274
275 // Query button information.
276 gamepad_info->button_caps_length = caps.NumberInputButtonCaps;
277 USHORT count = gamepad_info->button_caps_length;
278 if (count > 0) {
279 gamepad_info->button_caps.reset(new HIDP_BUTTON_CAPS[count]);
280 status = hidp_get_button_caps_(HidP_Input, gamepad_info->button_caps.get(),
281 &count, gamepad_info->preparsed_data);
282 DCHECK_EQ(HIDP_STATUS_SUCCESS, status);
283
284 for (uint32_t i = 0; i < count; ++i) {
285 HIDP_BUTTON_CAPS* button_caps = &gamepad_info->button_caps[i];
286 if (button_caps->Range.UsageMin - 1 < WebGamepad::buttonsLengthCap) {
scottmg 2014/02/04 01:15:06 i don't think this works if UsageMin == 0 still? (
bajones 2014/02/04 03:53:46 This DOES work, but it occurred to me that the cor
287 uint32_t max_index = std::min(WebGamepad::buttonsLengthCap,
288 static_cast<size_t>(button_caps->Range.UsageMax));
289 gamepad_info->buttons_length = std::max(
290 gamepad_info->buttons_length, max_index);
291 }
292 }
293 }
294
295 // Query axis information.
296 count = caps.NumberInputValueCaps;
297 scoped_ptr<HIDP_VALUE_CAPS[]> axes_caps(new HIDP_VALUE_CAPS[count]);
298 status = hidp_get_value_caps_(HidP_Input, axes_caps.get(), &count,
299 gamepad_info->preparsed_data);
300
301 for (UINT i = 0; i < count; i++) {
302 uint32_t axis_index = axes_caps[i].Range.UsageMin - kAxisMinimumUsageNumber;
303 if (axis_index < WebGamepad::axesLengthCap) {
304 gamepad_info->axes[axis_index].caps = axes_caps[i];
305 gamepad_info->axes[axis_index].value = 0;
306 gamepad_info->axes_length =
307 std::max(gamepad_info->axes_length, axis_index + 1);
308 }
309 }
310
311 return gamepad_info.release();
312 }
313
314 void RawInputDataFetcher::UpdateGamepad(
315 RAWINPUT* input,
316 RawGamepadInfo* gamepad_info) {
317 NTSTATUS status;
318
319 gamepad_info->report_id++;
320
321 // Query button state.
322 if (gamepad_info->buttons_length) {
323 // Clear the button state
324 ZeroMemory(gamepad_info->buttons, sizeof(gamepad_info->buttons));
325 for (uint32_t i = 0; i < gamepad_info->button_caps_length; ++i) {
326 HIDP_BUTTON_CAPS* button_caps = &gamepad_info->button_caps[i];
327 if (button_caps->Range.UsageMin <= WebGamepad::buttonsLengthCap) {
328 ULONG buttons_length = button_caps->Range.UsageMax -
329 button_caps->Range.UsageMin + 1;
330 scoped_ptr<USAGE[]> usages(new USAGE[buttons_length]);
331 status = hidp_get_usages_(HidP_Input,
332 button_caps->UsagePage, 0, usages.get(), &buttons_length,
333 gamepad_info->preparsed_data,
334 reinterpret_cast<PCHAR>(input->data.hid.bRawData),
335 input->data.hid.dwSizeHid);
336
337 if (status == HIDP_STATUS_SUCCESS) {
338 // Set each reported button to true.
339 for (uint32_t j = 0; j < buttons_length; j++) {
340 int32_t button_index = usages[j] - 1;
341 if (button_index >= 0 &&
342 button_index < blink::WebGamepad::buttonsLengthCap)
343 gamepad_info->buttons[button_index] = true;
344 }
345 }
346 }
347 }
348 }
349
350 // Query axis state.
351 ULONG axis_value = 0;
352 LONG scaled_axis_value = 0;
353 for (uint32_t i = 0; i < gamepad_info->axes_length; i++) {
354 RawGamepadAxis* axis = &gamepad_info->axes[i];
355
356 // If the min is < 0 we have to query the scaled value, otherwise we need
357 // the normal unscaled value.
358 if (axis->caps.LogicalMin < 0) {
359 status = hidp_get_scaled_usage_value_(HidP_Input, axis->caps.UsagePage, 0,
360 axis->caps.Range.UsageMin, &scaled_axis_value,
361 gamepad_info->preparsed_data,
362 reinterpret_cast<PCHAR>(input->data.hid.bRawData),
363 input->data.hid.dwSizeHid);
364 if (status == HIDP_STATUS_SUCCESS) {
365 axis->value = NormalizeAxis(scaled_axis_value,
366 axis->caps.LogicalMin, axis->caps.LogicalMax);
367 }
368 } else {
369 status = hidp_get_usage_value_(HidP_Input, axis->caps.UsagePage, 0,
370 axis->caps.Range.UsageMin, &axis_value,
371 gamepad_info->preparsed_data,
372 reinterpret_cast<PCHAR>(input->data.hid.bRawData),
373 input->data.hid.dwSizeHid);
374 if (status == HIDP_STATUS_SUCCESS) {
375 axis->value = NormalizeAxis(axis_value,
376 axis->caps.LogicalMin, axis->caps.LogicalMax);
377 }
378 }
379 }
380 }
381
382 LRESULT RawInputDataFetcher::OnInput(HRAWINPUT input_handle) {
383 // Get the size of the input record.
384 UINT size = 0;
385 UINT result = GetRawInputData(
386 input_handle, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER));
387 if (result == static_cast<UINT>(-1)) {
388 LOG_GETLASTERROR(ERROR) << "GetRawInputData() failed";
389 return 0;
390 }
391 DCHECK_EQ(0u, result);
392
393 // Retrieve the input record.
394 scoped_ptr<uint8[]> buffer(new uint8[size]);
395 RAWINPUT* input = reinterpret_cast<RAWINPUT*>(buffer.get());
396 result = GetRawInputData(
397 input_handle, RID_INPUT, buffer.get(), &size, sizeof(RAWINPUTHEADER));
398 if (result == static_cast<UINT>(-1)) {
399 LOG_GETLASTERROR(ERROR) << "GetRawInputData() failed";
400 return 0;
401 }
402 DCHECK_EQ(size, result);
403
404 // Notify the observer about events generated locally.
405 if (input->header.dwType == RIM_TYPEHID && input->header.hDevice != NULL) {
406 RawGamepadInfo* gamepad = GetGamepadInfo(input->header.hDevice);
407 if (gamepad)
408 UpdateGamepad(input, gamepad);
409 }
410
411 return DefRawInputProc(&input, 1, sizeof(RAWINPUTHEADER));
412 }
413
414 bool RawInputDataFetcher::HandleMessage(UINT message,
415 WPARAM wparam,
416 LPARAM lparam,
417 LRESULT* result) {
418 switch (message) {
419 case WM_INPUT:
420 *result = OnInput(reinterpret_cast<HRAWINPUT>(lparam));
421 return true;
422
423 default:
424 return false;
425 }
426 }
427
428 bool RawInputDataFetcher::GetHidDllFunctions() {
429 hidp_get_caps_ = NULL;
430 hidp_get_button_caps_ = NULL;
431 hidp_get_value_caps_ = NULL;
432 hidp_get_usages_ = NULL;
433 hidp_get_usage_value_ = NULL;
434 hidp_get_scaled_usage_value_ = NULL;
435 hidd_get_product_string_ = NULL;
436
437 if (!hid_dll_.is_valid()) return false;
438
439 hidp_get_caps_ = reinterpret_cast<HidPGetCapsFunc>(
440 hid_dll_.GetFunctionPointer("HidP_GetCaps"));
441 if (!hidp_get_caps_)
442 return false;
443 hidp_get_button_caps_ = reinterpret_cast<HidPGetButtonCapsFunc>(
444 hid_dll_.GetFunctionPointer("HidP_GetButtonCaps"));
445 if (!hidp_get_button_caps_)
446 return false;
447 hidp_get_value_caps_ = reinterpret_cast<HidPGetValueCapsFunc>(
448 hid_dll_.GetFunctionPointer("HidP_GetValueCaps"));
449 if (!hidp_get_value_caps_)
450 return false;
451 hidp_get_usages_ = reinterpret_cast<HidPGetUsagesFunc>(
452 hid_dll_.GetFunctionPointer("HidP_GetUsages"));
453 if (!hidp_get_usages_)
454 return false;
455 hidp_get_usage_value_ = reinterpret_cast<HidPGetUsageValueFunc>(
456 hid_dll_.GetFunctionPointer("HidP_GetUsageValue"));
457 if (!hidp_get_usage_value_)
458 return false;
459 hidp_get_scaled_usage_value_ = reinterpret_cast<HidPGetScaledUsageValueFunc>(
460 hid_dll_.GetFunctionPointer("HidP_GetScaledUsageValue"));
461 if (!hidp_get_scaled_usage_value_)
462 return false;
463 hidd_get_product_string_ = reinterpret_cast<HidDGetStringFunc>(
464 hid_dll_.GetFunctionPointer("HidD_GetProductString"));
465 if (!hidd_get_product_string_)
466 return false;
467
468 return true;
469 }
470
471 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/gamepad/raw_input_data_fetcher_win.h ('k') | content/content_browser.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698