Rename Animate as Begin(Main)Frame
[chromium-blink-merge.git] / content / browser / gamepad / gamepad_platform_data_fetcher_mac.mm
blobfc457cdfb558724bb457e2925ff489dc00b5516a
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) {
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   if (pause)
123     UnregisterFromNotifications();
124   else
125     RegisterForNotifications();
128 GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() {
129   UnregisterFromNotifications();
132 GamepadPlatformDataFetcherMac*
133 GamepadPlatformDataFetcherMac::InstanceFromContext(void* context) {
134   return reinterpret_cast<GamepadPlatformDataFetcherMac*>(context);
137 void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context,
138                                                       IOReturn result,
139                                                       void* sender,
140                                                       IOHIDDeviceRef ref) {
141   InstanceFromContext(context)->DeviceAdd(ref);
144 void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context,
145                                                          IOReturn result,
146                                                          void* sender,
147                                                          IOHIDDeviceRef ref) {
148   InstanceFromContext(context)->DeviceRemove(ref);
151 void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context,
152                                                          IOReturn result,
153                                                          void* sender,
154                                                          IOHIDValueRef ref) {
155   InstanceFromContext(context)->ValueChanged(ref);
158 void GamepadPlatformDataFetcherMac::AddButtonsAndAxes(NSArray* elements,
159                                                       size_t slot) {
160   WebGamepad& pad = data_.items[slot];
161   AssociatedData& associated = associated_[slot];
162   CHECK(!associated.is_xbox);
164   pad.axesLength = 0;
165   pad.buttonsLength = 0;
166   pad.timestamp = 0;
167   memset(pad.axes, 0, sizeof(pad.axes));
168   memset(pad.buttons, 0, sizeof(pad.buttons));
170   bool mapped_all_axes = true;
172   for (id elem in elements) {
173     IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
174     uint32_t usage_page = IOHIDElementGetUsagePage(element);
175     uint32_t usage = IOHIDElementGetUsage(element);
176     if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button &&
177         usage_page == kButtonUsagePage) {
178       uint32_t button_index = usage - 1;
179       if (button_index < WebGamepad::buttonsLengthCap) {
180         associated.hid.button_elements[button_index] = element;
181         pad.buttonsLength = std::max(pad.buttonsLength, button_index + 1);
182       }
183     }
184     else if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc) {
185       uint32_t axis_index = usage - kAxisMinimumUsageNumber;
186       if (axis_index < WebGamepad::axesLengthCap) {
187         associated.hid.axis_minimums[axis_index] =
188             IOHIDElementGetLogicalMin(element);
189         associated.hid.axis_maximums[axis_index] =
190             IOHIDElementGetLogicalMax(element);
191         associated.hid.axis_elements[axis_index] = element;
192         pad.axesLength = std::max(pad.axesLength, axis_index + 1);
193       } else {
194         mapped_all_axes = false;
195       }
196     }
197   }
199   if (!mapped_all_axes) {
200     // For axes who's usage puts them outside the standard axesLengthCap range.
201     uint32_t next_index = 0;
202     for (id elem in elements) {
203       IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
204       uint32_t usage_page = IOHIDElementGetUsagePage(element);
205       uint32_t usage = IOHIDElementGetUsage(element);
206       if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc &&
207           usage - kAxisMinimumUsageNumber >= WebGamepad::axesLengthCap &&
208           usage_page <= kGameControlsUsagePage) {
209         for (; next_index < WebGamepad::axesLengthCap; ++next_index) {
210           if (associated.hid.axis_elements[next_index] == NULL)
211             break;
212         }
213         if (next_index < WebGamepad::axesLengthCap) {
214           associated.hid.axis_minimums[next_index] =
215               IOHIDElementGetLogicalMin(element);
216           associated.hid.axis_maximums[next_index] =
217               IOHIDElementGetLogicalMax(element);
218           associated.hid.axis_elements[next_index] = element;
219           pad.axesLength = std::max(pad.axesLength, next_index + 1);
220         }
221       }
223       if (next_index >= WebGamepad::axesLengthCap)
224         break;
225     }
226   }
229 size_t GamepadPlatformDataFetcherMac::GetEmptySlot() {
230   // Find a free slot for this device.
231   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
232     if (!data_.items[slot].connected)
233       return slot;
234   }
235   return WebGamepads::itemsLengthCap;
238 size_t GamepadPlatformDataFetcherMac::GetSlotForDevice(IOHIDDeviceRef device) {
239   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
240     // If we already have this device, and it's already connected, don't do
241     // anything now.
242     if (data_.items[slot].connected &&
243         !associated_[slot].is_xbox &&
244         associated_[slot].hid.device_ref == device)
245       return WebGamepads::itemsLengthCap;
246   }
247   return GetEmptySlot();
250 size_t GamepadPlatformDataFetcherMac::GetSlotForXboxDevice(
251     XboxController* device) {
252   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
253     if (associated_[slot].is_xbox &&
254         associated_[slot].xbox.location_id == device->location_id()) {
255       if (data_.items[slot].connected) {
256         // The device is already connected. No idea why we got a second "device
257         // added" call, but let's not add it twice.
258         DCHECK_EQ(associated_[slot].xbox.device, device);
259         return WebGamepads::itemsLengthCap;
260       } else {
261         // A device with the same location ID was previously connected, so put
262         // it in the same slot.
263         return slot;
264       }
265     }
266   }
267   return GetEmptySlot();
270 void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
271   using base::mac::CFToNSCast;
272   using base::mac::CFCastStrict;
274   if (!enabled_)
275     return;
277   // Find an index for this device.
278   size_t slot = GetSlotForDevice(device);
280   // We can't handle this many connected devices.
281   if (slot == WebGamepads::itemsLengthCap)
282     return;
284   NSNumber* vendor_id = CFToNSCast(CFCastStrict<CFNumberRef>(
285       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))));
286   NSNumber* product_id = CFToNSCast(CFCastStrict<CFNumberRef>(
287       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey))));
288   NSString* product = CFToNSCast(CFCastStrict<CFStringRef>(
289       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))));
290   int vendor_int = [vendor_id intValue];
291   int product_int = [product_id intValue];
293   char vendor_as_str[5], product_as_str[5];
294   snprintf(vendor_as_str, sizeof(vendor_as_str), "%04x", vendor_int);
295   snprintf(product_as_str, sizeof(product_as_str), "%04x", product_int);
296   associated_[slot].hid.mapper =
297       GetGamepadStandardMappingFunction(vendor_as_str, product_as_str);
299   NSString* ident = [NSString stringWithFormat:
300       @"%@ (%sVendor: %04x Product: %04x)",
301       product,
302       associated_[slot].hid.mapper ? "STANDARD GAMEPAD " : "",
303       vendor_int,
304       product_int];
305   CopyNSStringAsUTF16LittleEndian(
306       ident,
307       data_.items[slot].id,
308       sizeof(data_.items[slot].id));
310   if (associated_[slot].hid.mapper) {
311     CopyNSStringAsUTF16LittleEndian(
312       @"standard",
313       data_.items[slot].mapping,
314       sizeof(data_.items[slot].mapping));
315   } else {
316     data_.items[slot].mapping[0] = 0;
317   }
319   base::ScopedCFTypeRef<CFArrayRef> elements(
320       IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
321   AddButtonsAndAxes(CFToNSCast(elements), slot);
323   associated_[slot].hid.device_ref = device;
324   data_.items[slot].connected = true;
325   if (slot >= data_.length)
326     data_.length = slot + 1;
329 void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) {
330   if (!enabled_)
331     return;
333   // Find the index for this device.
334   size_t slot;
335   for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
336     if (data_.items[slot].connected &&
337         !associated_[slot].is_xbox &&
338         associated_[slot].hid.device_ref == device)
339       break;
340   }
341   DCHECK(slot < WebGamepads::itemsLengthCap);
342   // Leave associated device_ref so that it will be reconnected in the same
343   // location. Simply mark it as disconnected.
344   data_.items[slot].connected = false;
347 void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) {
348   if (!enabled_)
349     return;
351   IOHIDElementRef element = IOHIDValueGetElement(value);
352   IOHIDDeviceRef device = IOHIDElementGetDevice(element);
354   // Find device slot.
355   size_t slot;
356   for (slot = 0; slot < data_.length; ++slot) {
357     if (data_.items[slot].connected &&
358         !associated_[slot].is_xbox &&
359         associated_[slot].hid.device_ref == device)
360       break;
361   }
362   if (slot == data_.length)
363     return;
365   WebGamepad& pad = data_.items[slot];
366   AssociatedData& associated = associated_[slot];
368   uint32_t value_length = IOHIDValueGetLength(value);
369   if (value_length > 4) {
370     // Workaround for bizarre issue with PS3 controllers that try to return
371     // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
372     return;
373   }
375   // Find and fill in the associated button event, if any.
376   for (size_t i = 0; i < pad.buttonsLength; ++i) {
377     if (associated.hid.button_elements[i] == element) {
378       pad.buttons[i].pressed = IOHIDValueGetIntegerValue(value);
379       pad.buttons[i].value = pad.buttons[i].pressed ? 1.f : 0.f;
380       pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
381       return;
382     }
383   }
385   // Find and fill in the associated axis event, if any.
386   for (size_t i = 0; i < pad.axesLength; ++i) {
387     if (associated.hid.axis_elements[i] == element) {
388       pad.axes[i] = NormalizeAxis(IOHIDValueGetIntegerValue(value),
389                                   associated.hid.axis_minimums[i],
390                                   associated.hid.axis_maximums[i]);
391       pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
392       return;
393     }
394   }
397 void GamepadPlatformDataFetcherMac::XboxDeviceAdd(XboxController* device) {
398   if (!enabled_)
399     return;
401   size_t slot = GetSlotForXboxDevice(device);
403   // We can't handle this many connected devices.
404   if (slot == WebGamepads::itemsLengthCap)
405     return;
407   device->SetLEDPattern(
408       (XboxController::LEDPattern)(XboxController::LED_FLASH_TOP_LEFT + slot));
410   NSString* ident =
411       [NSString stringWithFormat:
412           @"%@ (STANDARD GAMEPAD Vendor: %04x Product: %04x)",
413               device->GetControllerType() == XboxController::XBOX_360_CONTROLLER
414                   ? @"Xbox 360 Controller"
415                   : @"Xbox One Controller",
416               device->GetProductId(), device->GetVendorId()];
417   CopyNSStringAsUTF16LittleEndian(
418       ident,
419       data_.items[slot].id,
420       sizeof(data_.items[slot].id));
422   CopyNSStringAsUTF16LittleEndian(
423     @"standard",
424     data_.items[slot].mapping,
425     sizeof(data_.items[slot].mapping));
427   associated_[slot].is_xbox = true;
428   associated_[slot].xbox.device = device;
429   associated_[slot].xbox.location_id = device->location_id();
430   data_.items[slot].connected = true;
431   data_.items[slot].axesLength = 4;
432   data_.items[slot].buttonsLength = 17;
433   data_.items[slot].timestamp = 0;
434   if (slot >= data_.length)
435     data_.length = slot + 1;
438 void GamepadPlatformDataFetcherMac::XboxDeviceRemove(XboxController* device) {
439   if (!enabled_)
440     return;
442   // Find the index for this device.
443   size_t slot;
444   for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
445     if (data_.items[slot].connected &&
446         associated_[slot].is_xbox &&
447         associated_[slot].xbox.device == device)
448       break;
449   }
450   DCHECK(slot < WebGamepads::itemsLengthCap);
451   // Leave associated location id so that the controller will be reconnected in
452   // the same slot if it is plugged in again. Simply mark it as disconnected.
453   data_.items[slot].connected = false;
456 void GamepadPlatformDataFetcherMac::XboxValueChanged(
457     XboxController* device, const XboxController::Data& data) {
458   // Find device slot.
459   size_t slot;
460   for (slot = 0; slot < data_.length; ++slot) {
461     if (data_.items[slot].connected &&
462         associated_[slot].is_xbox &&
463         associated_[slot].xbox.device == device)
464       break;
465   }
466   if (slot == data_.length)
467     return;
469   WebGamepad& pad = data_.items[slot];
471   for (size_t i = 0; i < 6; i++) {
472     pad.buttons[i].pressed = data.buttons[i];
473     pad.buttons[i].value = data.buttons[i] ? 1.0f : 0.0f;
474   }
475   pad.buttons[6].pressed = data.triggers[0] > kDefaultButtonPressedThreshold;
476   pad.buttons[6].value = data.triggers[0];
477   pad.buttons[7].pressed = data.triggers[1] > kDefaultButtonPressedThreshold;
478   pad.buttons[7].value = data.triggers[1];
479   for (size_t i = 8; i < 17; i++) {
480     pad.buttons[i].pressed = data.buttons[i - 2];
481     pad.buttons[i].value = data.buttons[i - 2] ? 1.0f : 0.0f;
482   }
483   for (size_t i = 0; i < arraysize(data.axes); i++) {
484     pad.axes[i] = data.axes[i];
485   }
487   pad.timestamp = base::TimeTicks::Now().ToInternalValue();
490 void GamepadPlatformDataFetcherMac::GetGamepadData(WebGamepads* pads, bool) {
491   if (!enabled_ && !xbox_fetcher_) {
492     pads->length = 0;
493     return;
494   }
496   // Copy to the current state to the output buffer, using the mapping
497   // function, if there is one available.
498   pads->length = WebGamepads::itemsLengthCap;
499   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
500     if (!associated_[i].is_xbox && associated_[i].hid.mapper)
501       associated_[i].hid.mapper(data_.items[i], &pads->items[i]);
502     else
503       pads->items[i] = data_.items[i];
504   }
507 }  // namespace content