Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / hal / cocoa / CocoaGamepad.cpp
blobc1e126c1e5423dee946529ad13cdcd50e6a7d23b
1 /* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // mostly derived from the Allegro source code at:
7 // http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup
9 #include "mozilla/dom/GamepadService.h"
10 #include <CoreFoundation/CoreFoundation.h>
11 #include <IOKit/hid/IOHIDBase.h>
12 #include <IOKit/hid/IOHIDKeys.h>
13 #include <IOKit/hid/IOHIDManager.h>
15 #include <stdio.h>
16 #include <vector>
18 namespace {
20 using mozilla::dom::GamepadService;
22 using std::vector;
24 struct Button {
25 int id;
26 IOHIDElementRef element;
29 struct Axis {
30 int id;
31 IOHIDElementRef element;
32 CFIndex min;
33 CFIndex max;
36 // These values can be found in the USB HID Usage Tables:
37 // http://www.usb.org/developers/hidpage
38 #define GENERIC_DESKTOP_USAGE_PAGE 0x01
39 #define JOYSTICK_USAGE_NUMBER 0x04
40 #define GAMEPAD_USAGE_NUMBER 0x05
41 #define AXIS_MIN_USAGE_NUMBER 0x30
42 #define AXIS_MAX_USAGE_NUMBER 0x35
43 #define BUTTON_USAGE_PAGE 0x09
45 class Gamepad {
46 private:
47 IOHIDDeviceRef mDevice;
48 vector<Button> buttons;
49 vector<Axis> axes;
51 public:
52 Gamepad() : mDevice(nullptr), mSuperIndex(-1) {}
53 bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
54 bool empty() const { return mDevice == nullptr; }
55 void clear() {
56 mDevice = nullptr;
57 buttons.clear();
58 axes.clear();
59 mSuperIndex = -1;
61 void init(IOHIDDeviceRef device);
62 size_t numButtons() { return buttons.size(); }
63 size_t numAxes() { return axes.size(); }
65 // Index given by our superclass.
66 uint32_t mSuperIndex;
68 const Button* lookupButton(IOHIDElementRef element) const {
69 for (size_t i = 0; i < buttons.size(); i++) {
70 if (buttons[i].element == element)
71 return &buttons[i];
73 return nullptr;
76 const Axis* lookupAxis(IOHIDElementRef element) const {
77 for (size_t i = 0; i < axes.size(); i++) {
78 if (axes[i].element == element)
79 return &axes[i];
81 return nullptr;
85 void Gamepad::init(IOHIDDeviceRef device) {
86 clear();
87 mDevice = device;
89 CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device,
90 nullptr,
91 kIOHIDOptionsTypeNone);
92 CFIndex n = CFArrayGetCount(elements);
93 for (CFIndex i = 0; i < n; i++) {
94 IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements,
95 i);
96 uint32_t usagePage = IOHIDElementGetUsagePage(element);
97 uint32_t usage = IOHIDElementGetUsage(element);
99 if (usagePage == GENERIC_DESKTOP_USAGE_PAGE &&
100 usage >= AXIS_MIN_USAGE_NUMBER &&
101 usage <= AXIS_MAX_USAGE_NUMBER)
103 Axis axis = { int(axes.size()),
104 element,
105 IOHIDElementGetLogicalMin(element),
106 IOHIDElementGetLogicalMax(element) };
107 axes.push_back(axis);
108 } else if (usagePage == BUTTON_USAGE_PAGE) {
109 Button button = { int(usage) - 1, element };
110 buttons.push_back(button);
111 } else {
112 //TODO: handle other usage pages
117 class DarwinGamepadService {
118 private:
119 IOHIDManagerRef mManager;
120 vector<Gamepad> mGamepads;
122 static void DeviceAddedCallback(void* data, IOReturn result,
123 void* sender, IOHIDDeviceRef device);
124 static void DeviceRemovedCallback(void* data, IOReturn result,
125 void* sender, IOHIDDeviceRef device);
126 static void InputValueChangedCallback(void* data, IOReturn result,
127 void* sender, IOHIDValueRef newValue);
129 void DeviceAdded(IOHIDDeviceRef device);
130 void DeviceRemoved(IOHIDDeviceRef device);
131 void InputValueChanged(IOHIDValueRef value);
133 public:
134 DarwinGamepadService();
135 ~DarwinGamepadService();
136 void Startup();
137 void Shutdown();
140 void
141 DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device)
143 size_t slot = size_t(-1);
144 for (size_t i = 0; i < mGamepads.size(); i++) {
145 if (mGamepads[i] == device)
146 return;
147 if (slot == size_t(-1) && mGamepads[i].empty())
148 slot = i;
151 if (slot == size_t(-1)) {
152 slot = mGamepads.size();
153 mGamepads.push_back(Gamepad());
155 mGamepads[slot].init(device);
157 // Gather some identifying information
158 CFNumberRef vendorIdRef =
159 (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
160 CFNumberRef productIdRef =
161 (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
162 CFStringRef productRef =
163 (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
164 int vendorId, productId;
165 CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
166 CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
167 char product_name[128];
168 CFStringGetCString(productRef, product_name,
169 sizeof(product_name), kCFStringEncodingASCII);
170 char buffer[256];
171 sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name);
172 nsRefPtr<GamepadService> service(GamepadService::GetService());
173 mGamepads[slot].mSuperIndex = service->AddGamepad(buffer,
174 mozilla::dom::GamepadMappingType::_empty,
175 (int)mGamepads[slot].numButtons(),
176 (int)mGamepads[slot].numAxes());
179 void
180 DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device)
182 nsRefPtr<GamepadService> service(GamepadService::GetService());
183 for (size_t i = 0; i < mGamepads.size(); i++) {
184 if (mGamepads[i] == device) {
185 service->RemoveGamepad(mGamepads[i].mSuperIndex);
186 mGamepads[i].clear();
187 return;
192 void
193 DarwinGamepadService::InputValueChanged(IOHIDValueRef value)
195 nsRefPtr<GamepadService> service(GamepadService::GetService());
196 IOHIDElementRef element = IOHIDValueGetElement(value);
197 IOHIDDeviceRef device = IOHIDElementGetDevice(element);
198 for (size_t i = 0; i < mGamepads.size(); i++) {
199 const Gamepad &gamepad = mGamepads[i];
200 if (gamepad == device) {
201 if (const Axis* axis = gamepad.lookupAxis(element)) {
202 double d = IOHIDValueGetIntegerValue(value);
203 double v = 2.0f * (d - axis->min) /
204 (double)(axis->max - axis->min) - 1.0f;
205 service->NewAxisMoveEvent(i, axis->id, v);
206 } else if (const Button* button = gamepad.lookupButton(element)) {
207 bool pressed = IOHIDValueGetIntegerValue(value) != 0;
208 service->NewButtonEvent(i, button->id, pressed);
210 return;
215 void
216 DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
217 void* sender, IOHIDDeviceRef device)
219 DarwinGamepadService* service = (DarwinGamepadService*)data;
220 service->DeviceAdded(device);
223 void
224 DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
225 void* sender, IOHIDDeviceRef device)
227 DarwinGamepadService* service = (DarwinGamepadService*)data;
228 service->DeviceRemoved(device);
231 void
232 DarwinGamepadService::InputValueChangedCallback(void* data,
233 IOReturn result,
234 void* sender,
235 IOHIDValueRef newValue)
237 DarwinGamepadService* service = (DarwinGamepadService*)data;
238 service->InputValueChanged(newValue);
241 static CFMutableDictionaryRef
242 MatchingDictionary(UInt32 inUsagePage, UInt32 inUsage)
244 CFMutableDictionaryRef dict =
245 CFDictionaryCreateMutable(kCFAllocatorDefault,
247 &kCFTypeDictionaryKeyCallBacks,
248 &kCFTypeDictionaryValueCallBacks);
249 if (!dict)
250 return nullptr;
251 CFNumberRef number = CFNumberCreate(kCFAllocatorDefault,
252 kCFNumberIntType,
253 &inUsagePage);
254 if (!number) {
255 CFRelease(dict);
256 return nullptr;
258 CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
259 CFRelease(number);
261 number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
262 if (!number) {
263 CFRelease(dict);
264 return nullptr;
266 CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
267 CFRelease(number);
269 return dict;
272 DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {}
274 DarwinGamepadService::~DarwinGamepadService()
276 if (mManager != nullptr)
277 CFRelease(mManager);
280 void DarwinGamepadService::Startup()
282 if (mManager != nullptr)
283 return;
285 IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault,
286 kIOHIDOptionsTypeNone);
288 CFMutableDictionaryRef criteria_arr[2];
289 criteria_arr[0] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE,
290 JOYSTICK_USAGE_NUMBER);
291 if (!criteria_arr[0]) {
292 CFRelease(manager);
293 return;
296 criteria_arr[1] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE,
297 GAMEPAD_USAGE_NUMBER);
298 if (!criteria_arr[1]) {
299 CFRelease(criteria_arr[0]);
300 CFRelease(manager);
301 return;
304 CFArrayRef criteria =
305 CFArrayCreate(kCFAllocatorDefault, (const void**)criteria_arr, 2, nullptr);
306 if (!criteria) {
307 CFRelease(criteria_arr[1]);
308 CFRelease(criteria_arr[0]);
309 CFRelease(manager);
310 return;
313 IOHIDManagerSetDeviceMatchingMultiple(manager, criteria);
314 CFRelease(criteria);
315 CFRelease(criteria_arr[1]);
316 CFRelease(criteria_arr[0]);
318 IOHIDManagerRegisterDeviceMatchingCallback(manager,
319 DeviceAddedCallback,
320 this);
321 IOHIDManagerRegisterDeviceRemovalCallback(manager,
322 DeviceRemovedCallback,
323 this);
324 IOHIDManagerRegisterInputValueCallback(manager,
325 InputValueChangedCallback,
326 this);
327 IOHIDManagerScheduleWithRunLoop(manager,
328 CFRunLoopGetCurrent(),
329 kCFRunLoopDefaultMode);
330 IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
331 if (rv != kIOReturnSuccess) {
332 CFRelease(manager);
333 return;
336 mManager = manager;
339 void DarwinGamepadService::Shutdown()
341 IOHIDManagerRef manager = (IOHIDManagerRef)mManager;
342 if (manager) {
343 IOHIDManagerClose(manager, 0);
344 CFRelease(manager);
345 mManager = nullptr;
349 } // namespace
351 namespace mozilla {
352 namespace hal_impl {
354 DarwinGamepadService* gService = nullptr;
356 void StartMonitoringGamepadStatus()
358 if (gService)
359 return;
361 gService = new DarwinGamepadService();
362 gService->Startup();
365 void StopMonitoringGamepadStatus()
367 if (!gService)
368 return;
370 gService->Shutdown();
371 delete gService;
372 gService = nullptr;
375 } // namespace hal_impl
376 } // namespace mozilla