Make USB permissions work in the new permission message system
[chromium-blink-merge.git] / content / browser / gamepad / gamepad_platform_data_fetcher_mac.mm
blobf25d15a5cf1b6dc406a0df21a212439a0d201d9c
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.
5 #include "content/browser/gamepad/gamepad_platform_data_fetcher_mac.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/scoped_nsobject.h"
9 #include "base/strings/string16.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/time/time.h"
14 #import <Foundation/Foundation.h>
15 #include <IOKit/hid/IOHIDKeys.h>
17 using blink::WebGamepad;
18 using blink::WebGamepads;
20 namespace content {
22 namespace {
24 void CopyNSStringAsUTF16LittleEndian(
25     NSString* src, blink::WebUChar* dest, size_t dest_len) {
26   NSData* as16 = [src dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
27   memset(dest, 0, dest_len);
28   [as16 getBytes:dest length:dest_len - sizeof(blink::WebUChar)];
31 NSDictionary* DeviceMatching(uint32_t usage_page, uint32_t usage) {
32   return [NSDictionary dictionaryWithObjectsAndKeys:
33       [NSNumber numberWithUnsignedInt:usage_page],
34           base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsagePageKey)),
35       [NSNumber numberWithUnsignedInt:usage],
36           base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsageKey)),
37       nil];
40 float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) {
41   return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
44 // http://www.usb.org/developers/hidpage
45 const uint32_t kGenericDesktopUsagePage = 0x01;
46 const uint32_t kGameControlsUsagePage = 0x05;
47 const uint32_t kButtonUsagePage = 0x09;
48 const uint32_t kJoystickUsageNumber = 0x04;
49 const uint32_t kGameUsageNumber = 0x05;
50 const uint32_t kMultiAxisUsageNumber = 0x08;
51 const uint32_t kAxisMinimumUsageNumber = 0x30;
53 }  // namespace
55 GamepadPlatformDataFetcherMac::GamepadPlatformDataFetcherMac()
56     : enabled_(true), paused_(false) {
57   memset(associated_, 0, sizeof(associated_));
59   xbox_fetcher_.reset(new XboxDataFetcher(this));
60   if (!xbox_fetcher_->RegisterForNotifications())
61     xbox_fetcher_.reset();
63   hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault,
64                                             kIOHIDOptionsTypeNone));
65   if (CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) {
66     enabled_ = false;
67     return;
68   }
70   base::scoped_nsobject<NSArray> criteria([[NSArray alloc] initWithObjects:
71       DeviceMatching(kGenericDesktopUsagePage, kJoystickUsageNumber),
72       DeviceMatching(kGenericDesktopUsagePage, kGameUsageNumber),
73       DeviceMatching(kGenericDesktopUsagePage, kMultiAxisUsageNumber),
74       nil]);
75   IOHIDManagerSetDeviceMatchingMultiple(
76       hid_manager_ref_,
77       base::mac::NSToCFCast(criteria));
79   RegisterForNotifications();
82 void GamepadPlatformDataFetcherMac::RegisterForNotifications() {
83   // Register for plug/unplug notifications.
84   IOHIDManagerRegisterDeviceMatchingCallback(
85       hid_manager_ref_,
86       DeviceAddCallback,
87       this);
88   IOHIDManagerRegisterDeviceRemovalCallback(
89       hid_manager_ref_,
90       DeviceRemoveCallback,
91       this);
93   // Register for value change notifications.
94   IOHIDManagerRegisterInputValueCallback(
95       hid_manager_ref_,
96       ValueChangedCallback,
97       this);
99   IOHIDManagerScheduleWithRunLoop(
100       hid_manager_ref_,
101       CFRunLoopGetMain(),
102       kCFRunLoopDefaultMode);
104   enabled_ = IOHIDManagerOpen(hid_manager_ref_,
105                               kIOHIDOptionsTypeNone) == kIOReturnSuccess;
107   if (xbox_fetcher_)
108     xbox_fetcher_->RegisterForNotifications();
111 void GamepadPlatformDataFetcherMac::UnregisterFromNotifications() {
112   IOHIDManagerUnscheduleFromRunLoop(
113       hid_manager_ref_,
114       CFRunLoopGetCurrent(),
115       kCFRunLoopDefaultMode);
116   IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone);
117   if (xbox_fetcher_)
118     xbox_fetcher_->UnregisterFromNotifications();
121 void GamepadPlatformDataFetcherMac::PauseHint(bool pause) {
122   paused_ = pause;
125 GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() {
126   UnregisterFromNotifications();
129 GamepadPlatformDataFetcherMac*
130 GamepadPlatformDataFetcherMac::InstanceFromContext(void* context) {
131   return reinterpret_cast<GamepadPlatformDataFetcherMac*>(context);
134 void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context,
135                                                       IOReturn result,
136                                                       void* sender,
137                                                       IOHIDDeviceRef ref) {
138   InstanceFromContext(context)->DeviceAdd(ref);
141 void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context,
142                                                          IOReturn result,
143                                                          void* sender,
144                                                          IOHIDDeviceRef ref) {
145   InstanceFromContext(context)->DeviceRemove(ref);
148 void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context,
149                                                          IOReturn result,
150                                                          void* sender,
151                                                          IOHIDValueRef ref) {
152   InstanceFromContext(context)->ValueChanged(ref);
155 bool GamepadPlatformDataFetcherMac::AddButtonsAndAxes(NSArray* elements,
156                                                       size_t slot) {
157   WebGamepad& pad = data_.items[slot];
158   AssociatedData& associated = associated_[slot];
159   CHECK(!associated.is_xbox);
161   pad.axesLength = 0;
162   pad.buttonsLength = 0;
163   pad.timestamp = 0;
164   memset(pad.axes, 0, sizeof(pad.axes));
165   memset(pad.buttons, 0, sizeof(pad.buttons));
167   bool mapped_all_axes = true;
169   for (id elem in elements) {
170     IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
171     uint32_t usage_page = IOHIDElementGetUsagePage(element);
172     uint32_t usage = IOHIDElementGetUsage(element);
173     if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button &&
174         usage_page == kButtonUsagePage) {
175       uint32_t button_index = usage - 1;
176       if (button_index < WebGamepad::buttonsLengthCap) {
177         associated.hid.button_elements[button_index] = element;
178         pad.buttonsLength = std::max(pad.buttonsLength, button_index + 1);
179       }
180     }
181     else if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc) {
182       uint32_t axis_index = usage - kAxisMinimumUsageNumber;
183       if (axis_index < WebGamepad::axesLengthCap) {
184         associated.hid.axis_minimums[axis_index] =
185             IOHIDElementGetLogicalMin(element);
186         associated.hid.axis_maximums[axis_index] =
187             IOHIDElementGetLogicalMax(element);
188         associated.hid.axis_elements[axis_index] = element;
189         pad.axesLength = std::max(pad.axesLength, axis_index + 1);
190       } else {
191         mapped_all_axes = false;
192       }
193     }
194   }
196   if (!mapped_all_axes) {
197     // For axes who's usage puts them outside the standard axesLengthCap range.
198     uint32_t next_index = 0;
199     for (id elem in elements) {
200       IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
201       uint32_t usage_page = IOHIDElementGetUsagePage(element);
202       uint32_t usage = IOHIDElementGetUsage(element);
203       if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc &&
204           usage - kAxisMinimumUsageNumber >= WebGamepad::axesLengthCap &&
205           usage_page <= kGameControlsUsagePage) {
206         for (; next_index < WebGamepad::axesLengthCap; ++next_index) {
207           if (associated.hid.axis_elements[next_index] == NULL)
208             break;
209         }
210         if (next_index < WebGamepad::axesLengthCap) {
211           associated.hid.axis_minimums[next_index] =
212               IOHIDElementGetLogicalMin(element);
213           associated.hid.axis_maximums[next_index] =
214               IOHIDElementGetLogicalMax(element);
215           associated.hid.axis_elements[next_index] = element;
216           pad.axesLength = std::max(pad.axesLength, next_index + 1);
217         }
218       }
220       if (next_index >= WebGamepad::axesLengthCap)
221         break;
222     }
223   }
225   return (pad.axesLength > 0 || pad.buttonsLength > 0);
228 size_t GamepadPlatformDataFetcherMac::GetEmptySlot() {
229   // Find a free slot for this device.
230   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
231     if (!data_.items[slot].connected)
232       return slot;
233   }
234   return WebGamepads::itemsLengthCap;
237 size_t GamepadPlatformDataFetcherMac::GetSlotForDevice(IOHIDDeviceRef device) {
238   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
239     // If we already have this device, and it's already connected, don't do
240     // anything now.
241     if (data_.items[slot].connected &&
242         !associated_[slot].is_xbox &&
243         associated_[slot].hid.device_ref == device)
244       return WebGamepads::itemsLengthCap;
245   }
246   return GetEmptySlot();
249 size_t GamepadPlatformDataFetcherMac::GetSlotForXboxDevice(
250     XboxController* device) {
251   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
252     if (associated_[slot].is_xbox &&
253         associated_[slot].xbox.location_id == device->location_id()) {
254       if (data_.items[slot].connected) {
255         // The device is already connected. No idea why we got a second "device
256         // added" call, but let's not add it twice.
257         DCHECK_EQ(associated_[slot].xbox.device, device);
258         return WebGamepads::itemsLengthCap;
259       } else {
260         // A device with the same location ID was previously connected, so put
261         // it in the same slot.
262         return slot;
263       }
264     }
265   }
266   return GetEmptySlot();
269 void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
270   using base::mac::CFToNSCast;
271   using base::mac::CFCastStrict;
273   if (!enabled_)
274     return;
276   // Find an index for this device.
277   size_t slot = GetSlotForDevice(device);
279   // We can't handle this many connected devices.
280   if (slot == WebGamepads::itemsLengthCap)
281     return;
283   // Clear some state that may have been left behind by previous gamepads
284   memset(&associated_[slot], 0, sizeof(AssociatedData));
286   NSNumber* vendor_id = CFToNSCast(CFCastStrict<CFNumberRef>(
287       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))));
288   NSNumber* product_id = CFToNSCast(CFCastStrict<CFNumberRef>(
289       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey))));
290   NSString* product = CFToNSCast(CFCastStrict<CFStringRef>(
291       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))));
292   int vendor_int = [vendor_id intValue];
293   int product_int = [product_id intValue];
295   char vendor_as_str[5], product_as_str[5];
296   snprintf(vendor_as_str, sizeof(vendor_as_str), "%04x", vendor_int);
297   snprintf(product_as_str, sizeof(product_as_str), "%04x", product_int);
298   associated_[slot].hid.mapper =
299       GetGamepadStandardMappingFunction(vendor_as_str, product_as_str);
301   NSString* ident = [NSString stringWithFormat:
302       @"%@ (%sVendor: %04x Product: %04x)",
303       product,
304       associated_[slot].hid.mapper ? "STANDARD GAMEPAD " : "",
305       vendor_int,
306       product_int];
307   CopyNSStringAsUTF16LittleEndian(
308       ident,
309       data_.items[slot].id,
310       sizeof(data_.items[slot].id));
312   if (associated_[slot].hid.mapper) {
313     CopyNSStringAsUTF16LittleEndian(
314       @"standard",
315       data_.items[slot].mapping,
316       sizeof(data_.items[slot].mapping));
317   } else {
318     data_.items[slot].mapping[0] = 0;
319   }
321   base::ScopedCFTypeRef<CFArrayRef> elements(
322       IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
324   if (!AddButtonsAndAxes(CFToNSCast(elements), slot))
325     return;
327   associated_[slot].hid.device_ref = device;
328   data_.items[slot].connected = true;
329   if (slot >= data_.length)
330     data_.length = slot + 1;
333 void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) {
334   if (!enabled_)
335     return;
337   // Find the index for this device.
338   size_t slot;
339   for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
340     if (data_.items[slot].connected &&
341         !associated_[slot].is_xbox &&
342         associated_[slot].hid.device_ref == device)
343       break;
344   }
345   DCHECK(slot < WebGamepads::itemsLengthCap);
346   // Leave associated device_ref so that it will be reconnected in the same
347   // location. Simply mark it as disconnected.
348   data_.items[slot].connected = false;
351 void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) {
352   if (!enabled_ || paused_)
353     return;
355   IOHIDElementRef element = IOHIDValueGetElement(value);
356   IOHIDDeviceRef device = IOHIDElementGetDevice(element);
358   // Find device slot.
359   size_t slot;
360   for (slot = 0; slot < data_.length; ++slot) {
361     if (data_.items[slot].connected &&
362         !associated_[slot].is_xbox &&
363         associated_[slot].hid.device_ref == device)
364       break;
365   }
366   if (slot == data_.length)
367     return;
369   WebGamepad& pad = data_.items[slot];
370   AssociatedData& associated = associated_[slot];
372   uint32_t value_length = IOHIDValueGetLength(value);
373   if (value_length > 4) {
374     // Workaround for bizarre issue with PS3 controllers that try to return
375     // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
376     return;
377   }
379   // Find and fill in the associated button event, if any.
380   for (size_t i = 0; i < pad.buttonsLength; ++i) {
381     if (associated.hid.button_elements[i] == element) {
382       pad.buttons[i].pressed = IOHIDValueGetIntegerValue(value);
383       pad.buttons[i].value = pad.buttons[i].pressed ? 1.f : 0.f;
384       pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
385       return;
386     }
387   }
389   // Find and fill in the associated axis event, if any.
390   for (size_t i = 0; i < pad.axesLength; ++i) {
391     if (associated.hid.axis_elements[i] == element) {
392       pad.axes[i] = NormalizeAxis(IOHIDValueGetIntegerValue(value),
393                                   associated.hid.axis_minimums[i],
394                                   associated.hid.axis_maximums[i]);
395       pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
396       return;
397     }
398   }
401 void GamepadPlatformDataFetcherMac::XboxDeviceAdd(XboxController* device) {
402   if (!enabled_)
403     return;
405   size_t slot = GetSlotForXboxDevice(device);
407   // We can't handle this many connected devices.
408   if (slot == WebGamepads::itemsLengthCap)
409     return;
411   device->SetLEDPattern(
412       (XboxController::LEDPattern)(XboxController::LED_FLASH_TOP_LEFT + slot));
414   NSString* ident =
415       [NSString stringWithFormat:
416           @"%@ (STANDARD GAMEPAD Vendor: %04x Product: %04x)",
417               device->GetControllerType() == XboxController::XBOX_360_CONTROLLER
418                   ? @"Xbox 360 Controller"
419                   : @"Xbox One Controller",
420               device->GetProductId(), device->GetVendorId()];
421   CopyNSStringAsUTF16LittleEndian(
422       ident,
423       data_.items[slot].id,
424       sizeof(data_.items[slot].id));
426   CopyNSStringAsUTF16LittleEndian(
427     @"standard",
428     data_.items[slot].mapping,
429     sizeof(data_.items[slot].mapping));
431   associated_[slot].is_xbox = true;
432   associated_[slot].xbox.device = device;
433   associated_[slot].xbox.location_id = device->location_id();
434   data_.items[slot].connected = true;
435   data_.items[slot].axesLength = 4;
436   data_.items[slot].buttonsLength = 17;
437   data_.items[slot].timestamp = 0;
438   if (slot >= data_.length)
439     data_.length = slot + 1;
442 void GamepadPlatformDataFetcherMac::XboxDeviceRemove(XboxController* device) {
443   if (!enabled_)
444     return;
446   // Find the index for this device.
447   size_t slot;
448   for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
449     if (data_.items[slot].connected &&
450         associated_[slot].is_xbox &&
451         associated_[slot].xbox.device == device)
452       break;
453   }
454   DCHECK(slot < WebGamepads::itemsLengthCap);
455   // Leave associated location id so that the controller will be reconnected in
456   // the same slot if it is plugged in again. Simply mark it as disconnected.
457   data_.items[slot].connected = false;
460 void GamepadPlatformDataFetcherMac::XboxValueChanged(
461     XboxController* device, const XboxController::Data& data) {
462   // Find device slot.
463   size_t slot;
464   for (slot = 0; slot < data_.length; ++slot) {
465     if (data_.items[slot].connected &&
466         associated_[slot].is_xbox &&
467         associated_[slot].xbox.device == device)
468       break;
469   }
470   if (slot == data_.length)
471     return;
473   WebGamepad& pad = data_.items[slot];
475   for (size_t i = 0; i < 6; i++) {
476     pad.buttons[i].pressed = data.buttons[i];
477     pad.buttons[i].value = data.buttons[i] ? 1.0f : 0.0f;
478   }
479   pad.buttons[6].pressed = data.triggers[0] > kDefaultButtonPressedThreshold;
480   pad.buttons[6].value = data.triggers[0];
481   pad.buttons[7].pressed = data.triggers[1] > kDefaultButtonPressedThreshold;
482   pad.buttons[7].value = data.triggers[1];
483   for (size_t i = 8; i < 17; i++) {
484     pad.buttons[i].pressed = data.buttons[i - 2];
485     pad.buttons[i].value = data.buttons[i - 2] ? 1.0f : 0.0f;
486   }
487   for (size_t i = 0; i < arraysize(data.axes); i++) {
488     pad.axes[i] = data.axes[i];
489   }
491   pad.timestamp = base::TimeTicks::Now().ToInternalValue();
494 void GamepadPlatformDataFetcherMac::GetGamepadData(WebGamepads* pads, bool) {
495   if (!enabled_ && !xbox_fetcher_) {
496     pads->length = 0;
497     return;
498   }
500   // Copy to the current state to the output buffer, using the mapping
501   // function, if there is one available.
502   pads->length = WebGamepads::itemsLengthCap;
503   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
504     if (!associated_[i].is_xbox && associated_[i].hid.mapper)
505       associated_[i].hid.mapper(data_.items[i], &pads->items[i]);
506     else
507       pads->items[i] = data_.items[i];
508   }
511 }  // namespace content