tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CocoaGamepad.cpp (22847B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 // mostly derived from the Allegro source code at:
      8 // http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup
      9 
     10 #include <CoreFoundation/CoreFoundation.h>
     11 #include <IOKit/hid/IOHIDBase.h>
     12 #include <IOKit/hid/IOHIDKeys.h>
     13 #include <IOKit/hid/IOHIDManager.h>
     14 #include <stdio.h>
     15 
     16 #include <vector>
     17 
     18 #include "mozilla/Sprintf.h"
     19 #include "mozilla/Tainting.h"
     20 #include "mozilla/dom/GamepadHandle.h"
     21 #include "mozilla/dom/GamepadPlatformService.h"
     22 #include "mozilla/dom/GamepadRemapping.h"
     23 #include "mozilla/ipc/BackgroundParent.h"
     24 #include "nsComponentManagerUtils.h"
     25 #include "nsITimer.h"
     26 #include "nsThreadUtils.h"
     27 
     28 namespace {
     29 
     30 using namespace mozilla;
     31 using namespace mozilla::dom;
     32 class DarwinGamepadService;
     33 
     34 DarwinGamepadService* gService = nullptr;
     35 
     36 struct Button {
     37  int id;
     38  bool analog;
     39  IOHIDElementRef element;
     40  CFIndex min;
     41  CFIndex max;
     42  bool pressed;
     43 
     44  Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax)
     45      : id(aId),
     46        analog((aMax - aMin) > 1),
     47        element(aElement),
     48        min(aMin),
     49        max(aMax),
     50        pressed(false) {}
     51 };
     52 
     53 struct Axis {
     54  int id;
     55  IOHIDElementRef element;
     56  uint32_t usagePage;
     57  uint32_t usage;
     58  CFIndex min;
     59  CFIndex max;
     60  double value;
     61 };
     62 
     63 // These values can be found in the USB HID Usage Tables:
     64 // http://www.usb.org/developers/hidpage
     65 const unsigned kDesktopUsagePage = 0x01;
     66 const unsigned kSimUsagePage = 0x02;
     67 const unsigned kAcceleratorUsage = 0xC4;
     68 const unsigned kBrakeUsage = 0xC5;
     69 const unsigned kJoystickUsage = 0x04;
     70 const unsigned kGamepadUsage = 0x05;
     71 const unsigned kAxisUsageMin = 0x30;
     72 const unsigned kAxisUsageMax = 0x35;
     73 const unsigned kDpadUsage = 0x39;
     74 const unsigned kButtonUsagePage = 0x09;
     75 const unsigned kConsumerPage = 0x0C;
     76 const unsigned kHomeUsage = 0x223;
     77 const unsigned kBackUsage = 0x224;
     78 
     79 // We poll it periodically,
     80 // 50ms is arbitrarily chosen.
     81 const uint32_t kDarwinGamepadPollInterval = 50;
     82 
     83 struct GamepadInputReportContext {
     84  DarwinGamepadService* service;
     85  size_t gamepadSlot;
     86 };
     87 
     88 class Gamepad {
     89 private:
     90  IOHIDDeviceRef mDevice;
     91  nsTArray<Button> buttons;
     92  nsTArray<Axis> axes;
     93 
     94 public:
     95  Gamepad() : mDevice(nullptr) {}
     96 
     97  bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
     98  bool empty() const { return mDevice == nullptr; }
     99  void clear() {
    100    mDevice = nullptr;
    101    buttons.Clear();
    102    axes.Clear();
    103    mHandle = GamepadHandle{};
    104  }
    105  void init(IOHIDDeviceRef device, bool defaultRemapper);
    106  void ReportChanged(uint8_t* report, CFIndex report_length);
    107  size_t WriteOutputReport(const std::vector<uint8_t>& aReport) const;
    108 
    109  size_t numButtons() { return buttons.Length(); }
    110  size_t numAxes() { return axes.Length(); }
    111 
    112  Button* lookupButton(IOHIDElementRef element) {
    113    for (unsigned i = 0; i < buttons.Length(); i++) {
    114      if (buttons[i].element == element) return &buttons[i];
    115    }
    116    return nullptr;
    117  }
    118 
    119  Axis* lookupAxis(IOHIDElementRef element) {
    120    for (unsigned i = 0; i < axes.Length(); i++) {
    121      if (axes[i].element == element) return &axes[i];
    122    }
    123    return nullptr;
    124  }
    125 
    126  GamepadHandle mHandle;
    127  RefPtr<GamepadRemapper> mRemapper;
    128  nsTArray<uint8_t> mInputReport;
    129  UniquePtr<GamepadInputReportContext> mInputReportContext;
    130 };
    131 
    132 void Gamepad::init(IOHIDDeviceRef aDevice, bool aDefaultRemapper) {
    133  clear();
    134  mDevice = aDevice;
    135 
    136  CFArrayRef elements =
    137      IOHIDDeviceCopyMatchingElements(aDevice, nullptr, kIOHIDOptionsTypeNone);
    138  CFIndex n = CFArrayGetCount(elements);
    139  for (CFIndex i = 0; i < n; i++) {
    140    IOHIDElementRef element =
    141        (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
    142    uint32_t usagePage = IOHIDElementGetUsagePage(element);
    143    uint32_t usage = IOHIDElementGetUsage(element);
    144 
    145    if (usagePage == kDesktopUsagePage && usage >= kAxisUsageMin &&
    146        usage <= kAxisUsageMax) {
    147      Axis axis = {aDefaultRemapper ? int(axes.Length())
    148                                    : static_cast<int>(usage - kAxisUsageMin),
    149                   element,
    150                   usagePage,
    151                   usage,
    152                   IOHIDElementGetLogicalMin(element),
    153                   IOHIDElementGetLogicalMax(element)};
    154      axes.AppendElement(axis);
    155    } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage &&
    156               // Don't know how to handle d-pads that return weird values.
    157               IOHIDElementGetLogicalMax(element) -
    158                       IOHIDElementGetLogicalMin(element) ==
    159                   7) {
    160      Axis axis = {aDefaultRemapper ? int(axes.Length())
    161                                    : static_cast<int>(usage - kAxisUsageMin),
    162                   element,
    163                   usagePage,
    164                   usage,
    165                   IOHIDElementGetLogicalMin(element),
    166                   IOHIDElementGetLogicalMax(element)};
    167      axes.AppendElement(axis);
    168    } else if (usagePage == kSimUsagePage &&
    169               (usage == kAcceleratorUsage || usage == kBrakeUsage)) {
    170      if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button) {
    171        Button button(int(buttons.Length()), element,
    172                      IOHIDElementGetLogicalMin(element),
    173                      IOHIDElementGetLogicalMax(element));
    174        buttons.AppendElement(button);
    175      } else {
    176        Axis axis = {aDefaultRemapper ? int(axes.Length())
    177                                      : static_cast<int>(usage - kAxisUsageMin),
    178                     element,
    179                     usagePage,
    180                     usage,
    181                     IOHIDElementGetLogicalMin(element),
    182                     IOHIDElementGetLogicalMax(element)};
    183        axes.AppendElement(axis);
    184      }
    185    } else if ((usagePage == kButtonUsagePage) ||
    186               (usagePage == kConsumerPage &&
    187                (usage == kHomeUsage || usage == kBackUsage))) {
    188      Button button(int(buttons.Length()), element,
    189                    IOHIDElementGetLogicalMin(element),
    190                    IOHIDElementGetLogicalMax(element));
    191      buttons.AppendElement(button);
    192    } else {
    193      // TODO: handle other usage pages
    194    }
    195  }
    196 }
    197 
    198 // This service is created and destroyed in Background thread while
    199 // operates in a seperate thread(We call it Monitor thread here).
    200 class DarwinGamepadService {
    201 private:
    202  IOHIDManagerRef mManager;
    203  nsTArray<Gamepad> mGamepads;
    204 
    205  nsCOMPtr<nsIThread> mMonitorThread;
    206  nsCOMPtr<nsIThread> mBackgroundThread;
    207  nsCOMPtr<nsITimer> mPollingTimer;
    208  bool mIsRunning;
    209 
    210  static void DeviceAddedCallback(void* data, IOReturn result, void* sender,
    211                                  IOHIDDeviceRef device);
    212  static void DeviceRemovedCallback(void* data, IOReturn result, void* sender,
    213                                    IOHIDDeviceRef device);
    214  static void InputValueChangedCallback(void* data, IOReturn result,
    215                                        void* sender, IOHIDValueRef newValue);
    216  static void EventLoopOnceCallback(nsITimer* aTimer, void* aClosure);
    217 
    218  void DeviceAdded(IOHIDDeviceRef device);
    219  void DeviceRemoved(IOHIDDeviceRef device);
    220  void InputValueChanged(IOHIDValueRef value);
    221  void StartupInternal();
    222  void RunEventLoopOnce();
    223 
    224 public:
    225  DarwinGamepadService();
    226  ~DarwinGamepadService();
    227 
    228  static void ReportChangedCallback(void* context, IOReturn result,
    229                                    void* sender, IOHIDReportType report_type,
    230                                    uint32_t report_id, uint8_t* report,
    231                                    CFIndex report_length);
    232 
    233  void Startup();
    234  void Shutdown();
    235  void SetLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
    236                              const Tainted<uint32_t>& aLightColorIndex,
    237                              const uint8_t& aRed, const uint8_t& aGreen,
    238                              const uint8_t& aBlue);
    239  friend class DarwinGamepadServiceStartupRunnable;
    240  friend class DarwinGamepadServiceShutdownRunnable;
    241 };
    242 
    243 class DarwinGamepadServiceStartupRunnable final : public Runnable {
    244 private:
    245  ~DarwinGamepadServiceStartupRunnable() {}
    246  // This Runnable schedules startup of DarwinGamepadService
    247  // in a new thread, pointer to DarwinGamepadService is only
    248  // used by this Runnable within its thread.
    249  DarwinGamepadService MOZ_NON_OWNING_REF* mService;
    250 
    251 public:
    252  explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService* service)
    253      : Runnable("DarwinGamepadServiceStartupRunnable"), mService(service) {}
    254  NS_IMETHOD Run() override {
    255    MOZ_ASSERT(mService);
    256    mService->StartupInternal();
    257    return NS_OK;
    258  }
    259 };
    260 
    261 class DarwinGamepadServiceShutdownRunnable final : public Runnable {
    262 private:
    263  ~DarwinGamepadServiceShutdownRunnable() {}
    264 
    265 public:
    266  // This Runnable schedules shutdown of DarwinGamepadService
    267  // in background thread.
    268  explicit DarwinGamepadServiceShutdownRunnable()
    269      : Runnable("DarwinGamepadServiceStartupRunnable") {}
    270  NS_IMETHOD Run() override {
    271    MOZ_ASSERT(gService);
    272    MOZ_ASSERT(NS_GetCurrentThread() == gService->mBackgroundThread);
    273 
    274    IOHIDManagerRef manager = (IOHIDManagerRef)gService->mManager;
    275 
    276    if (manager) {
    277      IOHIDManagerClose(manager, 0);
    278      CFRelease(manager);
    279      gService->mManager = nullptr;
    280    }
    281    gService->mMonitorThread->Shutdown();
    282    delete gService;
    283    gService = nullptr;
    284    return NS_OK;
    285  }
    286 };
    287 
    288 void DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) {
    289  RefPtr<GamepadPlatformService> service =
    290      GamepadPlatformService::GetParentService();
    291  if (!service) {
    292    return;
    293  }
    294 
    295  size_t slot = size_t(-1);
    296  for (size_t i = 0; i < mGamepads.Length(); i++) {
    297    if (mGamepads[i] == device) return;
    298    if (slot == size_t(-1) && mGamepads[i].empty()) slot = i;
    299  }
    300 
    301  if (slot == size_t(-1)) {
    302    slot = mGamepads.Length();
    303    mGamepads.AppendElement(Gamepad());
    304  }
    305 
    306  // Gather some identifying information
    307  auto uintProperty = [&](CFStringRef key) {
    308    int value;
    309    auto numberRef =
    310        reinterpret_cast<CFNumberRef>(IOHIDDeviceGetProperty(device, key));
    311    if (!numberRef || !CFNumberGetValue(numberRef, kCFNumberIntType, &value)) {
    312      return 0u;
    313    }
    314    return static_cast<unsigned int>(value);
    315  };
    316 
    317  unsigned int vendorId = uintProperty(CFSTR(kIOHIDVendorIDKey));
    318  unsigned int productId = uintProperty(CFSTR(kIOHIDProductIDKey));
    319 
    320  char productName[128];
    321  CFStringRef productRef =
    322      (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
    323  if (!productRef ||
    324      !CFStringGetCString(productRef, productName, sizeof(productName),
    325                          kCFStringEncodingASCII)) {
    326    SprintfLiteral(productName, "Unknown Device");
    327  }
    328 
    329  char buffer[256];
    330  SprintfLiteral(buffer, "%04x-%04x-%s", vendorId, productId, productName);
    331 
    332  bool defaultRemapper = false;
    333  RefPtr<GamepadRemapper> remapper =
    334      GetGamepadRemapper(vendorId, productId, defaultRemapper);
    335  MOZ_ASSERT(remapper);
    336  mGamepads[slot].init(device, defaultRemapper);
    337 
    338  remapper->SetAxisCount(mGamepads[slot].numAxes());
    339  remapper->SetButtonCount(mGamepads[slot].numButtons());
    340 
    341  GamepadHandle handle = service->AddGamepad(
    342      buffer, remapper->GetMappingType(), mozilla::dom::GamepadHand::_empty,
    343      remapper->GetButtonCount(), remapper->GetAxisCount(),
    344      0,  // TODO: Bug 680289, implement gamepad haptics for cocoa.
    345      remapper->GetLightIndicatorCount(), remapper->GetTouchEventCount());
    346 
    347  nsTArray<GamepadLightIndicatorType> lightTypes;
    348  remapper->GetLightIndicators(lightTypes);
    349  for (uint32_t i = 0; i < lightTypes.Length(); ++i) {
    350    if (lightTypes[i] != GamepadLightIndicator::DefaultType()) {
    351      service->NewLightIndicatorTypeEvent(handle, i, lightTypes[i]);
    352    }
    353  }
    354 
    355  mGamepads[slot].mHandle = handle;
    356  mGamepads[slot].mInputReport.SetLength(remapper->GetMaxInputReportLength());
    357  mGamepads[slot].mInputReportContext = UniquePtr<GamepadInputReportContext>(
    358      new GamepadInputReportContext{this, slot});
    359  mGamepads[slot].mRemapper = remapper.forget();
    360 
    361  IOHIDDeviceRegisterInputReportCallback(
    362      device, mGamepads[slot].mInputReport.Elements(),
    363      mGamepads[slot].mInputReport.Length(), ReportChangedCallback,
    364      mGamepads[slot].mInputReportContext.get());
    365 }
    366 
    367 void DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) {
    368  RefPtr<GamepadPlatformService> service =
    369      GamepadPlatformService::GetParentService();
    370  if (!service) {
    371    return;
    372  }
    373  for (Gamepad& gamepad : mGamepads) {
    374    if (gamepad == device) {
    375      IOHIDDeviceRegisterInputReportCallback(
    376          device, gamepad.mInputReport.Elements(), 0, NULL,
    377          gamepad.mInputReportContext.get());
    378 
    379      gamepad.mInputReportContext.reset();
    380 
    381      service->RemoveGamepad(gamepad.mHandle);
    382      gamepad.clear();
    383      return;
    384    }
    385  }
    386 }
    387 
    388 // Replace context to be Gamepad.
    389 void DarwinGamepadService::ReportChangedCallback(
    390    void* context, IOReturn result, void* sender, IOHIDReportType report_type,
    391    uint32_t report_id, uint8_t* report, CFIndex report_length) {
    392  if (context && report_type == kIOHIDReportTypeInput && report_length) {
    393    auto reportContext = static_cast<GamepadInputReportContext*>(context);
    394    DarwinGamepadService* service = reportContext->service;
    395    service->mGamepads[reportContext->gamepadSlot].ReportChanged(report,
    396                                                                 report_length);
    397  }
    398 }
    399 
    400 void Gamepad::ReportChanged(uint8_t* report, CFIndex report_len) {
    401  MOZ_RELEASE_ASSERT(report_len <= mRemapper->GetMaxInputReportLength());
    402  mRemapper->ProcessTouchData(mHandle, report);
    403 }
    404 
    405 size_t Gamepad::WriteOutputReport(const std::vector<uint8_t>& aReport) const {
    406  IOReturn success =
    407      IOHIDDeviceSetReport(mDevice, kIOHIDReportTypeOutput, aReport[0],
    408                           aReport.data(), aReport.size());
    409 
    410  MOZ_ASSERT(success == kIOReturnSuccess);
    411  return (success == kIOReturnSuccess) ? aReport.size() : 0;
    412 }
    413 
    414 void DarwinGamepadService::InputValueChanged(IOHIDValueRef value) {
    415  RefPtr<GamepadPlatformService> service =
    416      GamepadPlatformService::GetParentService();
    417  if (!service) {
    418    return;
    419  }
    420 
    421  uint32_t value_length = IOHIDValueGetLength(value);
    422  if (value_length > 4) {
    423    // Workaround for bizarre issue with PS3 controllers that try to return
    424    // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
    425    return;
    426  }
    427  IOHIDElementRef element = IOHIDValueGetElement(value);
    428  IOHIDDeviceRef device = IOHIDElementGetDevice(element);
    429 
    430  for (Gamepad& gamepad : mGamepads) {
    431    if (gamepad == device) {
    432      // Axis elements represent axes and d-pads.
    433      if (Axis* axis = gamepad.lookupAxis(element)) {
    434        const double d = IOHIDValueGetIntegerValue(value);
    435        const double v =
    436            2.0f * (d - axis->min) / (double)(axis->max - axis->min) - 1.0f;
    437        if (axis->value != v) {
    438          gamepad.mRemapper->RemapAxisMoveEvent(gamepad.mHandle, axis->id, v);
    439          axis->value = v;
    440        }
    441      } else if (Button* button = gamepad.lookupButton(element)) {
    442        const int iv = IOHIDValueGetIntegerValue(value);
    443        const bool pressed = iv != 0;
    444        if (button->pressed != pressed) {
    445          gamepad.mRemapper->RemapButtonEvent(gamepad.mHandle, button->id,
    446                                              pressed);
    447          button->pressed = pressed;
    448        }
    449      }
    450      return;
    451    }
    452  }
    453 }
    454 
    455 void DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
    456                                               void* sender,
    457                                               IOHIDDeviceRef device) {
    458  DarwinGamepadService* service = (DarwinGamepadService*)data;
    459  service->DeviceAdded(device);
    460 }
    461 
    462 void DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
    463                                                 void* sender,
    464                                                 IOHIDDeviceRef device) {
    465  DarwinGamepadService* service = (DarwinGamepadService*)data;
    466  service->DeviceRemoved(device);
    467 }
    468 
    469 void DarwinGamepadService::InputValueChangedCallback(void* data,
    470                                                     IOReturn result,
    471                                                     void* sender,
    472                                                     IOHIDValueRef newValue) {
    473  DarwinGamepadService* service = (DarwinGamepadService*)data;
    474  service->InputValueChanged(newValue);
    475 }
    476 
    477 void DarwinGamepadService::EventLoopOnceCallback(nsITimer* aTimer,
    478                                                 void* aClosure) {
    479  DarwinGamepadService* service = static_cast<DarwinGamepadService*>(aClosure);
    480  service->RunEventLoopOnce();
    481 }
    482 
    483 static CFMutableDictionaryRef MatchingDictionary(UInt32 inUsagePage,
    484                                                 UInt32 inUsage) {
    485  CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
    486      kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
    487      &kCFTypeDictionaryValueCallBacks);
    488  if (!dict) return nullptr;
    489  CFNumberRef number =
    490      CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage);
    491  if (!number) {
    492    CFRelease(dict);
    493    return nullptr;
    494  }
    495  CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
    496  CFRelease(number);
    497 
    498  number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
    499  if (!number) {
    500    CFRelease(dict);
    501    return nullptr;
    502  }
    503  CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
    504  CFRelease(number);
    505 
    506  return dict;
    507 }
    508 
    509 DarwinGamepadService::DarwinGamepadService()
    510    : mManager(nullptr), mIsRunning(false) {}
    511 
    512 DarwinGamepadService::~DarwinGamepadService() {
    513  if (mManager != nullptr) CFRelease(mManager);
    514  mMonitorThread = nullptr;
    515  mBackgroundThread = nullptr;
    516  if (mPollingTimer) {
    517    mPollingTimer->Cancel();
    518    mPollingTimer = nullptr;
    519  }
    520 }
    521 
    522 void DarwinGamepadService::RunEventLoopOnce() {
    523  MOZ_ASSERT(NS_GetCurrentThread() == mMonitorThread);
    524  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
    525 
    526  // This timer must be created in monitor thread
    527  if (!mPollingTimer) {
    528    mPollingTimer = NS_NewTimer();
    529  }
    530  mPollingTimer->Cancel();
    531  if (mIsRunning) {
    532    mPollingTimer->InitWithNamedFuncCallback(
    533        EventLoopOnceCallback, this, kDarwinGamepadPollInterval,
    534        nsITimer::TYPE_ONE_SHOT, "EventLoopOnceCallback"_ns);
    535  } else {
    536    // We schedule a task shutdown and cleaning up resources to Background
    537    // thread here to make sure no runloop is running to prevent potential race
    538    // condition.
    539    RefPtr<Runnable> shutdownTask = new DarwinGamepadServiceShutdownRunnable();
    540    mBackgroundThread->Dispatch(shutdownTask.forget(), NS_DISPATCH_NORMAL);
    541  }
    542 }
    543 
    544 void DarwinGamepadService::StartupInternal() {
    545  if (mManager != nullptr) return;
    546 
    547  IOHIDManagerRef manager =
    548      IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
    549 
    550  CFMutableDictionaryRef criteria_arr[2];
    551  criteria_arr[0] = MatchingDictionary(kDesktopUsagePage, kJoystickUsage);
    552  if (!criteria_arr[0]) {
    553    CFRelease(manager);
    554    return;
    555  }
    556 
    557  criteria_arr[1] = MatchingDictionary(kDesktopUsagePage, kGamepadUsage);
    558  if (!criteria_arr[1]) {
    559    CFRelease(criteria_arr[0]);
    560    CFRelease(manager);
    561    return;
    562  }
    563 
    564  CFArrayRef criteria = CFArrayCreate(kCFAllocatorDefault,
    565                                      (const void**)criteria_arr, 2, nullptr);
    566  if (!criteria) {
    567    CFRelease(criteria_arr[1]);
    568    CFRelease(criteria_arr[0]);
    569    CFRelease(manager);
    570    return;
    571  }
    572 
    573  IOHIDManagerSetDeviceMatchingMultiple(manager, criteria);
    574  CFRelease(criteria);
    575  CFRelease(criteria_arr[1]);
    576  CFRelease(criteria_arr[0]);
    577 
    578  IOHIDManagerRegisterDeviceMatchingCallback(manager, DeviceAddedCallback,
    579                                             this);
    580  IOHIDManagerRegisterDeviceRemovalCallback(manager, DeviceRemovedCallback,
    581                                            this);
    582  IOHIDManagerRegisterInputValueCallback(manager, InputValueChangedCallback,
    583                                         this);
    584  IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(),
    585                                  kCFRunLoopDefaultMode);
    586  IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
    587  if (rv != kIOReturnSuccess) {
    588    CFRelease(manager);
    589    return;
    590  }
    591 
    592  mManager = manager;
    593 
    594  mIsRunning = true;
    595  RunEventLoopOnce();
    596 }
    597 
    598 void DarwinGamepadService::Startup() {
    599  mBackgroundThread = NS_GetCurrentThread();
    600  (void)NS_NewNamedThread("Gamepad", getter_AddRefs(mMonitorThread),
    601                          new DarwinGamepadServiceStartupRunnable(this));
    602 }
    603 
    604 void DarwinGamepadService::Shutdown() {
    605  // Flipping this flag will stop the eventloop in Monitor thread
    606  // and dispatch a task destroying and cleaning up resources in
    607  // Background thread
    608  mIsRunning = false;
    609 }
    610 
    611 void DarwinGamepadService::SetLightIndicatorColor(
    612    const Tainted<GamepadHandle>& aGamepadHandle,
    613    const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
    614    const uint8_t& aGreen, const uint8_t& aBlue) {
    615  // We get aControllerIdx from GamepadPlatformService::AddGamepad(),
    616  // It begins from 1 and is stored at Gamepad.id.
    617  const Gamepad* gamepad = MOZ_FIND_AND_VALIDATE(
    618      aGamepadHandle, list_item.mHandle == aGamepadHandle, mGamepads);
    619  if (!gamepad) {
    620    MOZ_ASSERT(false);
    621    return;
    622  }
    623 
    624  RefPtr<GamepadRemapper> remapper = gamepad->mRemapper;
    625  if (!remapper ||
    626      MOZ_IS_VALID(aLightColorIndex,
    627                   remapper->GetLightIndicatorCount() <= aLightColorIndex)) {
    628    MOZ_ASSERT(false);
    629    return;
    630  }
    631 
    632  std::vector<uint8_t> report;
    633  remapper->GetLightColorReport(aRed, aGreen, aBlue, report);
    634  gamepad->WriteOutputReport(report);
    635 }
    636 
    637 }  // namespace
    638 
    639 namespace mozilla {
    640 namespace dom {
    641 
    642 void StartGamepadMonitoring() {
    643  ::mozilla::ipc::AssertIsOnBackgroundThread();
    644  if (gService) {
    645    return;
    646  }
    647 
    648  gService = new DarwinGamepadService();
    649  gService->Startup();
    650 }
    651 
    652 void StopGamepadMonitoring() {
    653  ::mozilla::ipc::AssertIsOnBackgroundThread();
    654  if (!gService) {
    655    return;
    656  }
    657 
    658  // Calling Shutdown() will delete gService as well
    659  gService->Shutdown();
    660 }
    661 
    662 void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
    663                                   const Tainted<uint32_t>& aLightColorIndex,
    664                                   const uint8_t& aRed, const uint8_t& aGreen,
    665                                   const uint8_t& aBlue) {
    666  MOZ_ASSERT(gService);
    667  if (!gService) {
    668    return;
    669  }
    670  gService->SetLightIndicatorColor(aGamepadHandle, aLightColorIndex, aRed,
    671                                   aGreen, aBlue);
    672 }
    673 
    674 }  // namespace dom
    675 }  // namespace mozilla