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