[content shell] implement testRunner.overridePreference
[chromium-blink-merge.git] / content / browser / gamepad / gamepad_platform_data_fetcher_mac.mm
blob8748730b056f510da93c5d86227721c2a5bfe21d
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/memory/scoped_nsobject.h"
9 #include "base/string16.h"
10 #include "base/string_util.h"
11 #include "base/utf_string_conversions.h"
13 #include <IOKit/hid/IOHIDKeys.h>
14 #import <Foundation/Foundation.h>
16 namespace content {
18 namespace {
20 NSDictionary* DeviceMatching(uint32_t usage_page, uint32_t usage) {
21   return [NSDictionary dictionaryWithObjectsAndKeys:
22       [NSNumber numberWithUnsignedInt:usage_page],
23           base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsagePageKey)),
24       [NSNumber numberWithUnsignedInt:usage],
25           base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsageKey)),
26       nil];
29 float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) {
30   return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
33 // http://www.usb.org/developers/hidpage
34 const uint32_t kGenericDesktopUsagePage = 0x01;
35 const uint32_t kButtonUsagePage = 0x09;
36 const uint32_t kJoystickUsageNumber = 0x04;
37 const uint32_t kGameUsageNumber = 0x05;
38 const uint32_t kMultiAxisUsageNumber = 0x08;
39 const uint32_t kAxisMinimumUsageNumber = 0x30;
40 const uint32_t kAxisMaximumUsageNumber = 0x35;
42 }  // namespace
44 GamepadPlatformDataFetcherMac::GamepadPlatformDataFetcherMac()
45     : enabled_(true) {
46   memset(associated_, 0, sizeof(associated_));
47   hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault,
48                                             kIOHIDOptionsTypeNone));
49   if (CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) {
50     enabled_ = false;
51     return;
52   }
54   scoped_nsobject<NSArray> criteria([[NSArray alloc] initWithObjects:
55       DeviceMatching(kGenericDesktopUsagePage, kJoystickUsageNumber),
56       DeviceMatching(kGenericDesktopUsagePage, kGameUsageNumber),
57       DeviceMatching(kGenericDesktopUsagePage, kMultiAxisUsageNumber),
58       nil]);
59   IOHIDManagerSetDeviceMatchingMultiple(
60       hid_manager_ref_,
61       base::mac::NSToCFCast(criteria));
63   RegisterForNotifications();
66 void GamepadPlatformDataFetcherMac::RegisterForNotifications() {
67   // Register for plug/unplug notifications.
68   IOHIDManagerRegisterDeviceMatchingCallback(
69       hid_manager_ref_,
70       &DeviceAddCallback,
71       this);
72   IOHIDManagerRegisterDeviceRemovalCallback(
73       hid_manager_ref_,
74       DeviceRemoveCallback,
75       this);
77   // Register for value change notifications.
78   IOHIDManagerRegisterInputValueCallback(
79       hid_manager_ref_,
80       ValueChangedCallback,
81       this);
83   IOHIDManagerScheduleWithRunLoop(
84       hid_manager_ref_,
85       CFRunLoopGetMain(),
86       kCFRunLoopDefaultMode);
88   enabled_ = IOHIDManagerOpen(hid_manager_ref_,
89                               kIOHIDOptionsTypeNone) == kIOReturnSuccess;
92 void GamepadPlatformDataFetcherMac::UnregisterFromNotifications() {
93   IOHIDManagerUnscheduleFromRunLoop(
94       hid_manager_ref_,
95       CFRunLoopGetCurrent(),
96       kCFRunLoopDefaultMode);
97   IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone);
100 void GamepadPlatformDataFetcherMac::PauseHint(bool pause) {
101   if (pause)
102     UnregisterFromNotifications();
103   else
104     RegisterForNotifications();
107 GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() {
108   UnregisterFromNotifications();
111 GamepadPlatformDataFetcherMac*
112 GamepadPlatformDataFetcherMac::InstanceFromContext(
113     void* context) {
114   return reinterpret_cast<GamepadPlatformDataFetcherMac*>(context);
117 void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context,
118                                               IOReturn result,
119                                               void* sender,
120                                               IOHIDDeviceRef ref) {
121   InstanceFromContext(context)->DeviceAdd(ref);
124 void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context,
125                                                  IOReturn result,
126                                                  void* sender,
127                                                  IOHIDDeviceRef ref) {
128   InstanceFromContext(context)->DeviceRemove(ref);
131 void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context,
132                                                  IOReturn result,
133                                                  void* sender,
134                                                  IOHIDValueRef ref) {
135   InstanceFromContext(context)->ValueChanged(ref);
138 void GamepadPlatformDataFetcherMac::AddButtonsAndAxes(NSArray* elements,
139                                               size_t slot) {
140   WebKit::WebGamepad& pad = data_.items[slot];
141   AssociatedData& associated = associated_[slot];
143   pad.axesLength = 0;
144   pad.buttonsLength = 0;
145   pad.timestamp = 0;
146   memset(pad.axes, 0, sizeof(pad.axes));
147   memset(pad.buttons, 0, sizeof(pad.buttons));
149   for (id elem in elements) {
150     IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
151     uint32_t usagePage = IOHIDElementGetUsagePage(element);
152     uint32_t usage = IOHIDElementGetUsage(element);
153     if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button &&
154         usagePage == kButtonUsagePage) {
155       uint32_t button_index = usage - 1;
156       if (button_index < WebKit::WebGamepad::buttonsLengthCap) {
157         associated.button_elements[button_index] = element;
158         pad.buttonsLength = std::max(pad.buttonsLength, button_index + 1);
159       }
160     }
161     else if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc) {
162       uint32_t axis_index = usage - kAxisMinimumUsageNumber;
163       if (axis_index < WebKit::WebGamepad::axesLengthCap) {
164         associated.axis_minimums[axis_index] =
165             IOHIDElementGetLogicalMin(element);
166         associated.axis_maximums[axis_index] =
167             IOHIDElementGetLogicalMax(element);
168         associated.axis_elements[axis_index] = element;
169         pad.axesLength = std::max(pad.axesLength, axis_index + 1);
170       }
171     }
172   }
175 void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
176   using WebKit::WebGamepad;
177   using WebKit::WebGamepads;
178   using base::mac::CFToNSCast;
179   using base::mac::CFCastStrict;
180   size_t slot;
182   if (!enabled_)
183     return;
185   // Find an index for this device.
186   for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
187     // If we already have this device, and it's already connected, don't do
188     // anything now.
189     if (associated_[slot].device_ref == device && data_.items[slot].connected)
190       return;
191     if (!data_.items[slot].connected)
192       break;
193   }
195   // We can't handle this many connected devices.
196   if (slot == WebGamepads::itemsLengthCap)
197     return;
199   NSNumber* vendor_id = CFToNSCast(CFCastStrict<CFNumberRef>(
200       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))));
201   NSNumber* product_id = CFToNSCast(CFCastStrict<CFNumberRef>(
202       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey))));
203   NSString* product = CFToNSCast(CFCastStrict<CFStringRef>(
204       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))));
205   int vendor_int = [vendor_id intValue];
206   int product_int = [product_id intValue];
208   char vendor_as_str[5], product_as_str[5];
209   snprintf(vendor_as_str, sizeof(vendor_as_str), "%04x", vendor_int);
210   snprintf(product_as_str, sizeof(product_as_str), "%04x", product_int);
211   associated_[slot].mapper =
212       GetGamepadStandardMappingFunction(vendor_as_str, product_as_str);
214   NSString* ident = [NSString stringWithFormat:
215       @"%@ (%sVendor: %04x Product: %04x)",
216       product,
217       associated_[slot].mapper ? "STANDARD GAMEPAD " : "",
218       vendor_int,
219       product_int];
220   NSData* as16 = [ident dataUsingEncoding:NSUTF16StringEncoding];
222   const size_t kOutputLengthBytes = sizeof(data_.items[slot].id);
223   memset(&data_.items[slot].id, 0, kOutputLengthBytes);
224   [as16 getBytes:data_.items[slot].id
225           length:kOutputLengthBytes - sizeof(WebKit::WebUChar)];
227   base::mac::ScopedCFTypeRef<CFArrayRef> elements(
228       IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
229   AddButtonsAndAxes(CFToNSCast(elements), slot);
231   associated_[slot].device_ref = device;
232   data_.items[slot].connected = true;
233   if (slot >= data_.length)
234     data_.length = slot + 1;
237 void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) {
238   using WebKit::WebGamepads;
239   size_t slot;
240   if (!enabled_)
241     return;
243   // Find the index for this device.
244   for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
245     if (associated_[slot].device_ref == device && data_.items[slot].connected)
246       break;
247   }
248   DCHECK(slot < WebGamepads::itemsLengthCap);
249   // Leave associated device_ref so that it will be reconnected in the same
250   // location. Simply mark it as disconnected.
251   data_.items[slot].connected = false;
254 void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) {
255   if (!enabled_)
256     return;
258   IOHIDElementRef element = IOHIDValueGetElement(value);
259   IOHIDDeviceRef device = IOHIDElementGetDevice(element);
261   // Find device slot.
262   size_t slot;
263   for (slot = 0; slot < data_.length; ++slot) {
264     if (data_.items[slot].connected && associated_[slot].device_ref == device)
265       break;
266   }
267   if (slot == data_.length)
268     return;
270   WebKit::WebGamepad& pad = data_.items[slot];
271   AssociatedData& associated = associated_[slot];
273   // Find and fill in the associated button event, if any.
274   for (size_t i = 0; i < pad.buttonsLength; ++i) {
275     if (associated.button_elements[i] == element) {
276       pad.buttons[i] = IOHIDValueGetIntegerValue(value) ? 1.f : 0.f;
277       pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
278       return;
279     }
280   }
282   // Find and fill in the associated axis event, if any.
283   for (size_t i = 0; i < pad.axesLength; ++i) {
284     if (associated.axis_elements[i] == element) {
285       pad.axes[i] = NormalizeAxis(IOHIDValueGetIntegerValue(value),
286                                   associated.axis_minimums[i],
287                                   associated.axis_maximums[i]);
288       pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
289       return;
290     }
291   }
294 void GamepadPlatformDataFetcherMac::GetGamepadData(
295     WebKit::WebGamepads* pads,
296     bool) {
297   if (!enabled_) {
298     pads->length = 0;
299     return;
300   }
302   // Copy to the current state to the output buffer, using the mapping
303   // function, if there is one available.
304   pads->length = data_.length;
305   for (size_t i = 0; i < WebKit::WebGamepads::itemsLengthCap; ++i) {
306     if (associated_[i].mapper)
307       associated_[i].mapper(data_.items[i], &pads->items[i]);
308     else
309       pads->items[i] = data_.items[i];
310   }
313 }  // namespace content