hal/cocoa/CocoaGamepad.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     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;
    27 };
    29 struct Axis {
    30   int id;
    31   IOHIDElementRef element;
    32   CFIndex min;
    33   CFIndex max;
    34 };
    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;
    60   }
    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];
    72     }
    73     return nullptr;
    74   }
    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];
    80     }
    81     return nullptr;
    82   }
    83 };
    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)
   102     {
   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
   113     }
   114   }
   115 }
   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();
   138 };
   140 void
   141 DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device)
   142 {
   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;
   149   }
   151   if (slot == size_t(-1)) {
   152     slot = mGamepads.size();
   153     mGamepads.push_back(Gamepad());
   154   }
   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::NoMapping,
   175                                                     (int)mGamepads[slot].numButtons(),
   176                                                     (int)mGamepads[slot].numAxes());
   177 }
   179 void
   180 DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device)
   181 {
   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;
   188     }
   189   }
   190 }
   192 void
   193 DarwinGamepadService::InputValueChanged(IOHIDValueRef value)
   194 {
   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);
   209       }
   210       return;
   211     }
   212   }
   213 }
   215 void
   216 DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
   217                                          void* sender, IOHIDDeviceRef device)
   218 {
   219   DarwinGamepadService* service = (DarwinGamepadService*)data;
   220   service->DeviceAdded(device);
   221 }
   223 void
   224 DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
   225                                            void* sender, IOHIDDeviceRef device)
   226 {
   227   DarwinGamepadService* service = (DarwinGamepadService*)data;
   228   service->DeviceRemoved(device);
   229 }
   231 void
   232 DarwinGamepadService::InputValueChangedCallback(void* data,
   233                                                IOReturn result,
   234                                                void* sender,
   235                                                IOHIDValueRef newValue)
   236 {
   237   DarwinGamepadService* service = (DarwinGamepadService*)data;
   238   service->InputValueChanged(newValue);
   239 }
   241 static CFMutableDictionaryRef
   242 MatchingDictionary(UInt32 inUsagePage, UInt32 inUsage)
   243 {
   244   CFMutableDictionaryRef dict =
   245     CFDictionaryCreateMutable(kCFAllocatorDefault,
   246                               0,
   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;
   257   }
   258   CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
   259   CFRelease(number);
   261   number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
   262   if (!number) {
   263     CFRelease(dict);
   264     return nullptr;
   265   }
   266   CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
   267   CFRelease(number);
   269   return dict;
   270 }
   272 DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {}
   274 DarwinGamepadService::~DarwinGamepadService()
   275 {
   276   if (mManager != nullptr)
   277     CFRelease(mManager);
   278 }
   280 void DarwinGamepadService::Startup()
   281 {
   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;
   294   }
   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;
   302   }
   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;
   311   }
   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;
   334   }
   336   mManager = manager;
   337 }
   339 void DarwinGamepadService::Shutdown()
   340 {
   341   IOHIDManagerRef manager = (IOHIDManagerRef)mManager;
   342   if (manager) {
   343     IOHIDManagerClose(manager, 0);
   344     CFRelease(manager);
   345     mManager = nullptr;
   346   }
   347 }
   349 } // namespace
   351 namespace mozilla {
   352 namespace hal_impl {
   354 DarwinGamepadService* gService = nullptr;
   356 void StartMonitoringGamepadStatus()
   357 {
   358   if (gService)
   359     return;
   361   gService = new DarwinGamepadService();
   362   gService->Startup();
   363 }
   365 void StopMonitoringGamepadStatus()
   366 {
   367   if (!gService)
   368     return;
   370   gService->Shutdown();
   371   delete gService;
   372   gService = nullptr;
   373 }
   375 } // namespace hal_impl
   376 } // namespace mozilla

mercurial