hal/cocoa/CocoaGamepad.cpp

changeset 0
6474c204b198
     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

mercurial