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>
20 using mozilla::dom::GamepadService
;
26 IOHIDElementRef element
;
31 IOHIDElementRef element
;
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
47 IOHIDDeviceRef mDevice
;
48 vector
<Button
> buttons
;
52 Gamepad() : mDevice(nullptr), mSuperIndex(-1) {}
53 bool operator==(IOHIDDeviceRef device
) const { return mDevice
== device
; }
54 bool empty() const { return mDevice
== nullptr; }
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.
68 const Button
* lookupButton(IOHIDElementRef element
) const {
69 for (size_t i
= 0; i
< buttons
.size(); i
++) {
70 if (buttons
[i
].element
== element
)
76 const Axis
* lookupAxis(IOHIDElementRef element
) const {
77 for (size_t i
= 0; i
< axes
.size(); i
++) {
78 if (axes
[i
].element
== element
)
85 void Gamepad::init(IOHIDDeviceRef device
) {
89 CFArrayRef elements
= IOHIDDeviceCopyMatchingElements(device
,
91 kIOHIDOptionsTypeNone
);
92 CFIndex n
= CFArrayGetCount(elements
);
93 for (CFIndex i
= 0; i
< n
; i
++) {
94 IOHIDElementRef element
= (IOHIDElementRef
)CFArrayGetValueAtIndex(elements
,
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()),
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
);
112 //TODO: handle other usage pages
117 class DarwinGamepadService
{
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
);
134 DarwinGamepadService();
135 ~DarwinGamepadService();
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
)
147 if (slot
== size_t(-1) && mGamepads
[i
].empty())
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
);
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());
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();
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
);
216 DarwinGamepadService::DeviceAddedCallback(void* data
, IOReturn result
,
217 void* sender
, IOHIDDeviceRef device
)
219 DarwinGamepadService
* service
= (DarwinGamepadService
*)data
;
220 service
->DeviceAdded(device
);
224 DarwinGamepadService::DeviceRemovedCallback(void* data
, IOReturn result
,
225 void* sender
, IOHIDDeviceRef device
)
227 DarwinGamepadService
* service
= (DarwinGamepadService
*)data
;
228 service
->DeviceRemoved(device
);
232 DarwinGamepadService::InputValueChangedCallback(void* data
,
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
);
251 CFNumberRef number
= CFNumberCreate(kCFAllocatorDefault
,
258 CFDictionarySetValue(dict
, CFSTR(kIOHIDDeviceUsagePageKey
), number
);
261 number
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberIntType
, &inUsage
);
266 CFDictionarySetValue(dict
, CFSTR(kIOHIDDeviceUsageKey
), number
);
272 DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {}
274 DarwinGamepadService::~DarwinGamepadService()
276 if (mManager
!= nullptr)
280 void DarwinGamepadService::Startup()
282 if (mManager
!= nullptr)
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]) {
296 criteria_arr
[1] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE
,
297 GAMEPAD_USAGE_NUMBER
);
298 if (!criteria_arr
[1]) {
299 CFRelease(criteria_arr
[0]);
304 CFArrayRef criteria
=
305 CFArrayCreate(kCFAllocatorDefault
, (const void**)criteria_arr
, 2, nullptr);
307 CFRelease(criteria_arr
[1]);
308 CFRelease(criteria_arr
[0]);
313 IOHIDManagerSetDeviceMatchingMultiple(manager
, criteria
);
315 CFRelease(criteria_arr
[1]);
316 CFRelease(criteria_arr
[0]);
318 IOHIDManagerRegisterDeviceMatchingCallback(manager
,
321 IOHIDManagerRegisterDeviceRemovalCallback(manager
,
322 DeviceRemovedCallback
,
324 IOHIDManagerRegisterInputValueCallback(manager
,
325 InputValueChangedCallback
,
327 IOHIDManagerScheduleWithRunLoop(manager
,
328 CFRunLoopGetCurrent(),
329 kCFRunLoopDefaultMode
);
330 IOReturn rv
= IOHIDManagerOpen(manager
, kIOHIDOptionsTypeNone
);
331 if (rv
!= kIOReturnSuccess
) {
339 void DarwinGamepadService::Shutdown()
341 IOHIDManagerRef manager
= (IOHIDManagerRef
)mManager
;
343 IOHIDManagerClose(manager
, 0);
354 DarwinGamepadService
* gService
= nullptr;
356 void StartMonitoringGamepadStatus()
361 gService
= new DarwinGamepadService();
365 void StopMonitoringGamepadStatus()
370 gService
->Shutdown();
375 } // namespace hal_impl
376 } // namespace mozilla