OLD | NEW |
| (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/platform_data_fetcher_mac.h" | |
6 | |
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" | |
12 | |
13 #include <IOKit/hid/IOHIDKeys.h> | |
14 #import <Foundation/Foundation.h> | |
15 | |
16 namespace content { | |
17 | |
18 namespace { | |
19 | |
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]; | |
27 } | |
28 | |
29 float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) { | |
30 return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f; | |
31 } | |
32 | |
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; | |
41 | |
42 } // namespace | |
43 | |
44 GamepadPlatformDataFetcherMac::GamepadPlatformDataFetcherMac() | |
45 : enabled_(true) { | |
46 hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault, | |
47 kIOHIDOptionsTypeNone)); | |
48 if (CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) { | |
49 enabled_ = false; | |
50 return; | |
51 } | |
52 | |
53 scoped_nsobject<NSArray> criteria([[NSArray alloc] initWithObjects: | |
54 DeviceMatching(kGenericDesktopUsagePage, kJoystickUsageNumber), | |
55 DeviceMatching(kGenericDesktopUsagePage, kGameUsageNumber), | |
56 DeviceMatching(kGenericDesktopUsagePage, kMultiAxisUsageNumber), | |
57 nil]); | |
58 IOHIDManagerSetDeviceMatchingMultiple( | |
59 hid_manager_ref_, | |
60 base::mac::NSToCFCast(criteria)); | |
61 | |
62 RegisterForNotifications(); | |
63 } | |
64 | |
65 void GamepadPlatformDataFetcherMac::RegisterForNotifications() { | |
66 // Register for plug/unplug notifications. | |
67 IOHIDManagerRegisterDeviceMatchingCallback( | |
68 hid_manager_ref_, | |
69 &DeviceAddCallback, | |
70 this); | |
71 IOHIDManagerRegisterDeviceRemovalCallback( | |
72 hid_manager_ref_, | |
73 DeviceRemoveCallback, | |
74 this); | |
75 | |
76 // Register for value change notifications. | |
77 IOHIDManagerRegisterInputValueCallback( | |
78 hid_manager_ref_, | |
79 ValueChangedCallback, | |
80 this); | |
81 | |
82 IOHIDManagerScheduleWithRunLoop( | |
83 hid_manager_ref_, | |
84 CFRunLoopGetMain(), | |
85 kCFRunLoopDefaultMode); | |
86 | |
87 enabled_ = IOHIDManagerOpen(hid_manager_ref_, | |
88 kIOHIDOptionsTypeSeizeDevice) == kIOReturnSuccess; | |
89 } | |
90 | |
91 void GamepadPlatformDataFetcherMac::UnregisterFromNotifications() { | |
92 IOHIDManagerUnscheduleFromRunLoop( | |
93 hid_manager_ref_, | |
94 CFRunLoopGetCurrent(), | |
95 kCFRunLoopDefaultMode); | |
96 IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone); | |
97 } | |
98 | |
99 void GamepadPlatformDataFetcherMac::PauseHint(bool pause) { | |
100 if (pause) | |
101 UnregisterFromNotifications(); | |
102 else | |
103 RegisterForNotifications(); | |
104 } | |
105 | |
106 GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() { | |
107 UnregisterFromNotifications(); | |
108 } | |
109 | |
110 GamepadPlatformDataFetcherMac* | |
111 GamepadPlatformDataFetcherMac::InstanceFromContext( | |
112 void* context) { | |
113 return reinterpret_cast<GamepadPlatformDataFetcherMac*>(context); | |
114 } | |
115 | |
116 void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context, | |
117 IOReturn result, | |
118 void* sender, | |
119 IOHIDDeviceRef ref) { | |
120 InstanceFromContext(context)->DeviceAdd(ref); | |
121 } | |
122 | |
123 void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context, | |
124 IOReturn result, | |
125 void* sender, | |
126 IOHIDDeviceRef ref) { | |
127 InstanceFromContext(context)->DeviceRemove(ref); | |
128 } | |
129 | |
130 void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context, | |
131 IOReturn result, | |
132 void* sender, | |
133 IOHIDValueRef ref) { | |
134 InstanceFromContext(context)->ValueChanged(ref); | |
135 } | |
136 | |
137 void GamepadPlatformDataFetcherMac::AddButtonsAndAxes(NSArray* elements, | |
138 size_t slot) { | |
139 WebKit::WebGamepad& pad = data_.items[slot]; | |
140 AssociatedData& associated = associated_[slot]; | |
141 | |
142 pad.axesLength = 0; | |
143 pad.buttonsLength = 0; | |
144 memset(pad.axes, 0, sizeof(pad.axes)); | |
145 memset(pad.buttons, 0, sizeof(pad.buttons)); | |
146 | |
147 for (id elem in elements) { | |
148 IOHIDElementRef element = reinterpret_cast<IOHIDElementRef>(elem); | |
149 uint32_t usagePage = IOHIDElementGetUsagePage(element); | |
150 uint32_t usage = IOHIDElementGetUsage(element); | |
151 if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button && | |
152 usagePage == kButtonUsagePage) { | |
153 uint32_t button_index = usage - 1; | |
154 if (button_index < WebKit::WebGamepad::buttonsLengthCap) { | |
155 associated.button_elements[button_index] = element; | |
156 pad.buttonsLength = std::max(pad.buttonsLength, button_index + 1); | |
157 } | |
158 } | |
159 else if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc) { | |
160 uint32_t axis_index = usage - kAxisMinimumUsageNumber; | |
161 if (axis_index < WebKit::WebGamepad::axesLengthCap) { | |
162 associated.axis_minimums[axis_index] = | |
163 IOHIDElementGetLogicalMin(element); | |
164 associated.axis_maximums[axis_index] = | |
165 IOHIDElementGetLogicalMax(element); | |
166 associated.axis_elements[axis_index] = element; | |
167 pad.axesLength = std::max(pad.axesLength, axis_index + 1); | |
168 } | |
169 } | |
170 } | |
171 } | |
172 | |
173 void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) { | |
174 using WebKit::WebGamepad; | |
175 using WebKit::WebGamepads; | |
176 using base::mac::CFToNSCast; | |
177 using base::mac::CFCastStrict; | |
178 size_t slot; | |
179 | |
180 if (!enabled_) | |
181 return; | |
182 | |
183 // Find an index for this device. | |
184 for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) { | |
185 // If we already have this device, and it's already connected, don't do | |
186 // anything now. | |
187 if (associated_[slot].device_ref == device && data_.items[slot].connected) | |
188 return; | |
189 if (!data_.items[slot].connected) | |
190 break; | |
191 } | |
192 | |
193 // We can't handle this many connected devices. | |
194 if (slot == WebGamepads::itemsLengthCap) | |
195 return; | |
196 | |
197 NSNumber* vendor_id = CFToNSCast(CFCastStrict<CFNumberRef>( | |
198 IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)))); | |
199 NSNumber* product_id = CFToNSCast(CFCastStrict<CFNumberRef>( | |
200 IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)))); | |
201 NSString* product = CFToNSCast(CFCastStrict<CFStringRef>( | |
202 IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)))); | |
203 | |
204 NSString* ident([NSString stringWithFormat: | |
205 @"%@ (Vendor: %04x Product: %04x)", | |
206 product, | |
207 [vendor_id intValue], | |
208 [product_id intValue]]); | |
209 NSData* as16 = [ident dataUsingEncoding:NSUTF16StringEncoding]; | |
210 | |
211 const size_t kOutputLengthBytes = sizeof(data_.items[slot].id); | |
212 memset(&data_.items[slot].id, 0, kOutputLengthBytes); | |
213 [as16 getBytes:data_.items[slot].id | |
214 length:kOutputLengthBytes - sizeof(WebKit::WebUChar)]; | |
215 | |
216 base::mac::ScopedCFTypeRef<CFArrayRef> elements( | |
217 IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone)); | |
218 AddButtonsAndAxes(CFToNSCast(elements), slot); | |
219 | |
220 associated_[slot].device_ref = device; | |
221 data_.items[slot].connected = true; | |
222 if (slot >= data_.length) | |
223 data_.length = slot + 1; | |
224 } | |
225 | |
226 void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) { | |
227 using WebKit::WebGamepads; | |
228 size_t slot; | |
229 if (!enabled_) | |
230 return; | |
231 | |
232 // Find the index for this device. | |
233 for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) { | |
234 if (associated_[slot].device_ref == device && data_.items[slot].connected) | |
235 break; | |
236 } | |
237 DCHECK(slot < WebGamepads::itemsLengthCap); | |
238 // Leave associated device_ref so that it will be reconnected in the same | |
239 // location. Simply mark it as disconnected. | |
240 data_.items[slot].connected = false; | |
241 } | |
242 | |
243 void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) { | |
244 if (!enabled_) | |
245 return; | |
246 | |
247 IOHIDElementRef element = IOHIDValueGetElement(value); | |
248 IOHIDDeviceRef device = IOHIDElementGetDevice(element); | |
249 | |
250 // Find device slot. | |
251 size_t slot; | |
252 for (slot = 0; slot < data_.length; ++slot) { | |
253 if (data_.items[slot].connected && associated_[slot].device_ref == device) | |
254 break; | |
255 } | |
256 if (slot == data_.length) | |
257 return; | |
258 | |
259 WebKit::WebGamepad& pad = data_.items[slot]; | |
260 AssociatedData& associated = associated_[slot]; | |
261 | |
262 // Find and fill in the associated button event, if any. | |
263 for (size_t i = 0; i < pad.buttonsLength; ++i) { | |
264 if (associated.button_elements[i] == element) { | |
265 pad.buttons[i] = IOHIDValueGetIntegerValue(value) ? 1.f : 0.f; | |
266 return; | |
267 } | |
268 } | |
269 | |
270 // Find and fill in the associated axis event, if any. | |
271 for (size_t i = 0; i < pad.axesLength; ++i) { | |
272 if (associated.axis_elements[i] == element) { | |
273 pad.axes[i] = NormalizeAxis(IOHIDValueGetIntegerValue(value), | |
274 associated.axis_minimums[i], | |
275 associated.axis_maximums[i]); | |
276 return; | |
277 } | |
278 } | |
279 } | |
280 | |
281 void GamepadPlatformDataFetcherMac::GetGamepadData( | |
282 WebKit::WebGamepads* pads, | |
283 bool) { | |
284 if (!enabled_) { | |
285 pads->length = 0; | |
286 return; | |
287 } | |
288 memcpy(pads, &data_, sizeof(WebKit::WebGamepads)); | |
289 } | |
290 | |
291 } // namespace content | |
OLD | NEW |