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

Side by Side Diff: content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm

Issue 2081583002: Migrating majority of gamepad from content/browser/ to device/ (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Next attempt Created 4 years, 5 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
OLDNEW
(Empty)
1 // Copyright (c) 2012 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/gamepad_platform_data_fetcher_mac.h"
6
7 #include <stdint.h>
8 #include <string.h>
9
10 #include "base/mac/foundation_util.h"
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/macros.h"
13 #include "base/strings/string16.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/time/time.h"
17
18 #import <Foundation/Foundation.h>
19 #include <IOKit/hid/IOHIDKeys.h>
20
21 using blink::WebGamepad;
22 using blink::WebGamepads;
23
24 namespace content {
25
26 namespace {
27
28 void CopyNSStringAsUTF16LittleEndian(
29 NSString* src, blink::WebUChar* dest, size_t dest_len) {
30 NSData* as16 = [src dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
31 memset(dest, 0, dest_len);
32 [as16 getBytes:dest length:dest_len - sizeof(blink::WebUChar)];
33 }
34
35 NSDictionary* DeviceMatching(uint32_t usage_page, uint32_t usage) {
36 return [NSDictionary dictionaryWithObjectsAndKeys:
37 [NSNumber numberWithUnsignedInt:usage_page],
38 base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsagePageKey)),
39 [NSNumber numberWithUnsignedInt:usage],
40 base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsageKey)),
41 nil];
42 }
43
44 float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) {
45 return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
46 }
47
48 float NormalizeUInt8Axis(uint8_t value, uint8_t min, uint8_t max) {
49 return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
50 }
51
52 float NormalizeUInt16Axis(uint16_t value, uint16_t min, uint16_t max) {
53 return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
54 }
55
56 float NormalizeUInt32Axis(uint32_t value, uint32_t min, uint32_t max) {
57 return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
58 }
59
60 // http://www.usb.org/developers/hidpage
61 const uint32_t kGenericDesktopUsagePage = 0x01;
62 const uint32_t kGameControlsUsagePage = 0x05;
63 const uint32_t kButtonUsagePage = 0x09;
64 const uint32_t kJoystickUsageNumber = 0x04;
65 const uint32_t kGameUsageNumber = 0x05;
66 const uint32_t kMultiAxisUsageNumber = 0x08;
67 const uint32_t kAxisMinimumUsageNumber = 0x30;
68
69 } // namespace
70
71 GamepadPlatformDataFetcherMac::GamepadPlatformDataFetcherMac()
72 : enabled_(true), paused_(false) {
73 memset(associated_, 0, sizeof(associated_));
74
75 xbox_fetcher_.reset(new XboxDataFetcher(this));
76 if (!xbox_fetcher_->RegisterForNotifications())
77 xbox_fetcher_.reset();
78
79 hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault,
80 kIOHIDOptionsTypeNone));
81 if (CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) {
82 enabled_ = false;
83 return;
84 }
85
86 base::scoped_nsobject<NSArray> criteria([[NSArray alloc] initWithObjects:
87 DeviceMatching(kGenericDesktopUsagePage, kJoystickUsageNumber),
88 DeviceMatching(kGenericDesktopUsagePage, kGameUsageNumber),
89 DeviceMatching(kGenericDesktopUsagePage, kMultiAxisUsageNumber),
90 nil]);
91 IOHIDManagerSetDeviceMatchingMultiple(
92 hid_manager_ref_,
93 base::mac::NSToCFCast(criteria));
94
95 RegisterForNotifications();
96 }
97
98 void GamepadPlatformDataFetcherMac::RegisterForNotifications() {
99 // Register for plug/unplug notifications.
100 IOHIDManagerRegisterDeviceMatchingCallback(
101 hid_manager_ref_,
102 DeviceAddCallback,
103 this);
104 IOHIDManagerRegisterDeviceRemovalCallback(
105 hid_manager_ref_,
106 DeviceRemoveCallback,
107 this);
108
109 // Register for value change notifications.
110 IOHIDManagerRegisterInputValueCallback(
111 hid_manager_ref_,
112 ValueChangedCallback,
113 this);
114
115 IOHIDManagerScheduleWithRunLoop(
116 hid_manager_ref_,
117 CFRunLoopGetMain(),
118 kCFRunLoopDefaultMode);
119
120 enabled_ = IOHIDManagerOpen(hid_manager_ref_,
121 kIOHIDOptionsTypeNone) == kIOReturnSuccess;
122
123 if (xbox_fetcher_)
124 xbox_fetcher_->RegisterForNotifications();
125 }
126
127 void GamepadPlatformDataFetcherMac::UnregisterFromNotifications() {
128 IOHIDManagerUnscheduleFromRunLoop(
129 hid_manager_ref_,
130 CFRunLoopGetCurrent(),
131 kCFRunLoopDefaultMode);
132 IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone);
133 if (xbox_fetcher_)
134 xbox_fetcher_->UnregisterFromNotifications();
135 }
136
137 void GamepadPlatformDataFetcherMac::PauseHint(bool pause) {
138 paused_ = pause;
139 }
140
141 GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() {
142 UnregisterFromNotifications();
143 }
144
145 GamepadPlatformDataFetcherMac*
146 GamepadPlatformDataFetcherMac::InstanceFromContext(void* context) {
147 return reinterpret_cast<GamepadPlatformDataFetcherMac*>(context);
148 }
149
150 void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context,
151 IOReturn result,
152 void* sender,
153 IOHIDDeviceRef ref) {
154 InstanceFromContext(context)->DeviceAdd(ref);
155 }
156
157 void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context,
158 IOReturn result,
159 void* sender,
160 IOHIDDeviceRef ref) {
161 InstanceFromContext(context)->DeviceRemove(ref);
162 }
163
164 void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context,
165 IOReturn result,
166 void* sender,
167 IOHIDValueRef ref) {
168 InstanceFromContext(context)->ValueChanged(ref);
169 }
170
171 bool GamepadPlatformDataFetcherMac::CheckCollection(IOHIDElementRef element) {
172 // Check that a parent collection of this element matches one of the usage
173 // numbers that we are looking for.
174 while ((element = IOHIDElementGetParent(element)) != NULL) {
175 uint32_t usage_page = IOHIDElementGetUsagePage(element);
176 uint32_t usage = IOHIDElementGetUsage(element);
177 if (usage_page == kGenericDesktopUsagePage) {
178 if (usage == kJoystickUsageNumber ||
179 usage == kGameUsageNumber ||
180 usage == kMultiAxisUsageNumber) {
181 return true;
182 }
183 }
184 }
185 return false;
186 }
187
188 bool GamepadPlatformDataFetcherMac::AddButtonsAndAxes(NSArray* elements,
189 size_t slot) {
190 WebGamepad& pad = pad_state_[slot].data;
191 AssociatedData& associated = associated_[slot];
192 CHECK(!associated.is_xbox);
193
194 pad.axesLength = 0;
195 pad.buttonsLength = 0;
196 pad.timestamp = 0;
197 memset(pad.axes, 0, sizeof(pad.axes));
198 memset(pad.buttons, 0, sizeof(pad.buttons));
199
200 bool mapped_all_axes = true;
201
202 for (id elem in elements) {
203 IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
204 if (!CheckCollection(element))
205 continue;
206
207 uint32_t usage_page = IOHIDElementGetUsagePage(element);
208 uint32_t usage = IOHIDElementGetUsage(element);
209 if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button &&
210 usage_page == kButtonUsagePage) {
211 uint32_t button_index = usage - 1;
212 if (button_index < WebGamepad::buttonsLengthCap) {
213 associated.hid.button_elements[button_index] = element;
214 pad.buttonsLength = std::max(pad.buttonsLength, button_index + 1);
215 }
216 }
217 else if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc) {
218 uint32_t axis_index = usage - kAxisMinimumUsageNumber;
219 if (axis_index < WebGamepad::axesLengthCap) {
220 associated.hid.axis_elements[axis_index] = element;
221 pad.axesLength = std::max(pad.axesLength, axis_index + 1);
222 } else {
223 mapped_all_axes = false;
224 }
225 }
226 }
227
228 if (!mapped_all_axes) {
229 // For axes who's usage puts them outside the standard axesLengthCap range.
230 uint32_t next_index = 0;
231 for (id elem in elements) {
232 IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
233 if (!CheckCollection(element))
234 continue;
235
236 uint32_t usage_page = IOHIDElementGetUsagePage(element);
237 uint32_t usage = IOHIDElementGetUsage(element);
238 if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc &&
239 usage - kAxisMinimumUsageNumber >= WebGamepad::axesLengthCap &&
240 usage_page <= kGameControlsUsagePage) {
241 for (; next_index < WebGamepad::axesLengthCap; ++next_index) {
242 if (associated.hid.axis_elements[next_index] == NULL)
243 break;
244 }
245 if (next_index < WebGamepad::axesLengthCap) {
246 associated.hid.axis_elements[next_index] = element;
247 pad.axesLength = std::max(pad.axesLength, next_index + 1);
248 }
249 }
250
251 if (next_index >= WebGamepad::axesLengthCap)
252 break;
253 }
254 }
255
256 for (uint32_t axis_index = 0; axis_index < pad.axesLength; ++axis_index) {
257 IOHIDElementRef element = associated.hid.axis_elements[axis_index];
258 if (element != NULL) {
259 CFIndex axis_min = IOHIDElementGetLogicalMin(element);
260 CFIndex axis_max = IOHIDElementGetLogicalMax(element);
261
262 // Some HID axes report a logical range of -1 to 0 signed, which must be
263 // interpreted as 0 to -1 unsigned for correct normalization behavior.
264 if (axis_min == -1 && axis_max == 0) {
265 axis_max = -1;
266 axis_min = 0;
267 }
268
269 associated.hid.axis_minimums[axis_index] = axis_min;
270 associated.hid.axis_maximums[axis_index] = axis_max;
271 associated.hid.axis_report_sizes[axis_index] =
272 IOHIDElementGetReportSize(element);
273 }
274 }
275
276 return (pad.axesLength > 0 || pad.buttonsLength > 0);
277 }
278
279 size_t GamepadPlatformDataFetcherMac::GetEmptySlot() {
280 // Find a free slot for this device.
281 for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
282 if (!pad_state_[slot].data.connected)
283 return slot;
284 }
285 return WebGamepads::itemsLengthCap;
286 }
287
288 size_t GamepadPlatformDataFetcherMac::GetSlotForDevice(IOHIDDeviceRef device) {
289 for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
290 // If we already have this device, and it's already connected, don't do
291 // anything now.
292 if (pad_state_[slot].data.connected &&
293 !associated_[slot].is_xbox &&
294 associated_[slot].hid.device_ref == device)
295 return WebGamepads::itemsLengthCap;
296 }
297 return GetEmptySlot();
298 }
299
300 size_t GamepadPlatformDataFetcherMac::GetSlotForXboxDevice(
301 XboxController* device) {
302 for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
303 if (associated_[slot].is_xbox &&
304 associated_[slot].xbox.location_id == device->location_id()) {
305 if (pad_state_[slot].data.connected) {
306 // The device is already connected. No idea why we got a second "device
307 // added" call, but let's not add it twice.
308 DCHECK_EQ(associated_[slot].xbox.device, device);
309 return WebGamepads::itemsLengthCap;
310 } else {
311 // A device with the same location ID was previously connected, so put
312 // it in the same slot.
313 return slot;
314 }
315 }
316 }
317 return GetEmptySlot();
318 }
319
320 void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
321 using base::mac::CFToNSCast;
322 using base::mac::CFCastStrict;
323
324 if (!enabled_)
325 return;
326
327 // Find an index for this device.
328 size_t slot = GetSlotForDevice(device);
329
330 // We can't handle this many connected devices.
331 if (slot == WebGamepads::itemsLengthCap)
332 return;
333
334 // Clear some state that may have been left behind by previous gamepads
335 memset(&associated_[slot], 0, sizeof(AssociatedData));
336
337 NSNumber* vendor_id = CFToNSCast(CFCastStrict<CFNumberRef>(
338 IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))));
339 NSNumber* product_id = CFToNSCast(CFCastStrict<CFNumberRef>(
340 IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey))));
341 NSString* product = CFToNSCast(CFCastStrict<CFStringRef>(
342 IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))));
343 int vendor_int = [vendor_id intValue];
344 int product_int = [product_id intValue];
345
346 char vendor_as_str[5], product_as_str[5];
347 snprintf(vendor_as_str, sizeof(vendor_as_str), "%04x", vendor_int);
348 snprintf(product_as_str, sizeof(product_as_str), "%04x", product_int);
349 pad_state_[slot].mapper =
350 GetGamepadStandardMappingFunction(vendor_as_str, product_as_str);
351
352 NSString* ident = [NSString stringWithFormat:
353 @"%@ (%sVendor: %04x Product: %04x)",
354 product,
355 pad_state_[slot].mapper ? "STANDARD GAMEPAD " : "",
356 vendor_int,
357 product_int];
358 CopyNSStringAsUTF16LittleEndian(
359 ident,
360 pad_state_[slot].data.id,
361 sizeof(pad_state_[slot].data.id));
362
363 if (pad_state_[slot].mapper) {
364 CopyNSStringAsUTF16LittleEndian(
365 @"standard",
366 pad_state_[slot].data.mapping,
367 sizeof(pad_state_[slot].data.mapping));
368 } else {
369 pad_state_[slot].data.mapping[0] = 0;
370 }
371
372 base::ScopedCFTypeRef<CFArrayRef> elements(
373 IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
374
375 if (!AddButtonsAndAxes(CFToNSCast(elements), slot))
376 return;
377
378 associated_[slot].hid.device_ref = device;
379 pad_state_[slot].data.connected = true;
380 pad_state_[slot].axis_mask = 0;
381 pad_state_[slot].button_mask = 0;
382 }
383
384 void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) {
385 if (!enabled_)
386 return;
387
388 // Find the index for this device.
389 size_t slot;
390 for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
391 if (pad_state_[slot].data.connected &&
392 !associated_[slot].is_xbox &&
393 associated_[slot].hid.device_ref == device)
394 break;
395 }
396 DCHECK(slot < WebGamepads::itemsLengthCap);
397 // Leave associated device_ref so that it will be reconnected in the same
398 // location. Simply mark it as disconnected.
399 pad_state_[slot].data.connected = false;
400 }
401
402 void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) {
403 if (!enabled_ || paused_)
404 return;
405
406 IOHIDElementRef element = IOHIDValueGetElement(value);
407 IOHIDDeviceRef device = IOHIDElementGetDevice(element);
408
409 // Find device slot.
410 size_t slot;
411 for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
412 if (pad_state_[slot].data.connected &&
413 !associated_[slot].is_xbox &&
414 associated_[slot].hid.device_ref == device)
415 break;
416 }
417 if (slot == WebGamepads::itemsLengthCap)
418 return;
419
420 WebGamepad& pad = pad_state_[slot].data;
421 AssociatedData& associated = associated_[slot];
422
423 uint32_t value_length = IOHIDValueGetLength(value);
424 if (value_length > 4) {
425 // Workaround for bizarre issue with PS3 controllers that try to return
426 // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
427 return;
428 }
429
430 // Find and fill in the associated button event, if any.
431 for (size_t i = 0; i < pad.buttonsLength; ++i) {
432 if (associated.hid.button_elements[i] == element) {
433 pad.buttons[i].pressed = IOHIDValueGetIntegerValue(value);
434 pad.buttons[i].value = pad.buttons[i].pressed ? 1.f : 0.f;
435 pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
436 return;
437 }
438 }
439
440 // Find and fill in the associated axis event, if any.
441 for (size_t i = 0; i < pad.axesLength; ++i) {
442 if (associated.hid.axis_elements[i] == element) {
443 CFIndex axis_min = associated.hid.axis_minimums[i];
444 CFIndex axis_max = associated.hid.axis_maximums[i];
445 CFIndex axis_value = IOHIDValueGetIntegerValue(value);
446
447 if (axis_min > axis_max) {
448 // We'll need to interpret this axis as unsigned during normalization.
449 switch (associated.hid.axis_report_sizes[i]) {
450 case 8:
451 pad.axes[i] = NormalizeUInt8Axis(axis_value, axis_min, axis_max);
452 break;
453 case 16:
454 pad.axes[i] = NormalizeUInt16Axis(axis_value, axis_min, axis_max);
455 break;
456 case 32:
457 pad.axes[i] = NormalizeUInt32Axis(axis_value, axis_min, axis_max);
458 break;
459 }
460 } else {
461 pad.axes[i] = NormalizeAxis(axis_value, axis_min, axis_max);
462 }
463
464 pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
465 return;
466 }
467 }
468 }
469
470 void GamepadPlatformDataFetcherMac::XboxDeviceAdd(XboxController* device) {
471 if (!enabled_)
472 return;
473
474 size_t slot = GetSlotForXboxDevice(device);
475
476 // We can't handle this many connected devices.
477 if (slot == WebGamepads::itemsLengthCap)
478 return;
479
480 device->SetLEDPattern(
481 (XboxController::LEDPattern)(XboxController::LED_FLASH_TOP_LEFT + slot));
482
483 NSString* ident =
484 [NSString stringWithFormat:
485 @"%@ (STANDARD GAMEPAD Vendor: %04x Product: %04x)",
486 device->GetControllerType() == XboxController::XBOX_360_CONTROLLER
487 ? @"Xbox 360 Controller"
488 : @"Xbox One Controller",
489 device->GetProductId(), device->GetVendorId()];
490 CopyNSStringAsUTF16LittleEndian(
491 ident,
492 pad_state_[slot].data.id,
493 sizeof(pad_state_[slot].data.id));
494
495 CopyNSStringAsUTF16LittleEndian(
496 @"standard",
497 pad_state_[slot].data.mapping,
498 sizeof(pad_state_[slot].data.mapping));
499
500 associated_[slot].is_xbox = true;
501 associated_[slot].xbox.device = device;
502 associated_[slot].xbox.location_id = device->location_id();
503 pad_state_[slot].data.connected = true;
504 pad_state_[slot].data.axesLength = 4;
505 pad_state_[slot].data.buttonsLength = 17;
506 pad_state_[slot].data.timestamp = 0;
507 pad_state_[slot].mapper = 0;
508 pad_state_[slot].axis_mask = 0;
509 pad_state_[slot].button_mask = 0;
510 }
511
512 void GamepadPlatformDataFetcherMac::XboxDeviceRemove(XboxController* device) {
513 if (!enabled_)
514 return;
515
516 // Find the index for this device.
517 size_t slot;
518 for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
519 if (pad_state_[slot].data.connected &&
520 associated_[slot].is_xbox &&
521 associated_[slot].xbox.device == device)
522 break;
523 }
524 DCHECK(slot < WebGamepads::itemsLengthCap);
525 // Leave associated location id so that the controller will be reconnected in
526 // the same slot if it is plugged in again. Simply mark it as disconnected.
527 pad_state_[slot].data.connected = false;
528 }
529
530 void GamepadPlatformDataFetcherMac::XboxValueChanged(
531 XboxController* device, const XboxController::Data& data) {
532 // Find device slot.
533 size_t slot;
534 for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
535 if (pad_state_[slot].data.connected &&
536 associated_[slot].is_xbox &&
537 associated_[slot].xbox.device == device)
538 break;
539 }
540 if (slot == WebGamepads::itemsLengthCap)
541 return;
542
543 WebGamepad& pad = pad_state_[slot].data;
544
545 for (size_t i = 0; i < 6; i++) {
546 pad.buttons[i].pressed = data.buttons[i];
547 pad.buttons[i].value = data.buttons[i] ? 1.0f : 0.0f;
548 }
549 pad.buttons[6].pressed = data.triggers[0] > kDefaultButtonPressedThreshold;
550 pad.buttons[6].value = data.triggers[0];
551 pad.buttons[7].pressed = data.triggers[1] > kDefaultButtonPressedThreshold;
552 pad.buttons[7].value = data.triggers[1];
553 for (size_t i = 8; i < 17; i++) {
554 pad.buttons[i].pressed = data.buttons[i - 2];
555 pad.buttons[i].value = data.buttons[i - 2] ? 1.0f : 0.0f;
556 }
557 for (size_t i = 0; i < arraysize(data.axes); i++) {
558 pad.axes[i] = data.axes[i];
559 }
560
561 pad.timestamp = base::TimeTicks::Now().ToInternalValue();
562 }
563
564 void GamepadPlatformDataFetcherMac::GetGamepadData(WebGamepads* pads, bool) {
565 if (!enabled_ && !xbox_fetcher_) {
566 pads->length = 0;
567 return;
568 }
569
570 pads->length = WebGamepads::itemsLengthCap;
571 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i)
572 MapAndSanitizeGamepadData(&pad_state_[i], &pads->items[i]);
573 }
574
575 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698