- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / gamepad / gamepad_platform_data_fetcher_mac.mm
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 "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"
13
14 #import <Foundation/Foundation.h>
15 #include <IOKit/hid/IOHIDKeys.h>
16
17 using WebKit::WebGamepad;
18 using WebKit::WebGamepads;
19
20 namespace content {
21
22 namespace {
23
24 NSDictionary* DeviceMatching(uint32_t usage_page, uint32_t usage) {
25   return [NSDictionary dictionaryWithObjectsAndKeys:
26       [NSNumber numberWithUnsignedInt:usage_page],
27           base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsagePageKey)),
28       [NSNumber numberWithUnsignedInt:usage],
29           base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsageKey)),
30       nil];
31 }
32
33 float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) {
34   return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
35 }
36
37 // http://www.usb.org/developers/hidpage
38 const uint32_t kGenericDesktopUsagePage = 0x01;
39 const uint32_t kButtonUsagePage = 0x09;
40 const uint32_t kJoystickUsageNumber = 0x04;
41 const uint32_t kGameUsageNumber = 0x05;
42 const uint32_t kMultiAxisUsageNumber = 0x08;
43 const uint32_t kAxisMinimumUsageNumber = 0x30;
44
45 }  // namespace
46
47 GamepadPlatformDataFetcherMac::GamepadPlatformDataFetcherMac()
48     : enabled_(true) {
49   memset(associated_, 0, sizeof(associated_));
50
51   xbox_fetcher_.reset(new XboxDataFetcher(this));
52   if (!xbox_fetcher_->RegisterForNotifications())
53     xbox_fetcher_.reset();
54
55   hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault,
56                                             kIOHIDOptionsTypeNone));
57   if (CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) {
58     enabled_ = false;
59     return;
60   }
61
62   base::scoped_nsobject<NSArray> criteria([[NSArray alloc] initWithObjects:
63       DeviceMatching(kGenericDesktopUsagePage, kJoystickUsageNumber),
64       DeviceMatching(kGenericDesktopUsagePage, kGameUsageNumber),
65       DeviceMatching(kGenericDesktopUsagePage, kMultiAxisUsageNumber),
66       nil]);
67   IOHIDManagerSetDeviceMatchingMultiple(
68       hid_manager_ref_,
69       base::mac::NSToCFCast(criteria));
70
71   RegisterForNotifications();
72 }
73
74 void GamepadPlatformDataFetcherMac::RegisterForNotifications() {
75   // Register for plug/unplug notifications.
76   IOHIDManagerRegisterDeviceMatchingCallback(
77       hid_manager_ref_,
78       &DeviceAddCallback,
79       this);
80   IOHIDManagerRegisterDeviceRemovalCallback(
81       hid_manager_ref_,
82       DeviceRemoveCallback,
83       this);
84
85   // Register for value change notifications.
86   IOHIDManagerRegisterInputValueCallback(
87       hid_manager_ref_,
88       ValueChangedCallback,
89       this);
90
91   IOHIDManagerScheduleWithRunLoop(
92       hid_manager_ref_,
93       CFRunLoopGetMain(),
94       kCFRunLoopDefaultMode);
95
96   enabled_ = IOHIDManagerOpen(hid_manager_ref_,
97                               kIOHIDOptionsTypeNone) == kIOReturnSuccess;
98
99   if (xbox_fetcher_)
100     xbox_fetcher_->RegisterForNotifications();
101 }
102
103 void GamepadPlatformDataFetcherMac::UnregisterFromNotifications() {
104   IOHIDManagerUnscheduleFromRunLoop(
105       hid_manager_ref_,
106       CFRunLoopGetCurrent(),
107       kCFRunLoopDefaultMode);
108   IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone);
109   if (xbox_fetcher_)
110     xbox_fetcher_->UnregisterFromNotifications();
111 }
112
113 void GamepadPlatformDataFetcherMac::PauseHint(bool pause) {
114   if (pause)
115     UnregisterFromNotifications();
116   else
117     RegisterForNotifications();
118 }
119
120 GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() {
121   UnregisterFromNotifications();
122 }
123
124 GamepadPlatformDataFetcherMac*
125 GamepadPlatformDataFetcherMac::InstanceFromContext(void* context) {
126   return reinterpret_cast<GamepadPlatformDataFetcherMac*>(context);
127 }
128
129 void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context,
130                                                       IOReturn result,
131                                                       void* sender,
132                                                       IOHIDDeviceRef ref) {
133   InstanceFromContext(context)->DeviceAdd(ref);
134 }
135
136 void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context,
137                                                          IOReturn result,
138                                                          void* sender,
139                                                          IOHIDDeviceRef ref) {
140   InstanceFromContext(context)->DeviceRemove(ref);
141 }
142
143 void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context,
144                                                          IOReturn result,
145                                                          void* sender,
146                                                          IOHIDValueRef ref) {
147   InstanceFromContext(context)->ValueChanged(ref);
148 }
149
150 void GamepadPlatformDataFetcherMac::AddButtonsAndAxes(NSArray* elements,
151                                                       size_t slot) {
152   WebGamepad& pad = data_.items[slot];
153   AssociatedData& associated = associated_[slot];
154   CHECK(!associated.is_xbox);
155
156   pad.axesLength = 0;
157   pad.buttonsLength = 0;
158   pad.timestamp = 0;
159   memset(pad.axes, 0, sizeof(pad.axes));
160   memset(pad.buttons, 0, sizeof(pad.buttons));
161
162   for (id elem in elements) {
163     IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem);
164     uint32_t usagePage = IOHIDElementGetUsagePage(element);
165     uint32_t usage = IOHIDElementGetUsage(element);
166     if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button &&
167         usagePage == kButtonUsagePage) {
168       uint32_t button_index = usage - 1;
169       if (button_index < WebGamepad::buttonsLengthCap) {
170         associated.hid.button_elements[button_index] = element;
171         pad.buttonsLength = std::max(pad.buttonsLength, button_index + 1);
172       }
173     }
174     else if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc) {
175       uint32_t axis_index = usage - kAxisMinimumUsageNumber;
176       if (axis_index < WebGamepad::axesLengthCap) {
177         associated.hid.axis_minimums[axis_index] =
178             IOHIDElementGetLogicalMin(element);
179         associated.hid.axis_maximums[axis_index] =
180             IOHIDElementGetLogicalMax(element);
181         associated.hid.axis_elements[axis_index] = element;
182         pad.axesLength = std::max(pad.axesLength, axis_index + 1);
183       }
184     }
185   }
186 }
187
188 size_t GamepadPlatformDataFetcherMac::GetEmptySlot() {
189   // Find a free slot for this device.
190   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
191     if (!data_.items[slot].connected)
192       return slot;
193   }
194   return WebGamepads::itemsLengthCap;
195 }
196
197 size_t GamepadPlatformDataFetcherMac::GetSlotForDevice(IOHIDDeviceRef device) {
198   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
199     // If we already have this device, and it's already connected, don't do
200     // anything now.
201     if (data_.items[slot].connected &&
202         !associated_[slot].is_xbox &&
203         associated_[slot].hid.device_ref == device)
204       return WebGamepads::itemsLengthCap;
205   }
206   return GetEmptySlot();
207 }
208
209 size_t GamepadPlatformDataFetcherMac::GetSlotForXboxDevice(
210     XboxController* device) {
211   for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
212     if (associated_[slot].is_xbox &&
213         associated_[slot].xbox.location_id == device->location_id()) {
214       if (data_.items[slot].connected) {
215         // The device is already connected. No idea why we got a second "device
216         // added" call, but let's not add it twice.
217         DCHECK_EQ(associated_[slot].xbox.device, device);
218         return WebGamepads::itemsLengthCap;
219       } else {
220         // A device with the same location ID was previously connected, so put
221         // it in the same slot.
222         return slot;
223       }
224     }
225   }
226   return GetEmptySlot();
227 }
228
229 void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
230   using base::mac::CFToNSCast;
231   using base::mac::CFCastStrict;
232
233   if (!enabled_)
234     return;
235
236   // Find an index for this device.
237   size_t slot = GetSlotForDevice(device);
238
239   // We can't handle this many connected devices.
240   if (slot == WebGamepads::itemsLengthCap)
241     return;
242
243   NSNumber* vendor_id = CFToNSCast(CFCastStrict<CFNumberRef>(
244       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))));
245   NSNumber* product_id = CFToNSCast(CFCastStrict<CFNumberRef>(
246       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey))));
247   NSString* product = CFToNSCast(CFCastStrict<CFStringRef>(
248       IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))));
249   int vendor_int = [vendor_id intValue];
250   int product_int = [product_id intValue];
251
252   char vendor_as_str[5], product_as_str[5];
253   snprintf(vendor_as_str, sizeof(vendor_as_str), "%04x", vendor_int);
254   snprintf(product_as_str, sizeof(product_as_str), "%04x", product_int);
255   associated_[slot].hid.mapper =
256       GetGamepadStandardMappingFunction(vendor_as_str, product_as_str);
257
258   NSString* ident = [NSString stringWithFormat:
259       @"%@ (%sVendor: %04x Product: %04x)",
260       product,
261       associated_[slot].hid.mapper ? "STANDARD GAMEPAD " : "",
262       vendor_int,
263       product_int];
264   NSData* as16 = [ident dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
265
266   const size_t kOutputLengthBytes = sizeof(data_.items[slot].id);
267   memset(&data_.items[slot].id, 0, kOutputLengthBytes);
268   [as16 getBytes:data_.items[slot].id
269           length:kOutputLengthBytes - sizeof(WebKit::WebUChar)];
270
271   base::ScopedCFTypeRef<CFArrayRef> elements(
272       IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
273   AddButtonsAndAxes(CFToNSCast(elements), slot);
274
275   associated_[slot].hid.device_ref = device;
276   data_.items[slot].connected = true;
277   if (slot >= data_.length)
278     data_.length = slot + 1;
279 }
280
281 void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) {
282   if (!enabled_)
283     return;
284
285   // Find the index for this device.
286   size_t slot;
287   for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
288     if (data_.items[slot].connected &&
289         !associated_[slot].is_xbox &&
290         associated_[slot].hid.device_ref == device)
291       break;
292   }
293   DCHECK(slot < WebGamepads::itemsLengthCap);
294   // Leave associated device_ref so that it will be reconnected in the same
295   // location. Simply mark it as disconnected.
296   data_.items[slot].connected = false;
297 }
298
299 void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) {
300   if (!enabled_)
301     return;
302
303   IOHIDElementRef element = IOHIDValueGetElement(value);
304   IOHIDDeviceRef device = IOHIDElementGetDevice(element);
305
306   // Find device slot.
307   size_t slot;
308   for (slot = 0; slot < data_.length; ++slot) {
309     if (data_.items[slot].connected &&
310         !associated_[slot].is_xbox &&
311         associated_[slot].hid.device_ref == device)
312       break;
313   }
314   if (slot == data_.length)
315     return;
316
317   WebGamepad& pad = data_.items[slot];
318   AssociatedData& associated = associated_[slot];
319
320   // Find and fill in the associated button event, if any.
321   for (size_t i = 0; i < pad.buttonsLength; ++i) {
322     if (associated.hid.button_elements[i] == element) {
323       pad.buttons[i] = IOHIDValueGetIntegerValue(value) ? 1.f : 0.f;
324       pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
325       return;
326     }
327   }
328
329   // Find and fill in the associated axis event, if any.
330   for (size_t i = 0; i < pad.axesLength; ++i) {
331     if (associated.hid.axis_elements[i] == element) {
332       pad.axes[i] = NormalizeAxis(IOHIDValueGetIntegerValue(value),
333                                   associated.hid.axis_minimums[i],
334                                   associated.hid.axis_maximums[i]);
335       pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value));
336       return;
337     }
338   }
339 }
340
341 void GamepadPlatformDataFetcherMac::XboxDeviceAdd(XboxController* device) {
342   if (!enabled_)
343     return;
344
345   size_t slot = GetSlotForXboxDevice(device);
346
347   // We can't handle this many connected devices.
348   if (slot == WebGamepads::itemsLengthCap)
349     return;
350
351   device->SetLEDPattern(
352       (XboxController::LEDPattern)(XboxController::LED_FLASH_TOP_LEFT + slot));
353
354   NSString* ident =
355       [NSString stringWithFormat:
356           @"Xbox 360 Controller (STANDARD GAMEPAD Vendor: %04x Product: %04x)",
357               device->GetProductId(), device->GetVendorId()];
358   NSData* as16 = [ident dataUsingEncoding:NSUTF16StringEncoding];
359   const size_t kOutputLengthBytes = sizeof(data_.items[slot].id);
360   memset(&data_.items[slot].id, 0, kOutputLengthBytes);
361   [as16 getBytes:data_.items[slot].id
362           length:kOutputLengthBytes - sizeof(WebKit::WebUChar)];
363
364   associated_[slot].is_xbox = true;
365   associated_[slot].xbox.device = device;
366   associated_[slot].xbox.location_id = device->location_id();
367   data_.items[slot].connected = true;
368   data_.items[slot].axesLength = 4;
369   data_.items[slot].buttonsLength = 17;
370   data_.items[slot].timestamp = 0;
371   if (slot >= data_.length)
372     data_.length = slot + 1;
373 }
374
375 void GamepadPlatformDataFetcherMac::XboxDeviceRemove(XboxController* device) {
376   if (!enabled_)
377     return;
378
379   // Find the index for this device.
380   size_t slot;
381   for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) {
382     if (data_.items[slot].connected &&
383         associated_[slot].is_xbox &&
384         associated_[slot].xbox.device == device)
385       break;
386   }
387   DCHECK(slot < WebGamepads::itemsLengthCap);
388   // Leave associated location id so that the controller will be reconnected in
389   // the same slot if it is plugged in again. Simply mark it as disconnected.
390   data_.items[slot].connected = false;
391 }
392
393 void GamepadPlatformDataFetcherMac::XboxValueChanged(
394     XboxController* device, const XboxController::Data& data) {
395   // Find device slot.
396   size_t slot;
397   for (slot = 0; slot < data_.length; ++slot) {
398     if (data_.items[slot].connected &&
399         associated_[slot].is_xbox &&
400         associated_[slot].xbox.device == device)
401       break;
402   }
403   if (slot == data_.length)
404     return;
405
406   WebGamepad& pad = data_.items[slot];
407
408   for (size_t i = 0; i < 6; i++) {
409     pad.buttons[i] = data.buttons[i] ? 1.0f : 0.0f;
410   }
411   pad.buttons[6] = data.triggers[0];
412   pad.buttons[7] = data.triggers[1];
413   for (size_t i = 8; i < 17; i++) {
414     pad.buttons[i] = data.buttons[i - 2] ? 1.0f : 0.0f;
415   }
416   for (size_t i = 0; i < arraysize(data.axes); i++) {
417     pad.axes[i] = data.axes[i];
418   }
419
420   pad.timestamp = base::TimeTicks::Now().ToInternalValue();
421 }
422
423 void GamepadPlatformDataFetcherMac::GetGamepadData(WebGamepads* pads, bool) {
424   if (!enabled_ && !xbox_fetcher_) {
425     pads->length = 0;
426     return;
427   }
428
429   // Copy to the current state to the output buffer, using the mapping
430   // function, if there is one available.
431   pads->length = WebGamepads::itemsLengthCap;
432   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
433     if (!associated_[i].is_xbox && associated_[i].hid.mapper)
434       associated_[i].hid.mapper(data_.items[i], &pads->items[i]);
435     else
436       pads->items[i] = data_.items[i];
437   }
438 }
439
440 }  // namespace content