1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/hal/cocoa/CocoaGamepad.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,376 @@ 1.4 +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +// mostly derived from the Allegro source code at: 1.10 +// http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup 1.11 + 1.12 +#include "mozilla/dom/GamepadService.h" 1.13 +#include <CoreFoundation/CoreFoundation.h> 1.14 +#include <IOKit/hid/IOHIDBase.h> 1.15 +#include <IOKit/hid/IOHIDKeys.h> 1.16 +#include <IOKit/hid/IOHIDManager.h> 1.17 + 1.18 +#include <stdio.h> 1.19 +#include <vector> 1.20 + 1.21 +namespace { 1.22 + 1.23 +using mozilla::dom::GamepadService; 1.24 + 1.25 +using std::vector; 1.26 + 1.27 +struct Button { 1.28 + int id; 1.29 + IOHIDElementRef element; 1.30 +}; 1.31 + 1.32 +struct Axis { 1.33 + int id; 1.34 + IOHIDElementRef element; 1.35 + CFIndex min; 1.36 + CFIndex max; 1.37 +}; 1.38 + 1.39 +// These values can be found in the USB HID Usage Tables: 1.40 +// http://www.usb.org/developers/hidpage 1.41 +#define GENERIC_DESKTOP_USAGE_PAGE 0x01 1.42 +#define JOYSTICK_USAGE_NUMBER 0x04 1.43 +#define GAMEPAD_USAGE_NUMBER 0x05 1.44 +#define AXIS_MIN_USAGE_NUMBER 0x30 1.45 +#define AXIS_MAX_USAGE_NUMBER 0x35 1.46 +#define BUTTON_USAGE_PAGE 0x09 1.47 + 1.48 +class Gamepad { 1.49 + private: 1.50 + IOHIDDeviceRef mDevice; 1.51 + vector<Button> buttons; 1.52 + vector<Axis> axes; 1.53 + 1.54 + public: 1.55 + Gamepad() : mDevice(nullptr), mSuperIndex(-1) {} 1.56 + bool operator==(IOHIDDeviceRef device) const { return mDevice == device; } 1.57 + bool empty() const { return mDevice == nullptr; } 1.58 + void clear() { 1.59 + mDevice = nullptr; 1.60 + buttons.clear(); 1.61 + axes.clear(); 1.62 + mSuperIndex = -1; 1.63 + } 1.64 + void init(IOHIDDeviceRef device); 1.65 + size_t numButtons() { return buttons.size(); } 1.66 + size_t numAxes() { return axes.size(); } 1.67 + 1.68 + // Index given by our superclass. 1.69 + uint32_t mSuperIndex; 1.70 + 1.71 + const Button* lookupButton(IOHIDElementRef element) const { 1.72 + for (size_t i = 0; i < buttons.size(); i++) { 1.73 + if (buttons[i].element == element) 1.74 + return &buttons[i]; 1.75 + } 1.76 + return nullptr; 1.77 + } 1.78 + 1.79 + const Axis* lookupAxis(IOHIDElementRef element) const { 1.80 + for (size_t i = 0; i < axes.size(); i++) { 1.81 + if (axes[i].element == element) 1.82 + return &axes[i]; 1.83 + } 1.84 + return nullptr; 1.85 + } 1.86 +}; 1.87 + 1.88 +void Gamepad::init(IOHIDDeviceRef device) { 1.89 + clear(); 1.90 + mDevice = device; 1.91 + 1.92 + CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, 1.93 + nullptr, 1.94 + kIOHIDOptionsTypeNone); 1.95 + CFIndex n = CFArrayGetCount(elements); 1.96 + for (CFIndex i = 0; i < n; i++) { 1.97 + IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, 1.98 + i); 1.99 + uint32_t usagePage = IOHIDElementGetUsagePage(element); 1.100 + uint32_t usage = IOHIDElementGetUsage(element); 1.101 + 1.102 + if (usagePage == GENERIC_DESKTOP_USAGE_PAGE && 1.103 + usage >= AXIS_MIN_USAGE_NUMBER && 1.104 + usage <= AXIS_MAX_USAGE_NUMBER) 1.105 + { 1.106 + Axis axis = { int(axes.size()), 1.107 + element, 1.108 + IOHIDElementGetLogicalMin(element), 1.109 + IOHIDElementGetLogicalMax(element) }; 1.110 + axes.push_back(axis); 1.111 + } else if (usagePage == BUTTON_USAGE_PAGE) { 1.112 + Button button = { int(usage) - 1, element }; 1.113 + buttons.push_back(button); 1.114 + } else { 1.115 + //TODO: handle other usage pages 1.116 + } 1.117 + } 1.118 +} 1.119 + 1.120 +class DarwinGamepadService { 1.121 + private: 1.122 + IOHIDManagerRef mManager; 1.123 + vector<Gamepad> mGamepads; 1.124 + 1.125 + static void DeviceAddedCallback(void* data, IOReturn result, 1.126 + void* sender, IOHIDDeviceRef device); 1.127 + static void DeviceRemovedCallback(void* data, IOReturn result, 1.128 + void* sender, IOHIDDeviceRef device); 1.129 + static void InputValueChangedCallback(void* data, IOReturn result, 1.130 + void* sender, IOHIDValueRef newValue); 1.131 + 1.132 + void DeviceAdded(IOHIDDeviceRef device); 1.133 + void DeviceRemoved(IOHIDDeviceRef device); 1.134 + void InputValueChanged(IOHIDValueRef value); 1.135 + 1.136 + public: 1.137 + DarwinGamepadService(); 1.138 + ~DarwinGamepadService(); 1.139 + void Startup(); 1.140 + void Shutdown(); 1.141 +}; 1.142 + 1.143 +void 1.144 +DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) 1.145 +{ 1.146 + size_t slot = size_t(-1); 1.147 + for (size_t i = 0; i < mGamepads.size(); i++) { 1.148 + if (mGamepads[i] == device) 1.149 + return; 1.150 + if (slot == size_t(-1) && mGamepads[i].empty()) 1.151 + slot = i; 1.152 + } 1.153 + 1.154 + if (slot == size_t(-1)) { 1.155 + slot = mGamepads.size(); 1.156 + mGamepads.push_back(Gamepad()); 1.157 + } 1.158 + mGamepads[slot].init(device); 1.159 + 1.160 + // Gather some identifying information 1.161 + CFNumberRef vendorIdRef = 1.162 + (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)); 1.163 + CFNumberRef productIdRef = 1.164 + (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)); 1.165 + CFStringRef productRef = 1.166 + (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); 1.167 + int vendorId, productId; 1.168 + CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId); 1.169 + CFNumberGetValue(productIdRef, kCFNumberIntType, &productId); 1.170 + char product_name[128]; 1.171 + CFStringGetCString(productRef, product_name, 1.172 + sizeof(product_name), kCFStringEncodingASCII); 1.173 + char buffer[256]; 1.174 + sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name); 1.175 + nsRefPtr<GamepadService> service(GamepadService::GetService()); 1.176 + mGamepads[slot].mSuperIndex = service->AddGamepad(buffer, 1.177 + mozilla::dom::NoMapping, 1.178 + (int)mGamepads[slot].numButtons(), 1.179 + (int)mGamepads[slot].numAxes()); 1.180 +} 1.181 + 1.182 +void 1.183 +DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) 1.184 +{ 1.185 + nsRefPtr<GamepadService> service(GamepadService::GetService()); 1.186 + for (size_t i = 0; i < mGamepads.size(); i++) { 1.187 + if (mGamepads[i] == device) { 1.188 + service->RemoveGamepad(mGamepads[i].mSuperIndex); 1.189 + mGamepads[i].clear(); 1.190 + return; 1.191 + } 1.192 + } 1.193 +} 1.194 + 1.195 +void 1.196 +DarwinGamepadService::InputValueChanged(IOHIDValueRef value) 1.197 +{ 1.198 + nsRefPtr<GamepadService> service(GamepadService::GetService()); 1.199 + IOHIDElementRef element = IOHIDValueGetElement(value); 1.200 + IOHIDDeviceRef device = IOHIDElementGetDevice(element); 1.201 + for (size_t i = 0; i < mGamepads.size(); i++) { 1.202 + const Gamepad &gamepad = mGamepads[i]; 1.203 + if (gamepad == device) { 1.204 + if (const Axis* axis = gamepad.lookupAxis(element)) { 1.205 + double d = IOHIDValueGetIntegerValue(value); 1.206 + double v = 2.0f * (d - axis->min) / 1.207 + (double)(axis->max - axis->min) - 1.0f; 1.208 + service->NewAxisMoveEvent(i, axis->id, v); 1.209 + } else if (const Button* button = gamepad.lookupButton(element)) { 1.210 + bool pressed = IOHIDValueGetIntegerValue(value) != 0; 1.211 + service->NewButtonEvent(i, button->id, pressed); 1.212 + } 1.213 + return; 1.214 + } 1.215 + } 1.216 +} 1.217 + 1.218 +void 1.219 +DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result, 1.220 + void* sender, IOHIDDeviceRef device) 1.221 +{ 1.222 + DarwinGamepadService* service = (DarwinGamepadService*)data; 1.223 + service->DeviceAdded(device); 1.224 +} 1.225 + 1.226 +void 1.227 +DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result, 1.228 + void* sender, IOHIDDeviceRef device) 1.229 +{ 1.230 + DarwinGamepadService* service = (DarwinGamepadService*)data; 1.231 + service->DeviceRemoved(device); 1.232 +} 1.233 + 1.234 +void 1.235 +DarwinGamepadService::InputValueChangedCallback(void* data, 1.236 + IOReturn result, 1.237 + void* sender, 1.238 + IOHIDValueRef newValue) 1.239 +{ 1.240 + DarwinGamepadService* service = (DarwinGamepadService*)data; 1.241 + service->InputValueChanged(newValue); 1.242 +} 1.243 + 1.244 +static CFMutableDictionaryRef 1.245 +MatchingDictionary(UInt32 inUsagePage, UInt32 inUsage) 1.246 +{ 1.247 + CFMutableDictionaryRef dict = 1.248 + CFDictionaryCreateMutable(kCFAllocatorDefault, 1.249 + 0, 1.250 + &kCFTypeDictionaryKeyCallBacks, 1.251 + &kCFTypeDictionaryValueCallBacks); 1.252 + if (!dict) 1.253 + return nullptr; 1.254 + CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, 1.255 + kCFNumberIntType, 1.256 + &inUsagePage); 1.257 + if (!number) { 1.258 + CFRelease(dict); 1.259 + return nullptr; 1.260 + } 1.261 + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number); 1.262 + CFRelease(number); 1.263 + 1.264 + number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage); 1.265 + if (!number) { 1.266 + CFRelease(dict); 1.267 + return nullptr; 1.268 + } 1.269 + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number); 1.270 + CFRelease(number); 1.271 + 1.272 + return dict; 1.273 +} 1.274 + 1.275 +DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {} 1.276 + 1.277 +DarwinGamepadService::~DarwinGamepadService() 1.278 +{ 1.279 + if (mManager != nullptr) 1.280 + CFRelease(mManager); 1.281 +} 1.282 + 1.283 +void DarwinGamepadService::Startup() 1.284 +{ 1.285 + if (mManager != nullptr) 1.286 + return; 1.287 + 1.288 + IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, 1.289 + kIOHIDOptionsTypeNone); 1.290 + 1.291 + CFMutableDictionaryRef criteria_arr[2]; 1.292 + criteria_arr[0] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE, 1.293 + JOYSTICK_USAGE_NUMBER); 1.294 + if (!criteria_arr[0]) { 1.295 + CFRelease(manager); 1.296 + return; 1.297 + } 1.298 + 1.299 + criteria_arr[1] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE, 1.300 + GAMEPAD_USAGE_NUMBER); 1.301 + if (!criteria_arr[1]) { 1.302 + CFRelease(criteria_arr[0]); 1.303 + CFRelease(manager); 1.304 + return; 1.305 + } 1.306 + 1.307 + CFArrayRef criteria = 1.308 + CFArrayCreate(kCFAllocatorDefault, (const void**)criteria_arr, 2, nullptr); 1.309 + if (!criteria) { 1.310 + CFRelease(criteria_arr[1]); 1.311 + CFRelease(criteria_arr[0]); 1.312 + CFRelease(manager); 1.313 + return; 1.314 + } 1.315 + 1.316 + IOHIDManagerSetDeviceMatchingMultiple(manager, criteria); 1.317 + CFRelease(criteria); 1.318 + CFRelease(criteria_arr[1]); 1.319 + CFRelease(criteria_arr[0]); 1.320 + 1.321 + IOHIDManagerRegisterDeviceMatchingCallback(manager, 1.322 + DeviceAddedCallback, 1.323 + this); 1.324 + IOHIDManagerRegisterDeviceRemovalCallback(manager, 1.325 + DeviceRemovedCallback, 1.326 + this); 1.327 + IOHIDManagerRegisterInputValueCallback(manager, 1.328 + InputValueChangedCallback, 1.329 + this); 1.330 + IOHIDManagerScheduleWithRunLoop(manager, 1.331 + CFRunLoopGetCurrent(), 1.332 + kCFRunLoopDefaultMode); 1.333 + IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone); 1.334 + if (rv != kIOReturnSuccess) { 1.335 + CFRelease(manager); 1.336 + return; 1.337 + } 1.338 + 1.339 + mManager = manager; 1.340 +} 1.341 + 1.342 +void DarwinGamepadService::Shutdown() 1.343 +{ 1.344 + IOHIDManagerRef manager = (IOHIDManagerRef)mManager; 1.345 + if (manager) { 1.346 + IOHIDManagerClose(manager, 0); 1.347 + CFRelease(manager); 1.348 + mManager = nullptr; 1.349 + } 1.350 +} 1.351 + 1.352 +} // namespace 1.353 + 1.354 +namespace mozilla { 1.355 +namespace hal_impl { 1.356 + 1.357 +DarwinGamepadService* gService = nullptr; 1.358 + 1.359 +void StartMonitoringGamepadStatus() 1.360 +{ 1.361 + if (gService) 1.362 + return; 1.363 + 1.364 + gService = new DarwinGamepadService(); 1.365 + gService->Startup(); 1.366 +} 1.367 + 1.368 +void StopMonitoringGamepadStatus() 1.369 +{ 1.370 + if (!gService) 1.371 + return; 1.372 + 1.373 + gService->Shutdown(); 1.374 + delete gService; 1.375 + gService = nullptr; 1.376 +} 1.377 + 1.378 +} // namespace hal_impl 1.379 +} // namespace mozilla