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

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

Powered by Google App Engine
This is Rietveld 408576698