summaryrefslogtreecommitdiffstats
path: root/dom/gamepad/cocoa
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/gamepad/cocoa
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/gamepad/cocoa')
-rw-r--r--dom/gamepad/cocoa/CocoaGamepad.cpp665
1 files changed, 665 insertions, 0 deletions
diff --git a/dom/gamepad/cocoa/CocoaGamepad.cpp b/dom/gamepad/cocoa/CocoaGamepad.cpp
new file mode 100644
index 0000000000..544c2e42b7
--- /dev/null
+++ b/dom/gamepad/cocoa/CocoaGamepad.cpp
@@ -0,0 +1,665 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// mostly derived from the Allegro source code at:
+// http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup
+
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/dom/GamepadRemapping.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Tainting.h"
+#include "nsComponentManagerUtils.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDBase.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+#include <stdio.h>
+#include <vector>
+
+namespace {
+
+using namespace mozilla;
+using namespace mozilla::dom;
+class DarwinGamepadService;
+
+DarwinGamepadService* gService = nullptr;
+
+struct Button {
+ int id;
+ bool analog;
+ IOHIDElementRef element;
+ CFIndex min;
+ CFIndex max;
+ bool pressed;
+
+ Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax)
+ : id(aId),
+ analog((aMax - aMin) > 1),
+ element(aElement),
+ min(aMin),
+ max(aMax),
+ pressed(false) {}
+};
+
+struct Axis {
+ int id;
+ IOHIDElementRef element;
+ uint32_t usagePage;
+ uint32_t usage;
+ CFIndex min;
+ CFIndex max;
+ double value;
+};
+
+// These values can be found in the USB HID Usage Tables:
+// http://www.usb.org/developers/hidpage
+const unsigned kDesktopUsagePage = 0x01;
+const unsigned kSimUsagePage = 0x02;
+const unsigned kAcceleratorUsage = 0xC4;
+const unsigned kBrakeUsage = 0xC5;
+const unsigned kJoystickUsage = 0x04;
+const unsigned kGamepadUsage = 0x05;
+const unsigned kAxisUsageMin = 0x30;
+const unsigned kAxisUsageMax = 0x35;
+const unsigned kDpadUsage = 0x39;
+const unsigned kButtonUsagePage = 0x09;
+const unsigned kConsumerPage = 0x0C;
+const unsigned kHomeUsage = 0x223;
+const unsigned kBackUsage = 0x224;
+
+// We poll it periodically,
+// 50ms is arbitrarily chosen.
+const uint32_t kDarwinGamepadPollInterval = 50;
+
+struct GamepadInputReportContext {
+ DarwinGamepadService* service;
+ size_t gamepadSlot;
+};
+
+class Gamepad {
+ private:
+ IOHIDDeviceRef mDevice;
+ nsTArray<Button> buttons;
+ nsTArray<Axis> axes;
+
+ public:
+ Gamepad() : mDevice(nullptr) {}
+
+ bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
+ bool empty() const { return mDevice == nullptr; }
+ void clear() {
+ mDevice = nullptr;
+ buttons.Clear();
+ axes.Clear();
+ mHandle = GamepadHandle{};
+ }
+ void init(IOHIDDeviceRef device, bool defaultRemapper);
+ void ReportChanged(uint8_t* report, CFIndex report_length);
+ size_t WriteOutputReport(const std::vector<uint8_t>& aReport) const;
+
+ size_t numButtons() { return buttons.Length(); }
+ size_t numAxes() { return axes.Length(); }
+
+ Button* lookupButton(IOHIDElementRef element) {
+ for (unsigned i = 0; i < buttons.Length(); i++) {
+ if (buttons[i].element == element) return &buttons[i];
+ }
+ return nullptr;
+ }
+
+ Axis* lookupAxis(IOHIDElementRef element) {
+ for (unsigned i = 0; i < axes.Length(); i++) {
+ if (axes[i].element == element) return &axes[i];
+ }
+ return nullptr;
+ }
+
+ GamepadHandle mHandle;
+ RefPtr<GamepadRemapper> mRemapper;
+ nsTArray<uint8_t> mInputReport;
+ UniquePtr<GamepadInputReportContext> mInputReportContext;
+};
+
+void Gamepad::init(IOHIDDeviceRef aDevice, bool aDefaultRemapper) {
+ clear();
+ mDevice = aDevice;
+
+ CFArrayRef elements =
+ IOHIDDeviceCopyMatchingElements(aDevice, nullptr, kIOHIDOptionsTypeNone);
+ CFIndex n = CFArrayGetCount(elements);
+ for (CFIndex i = 0; i < n; i++) {
+ IOHIDElementRef element =
+ (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
+ uint32_t usagePage = IOHIDElementGetUsagePage(element);
+ uint32_t usage = IOHIDElementGetUsage(element);
+
+ if (usagePage == kDesktopUsagePage && usage >= kAxisUsageMin &&
+ usage <= kAxisUsageMax) {
+ Axis axis = {aDefaultRemapper ? int(axes.Length())
+ : static_cast<int>(usage - kAxisUsageMin),
+ element,
+ usagePage,
+ usage,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element)};
+ axes.AppendElement(axis);
+ } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage &&
+ // Don't know how to handle d-pads that return weird values.
+ IOHIDElementGetLogicalMax(element) -
+ IOHIDElementGetLogicalMin(element) ==
+ 7) {
+ Axis axis = {aDefaultRemapper ? int(axes.Length())
+ : static_cast<int>(usage - kAxisUsageMin),
+ element,
+ usagePage,
+ usage,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element)};
+ axes.AppendElement(axis);
+ } else if (usagePage == kSimUsagePage &&
+ (usage == kAcceleratorUsage || usage == kBrakeUsage)) {
+ if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button) {
+ Button button(int(buttons.Length()), element,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element));
+ buttons.AppendElement(button);
+ } else {
+ Axis axis = {aDefaultRemapper ? int(axes.Length())
+ : static_cast<int>(usage - kAxisUsageMin),
+ element,
+ usagePage,
+ usage,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element)};
+ axes.AppendElement(axis);
+ }
+ } else if ((usagePage == kButtonUsagePage) ||
+ (usagePage == kConsumerPage &&
+ (usage == kHomeUsage || usage == kBackUsage))) {
+ Button button(int(buttons.Length()), element,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element));
+ buttons.AppendElement(button);
+ } else {
+ // TODO: handle other usage pages
+ }
+ }
+}
+
+// This service is created and destroyed in Background thread while
+// operates in a seperate thread(We call it Monitor thread here).
+class DarwinGamepadService {
+ private:
+ IOHIDManagerRef mManager;
+ nsTArray<Gamepad> mGamepads;
+
+ nsCOMPtr<nsIThread> mMonitorThread;
+ nsCOMPtr<nsIThread> mBackgroundThread;
+ nsCOMPtr<nsITimer> mPollingTimer;
+ bool mIsRunning;
+
+ static void DeviceAddedCallback(void* data, IOReturn result, void* sender,
+ IOHIDDeviceRef device);
+ static void DeviceRemovedCallback(void* data, IOReturn result, void* sender,
+ IOHIDDeviceRef device);
+ static void InputValueChangedCallback(void* data, IOReturn result,
+ void* sender, IOHIDValueRef newValue);
+ static void EventLoopOnceCallback(nsITimer* aTimer, void* aClosure);
+
+ void DeviceAdded(IOHIDDeviceRef device);
+ void DeviceRemoved(IOHIDDeviceRef device);
+ void InputValueChanged(IOHIDValueRef value);
+ void StartupInternal();
+ void RunEventLoopOnce();
+
+ public:
+ DarwinGamepadService();
+ ~DarwinGamepadService();
+
+ static void ReportChangedCallback(void* context, IOReturn result,
+ void* sender, IOHIDReportType report_type,
+ uint32_t report_id, uint8_t* report,
+ CFIndex report_length);
+
+ void Startup();
+ void Shutdown();
+ void SetLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue);
+ friend class DarwinGamepadServiceStartupRunnable;
+ friend class DarwinGamepadServiceShutdownRunnable;
+};
+
+class DarwinGamepadServiceStartupRunnable final : public Runnable {
+ private:
+ ~DarwinGamepadServiceStartupRunnable() {}
+ // This Runnable schedules startup of DarwinGamepadService
+ // in a new thread, pointer to DarwinGamepadService is only
+ // used by this Runnable within its thread.
+ DarwinGamepadService MOZ_NON_OWNING_REF* mService;
+
+ public:
+ explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService* service)
+ : Runnable("DarwinGamepadServiceStartupRunnable"), mService(service) {}
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mService);
+ mService->StartupInternal();
+ return NS_OK;
+ }
+};
+
+class DarwinGamepadServiceShutdownRunnable final : public Runnable {
+ private:
+ ~DarwinGamepadServiceShutdownRunnable() {}
+
+ public:
+ // This Runnable schedules shutdown of DarwinGamepadService
+ // in background thread.
+ explicit DarwinGamepadServiceShutdownRunnable()
+ : Runnable("DarwinGamepadServiceStartupRunnable") {}
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(gService);
+ MOZ_ASSERT(NS_GetCurrentThread() == gService->mBackgroundThread);
+
+ IOHIDManagerRef manager = (IOHIDManagerRef)gService->mManager;
+
+ if (manager) {
+ IOHIDManagerClose(manager, 0);
+ CFRelease(manager);
+ gService->mManager = nullptr;
+ }
+ gService->mMonitorThread->Shutdown();
+ delete gService;
+ gService = nullptr;
+ return NS_OK;
+ }
+};
+
+void DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ size_t slot = size_t(-1);
+ for (size_t i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i] == device) return;
+ if (slot == size_t(-1) && mGamepads[i].empty()) slot = i;
+ }
+
+ if (slot == size_t(-1)) {
+ slot = mGamepads.Length();
+ mGamepads.AppendElement(Gamepad());
+ }
+
+ // Gather some identifying information
+ CFNumberRef vendorIdRef =
+ (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
+ CFNumberRef productIdRef =
+ (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
+ CFStringRef productRef =
+ (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
+ int vendorId, productId;
+ CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
+ CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
+ char product_name[128];
+ CFStringGetCString(productRef, product_name, sizeof(product_name),
+ kCFStringEncodingASCII);
+ char buffer[256];
+ SprintfLiteral(buffer, "%x-%x-%s", vendorId, productId, product_name);
+
+ bool defaultRemapper = false;
+ RefPtr<GamepadRemapper> remapper =
+ GetGamepadRemapper(vendorId, productId, defaultRemapper);
+ MOZ_ASSERT(remapper);
+ mGamepads[slot].init(device, defaultRemapper);
+
+ remapper->SetAxisCount(mGamepads[slot].numAxes());
+ remapper->SetButtonCount(mGamepads[slot].numButtons());
+
+ GamepadHandle handle = service->AddGamepad(
+ buffer, remapper->GetMappingType(), mozilla::dom::GamepadHand::_empty,
+ remapper->GetButtonCount(), remapper->GetAxisCount(),
+ 0, // TODO: Bug 680289, implement gamepad haptics for cocoa.
+ remapper->GetLightIndicatorCount(), remapper->GetTouchEventCount());
+
+ nsTArray<GamepadLightIndicatorType> lightTypes;
+ remapper->GetLightIndicators(lightTypes);
+ for (uint32_t i = 0; i < lightTypes.Length(); ++i) {
+ if (lightTypes[i] != GamepadLightIndicator::DefaultType()) {
+ service->NewLightIndicatorTypeEvent(handle, i, lightTypes[i]);
+ }
+ }
+
+ mGamepads[slot].mHandle = handle;
+ mGamepads[slot].mInputReport.SetLength(remapper->GetMaxInputReportLength());
+ mGamepads[slot].mInputReportContext = UniquePtr<GamepadInputReportContext>(
+ new GamepadInputReportContext{this, slot});
+ mGamepads[slot].mRemapper = remapper.forget();
+
+ IOHIDDeviceRegisterInputReportCallback(
+ device, mGamepads[slot].mInputReport.Elements(),
+ mGamepads[slot].mInputReport.Length(), ReportChangedCallback,
+ mGamepads[slot].mInputReportContext.get());
+}
+
+void DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+ for (Gamepad& gamepad : mGamepads) {
+ if (gamepad == device) {
+ IOHIDDeviceRegisterInputReportCallback(
+ device, gamepad.mInputReport.Elements(), 0, NULL,
+ gamepad.mInputReportContext.get());
+
+ gamepad.mInputReportContext.reset();
+
+ service->RemoveGamepad(gamepad.mHandle);
+ gamepad.clear();
+ return;
+ }
+ }
+}
+
+// Replace context to be Gamepad.
+void DarwinGamepadService::ReportChangedCallback(
+ void* context, IOReturn result, void* sender, IOHIDReportType report_type,
+ uint32_t report_id, uint8_t* report, CFIndex report_length) {
+ if (context && report_type == kIOHIDReportTypeInput && report_length) {
+ auto reportContext = static_cast<GamepadInputReportContext*>(context);
+ DarwinGamepadService* service = reportContext->service;
+ service->mGamepads[reportContext->gamepadSlot].ReportChanged(report,
+ report_length);
+ }
+}
+
+void Gamepad::ReportChanged(uint8_t* report, CFIndex report_len) {
+ MOZ_RELEASE_ASSERT(report_len <= mRemapper->GetMaxInputReportLength());
+ mRemapper->ProcessTouchData(mHandle, report);
+}
+
+size_t Gamepad::WriteOutputReport(const std::vector<uint8_t>& aReport) const {
+ IOReturn success =
+ IOHIDDeviceSetReport(mDevice, kIOHIDReportTypeOutput, aReport[0],
+ aReport.data(), aReport.size());
+
+ MOZ_ASSERT(success == kIOReturnSuccess);
+ return (success == kIOReturnSuccess) ? aReport.size() : 0;
+}
+
+void DarwinGamepadService::InputValueChanged(IOHIDValueRef value) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ uint32_t value_length = IOHIDValueGetLength(value);
+ if (value_length > 4) {
+ // Workaround for bizarre issue with PS3 controllers that try to return
+ // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
+ return;
+ }
+ IOHIDElementRef element = IOHIDValueGetElement(value);
+ IOHIDDeviceRef device = IOHIDElementGetDevice(element);
+
+ for (Gamepad& gamepad : mGamepads) {
+ if (gamepad == device) {
+ // Axis elements represent axes and d-pads.
+ if (Axis* axis = gamepad.lookupAxis(element)) {
+ const double d = IOHIDValueGetIntegerValue(value);
+ const double v =
+ 2.0f * (d - axis->min) / (double)(axis->max - axis->min) - 1.0f;
+ if (axis->value != v) {
+ gamepad.mRemapper->RemapAxisMoveEvent(gamepad.mHandle, axis->id, v);
+ axis->value = v;
+ }
+ } else if (Button* button = gamepad.lookupButton(element)) {
+ const int iv = IOHIDValueGetIntegerValue(value);
+ const bool pressed = iv != 0;
+ if (button->pressed != pressed) {
+ gamepad.mRemapper->RemapButtonEvent(gamepad.mHandle, button->id,
+ pressed);
+ button->pressed = pressed;
+ }
+ }
+ return;
+ }
+ }
+}
+
+void DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
+ void* sender,
+ IOHIDDeviceRef device) {
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->DeviceAdded(device);
+}
+
+void DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
+ void* sender,
+ IOHIDDeviceRef device) {
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->DeviceRemoved(device);
+}
+
+void DarwinGamepadService::InputValueChangedCallback(void* data,
+ IOReturn result,
+ void* sender,
+ IOHIDValueRef newValue) {
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->InputValueChanged(newValue);
+}
+
+void DarwinGamepadService::EventLoopOnceCallback(nsITimer* aTimer,
+ void* aClosure) {
+ DarwinGamepadService* service = static_cast<DarwinGamepadService*>(aClosure);
+ service->RunEventLoopOnce();
+}
+
+static CFMutableDictionaryRef MatchingDictionary(UInt32 inUsagePage,
+ UInt32 inUsage) {
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (!dict) return nullptr;
+ CFNumberRef number =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage);
+ if (!number) {
+ CFRelease(dict);
+ return nullptr;
+ }
+ CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
+ CFRelease(number);
+
+ number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
+ if (!number) {
+ CFRelease(dict);
+ return nullptr;
+ }
+ CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
+ CFRelease(number);
+
+ return dict;
+}
+
+DarwinGamepadService::DarwinGamepadService()
+ : mManager(nullptr), mIsRunning(false) {}
+
+DarwinGamepadService::~DarwinGamepadService() {
+ if (mManager != nullptr) CFRelease(mManager);
+ mMonitorThread = nullptr;
+ mBackgroundThread = nullptr;
+ if (mPollingTimer) {
+ mPollingTimer->Cancel();
+ mPollingTimer = nullptr;
+ }
+}
+
+void DarwinGamepadService::RunEventLoopOnce() {
+ MOZ_ASSERT(NS_GetCurrentThread() == mMonitorThread);
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
+
+ // This timer must be created in monitor thread
+ if (!mPollingTimer) {
+ mPollingTimer = NS_NewTimer();
+ }
+ mPollingTimer->Cancel();
+ if (mIsRunning) {
+ mPollingTimer->InitWithNamedFuncCallback(
+ EventLoopOnceCallback, this, kDarwinGamepadPollInterval,
+ nsITimer::TYPE_ONE_SHOT, "EventLoopOnceCallback");
+ } else {
+ // We schedule a task shutdown and cleaning up resources to Background
+ // thread here to make sure no runloop is running to prevent potential race
+ // condition.
+ RefPtr<Runnable> shutdownTask = new DarwinGamepadServiceShutdownRunnable();
+ mBackgroundThread->Dispatch(shutdownTask.forget(), NS_DISPATCH_NORMAL);
+ }
+}
+
+void DarwinGamepadService::StartupInternal() {
+ if (mManager != nullptr) return;
+
+ IOHIDManagerRef manager =
+ IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+
+ CFMutableDictionaryRef criteria_arr[2];
+ criteria_arr[0] = MatchingDictionary(kDesktopUsagePage, kJoystickUsage);
+ if (!criteria_arr[0]) {
+ CFRelease(manager);
+ return;
+ }
+
+ criteria_arr[1] = MatchingDictionary(kDesktopUsagePage, kGamepadUsage);
+ if (!criteria_arr[1]) {
+ CFRelease(criteria_arr[0]);
+ CFRelease(manager);
+ return;
+ }
+
+ CFArrayRef criteria = CFArrayCreate(kCFAllocatorDefault,
+ (const void**)criteria_arr, 2, nullptr);
+ if (!criteria) {
+ CFRelease(criteria_arr[1]);
+ CFRelease(criteria_arr[0]);
+ CFRelease(manager);
+ return;
+ }
+
+ IOHIDManagerSetDeviceMatchingMultiple(manager, criteria);
+ CFRelease(criteria);
+ CFRelease(criteria_arr[1]);
+ CFRelease(criteria_arr[0]);
+
+ IOHIDManagerRegisterDeviceMatchingCallback(manager, DeviceAddedCallback,
+ this);
+ IOHIDManagerRegisterDeviceRemovalCallback(manager, DeviceRemovedCallback,
+ this);
+ IOHIDManagerRegisterInputValueCallback(manager, InputValueChangedCallback,
+ this);
+ IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+ IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
+ if (rv != kIOReturnSuccess) {
+ CFRelease(manager);
+ return;
+ }
+
+ mManager = manager;
+
+ mIsRunning = true;
+ RunEventLoopOnce();
+}
+
+void DarwinGamepadService::Startup() {
+ mBackgroundThread = NS_GetCurrentThread();
+ Unused << NS_NewNamedThread("Gamepad", getter_AddRefs(mMonitorThread),
+ new DarwinGamepadServiceStartupRunnable(this));
+}
+
+void DarwinGamepadService::Shutdown() {
+ // Flipping this flag will stop the eventloop in Monitor thread
+ // and dispatch a task destroying and cleaning up resources in
+ // Background thread
+ mIsRunning = false;
+}
+
+void DarwinGamepadService::SetLightIndicatorColor(
+ const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
+ const uint8_t& aGreen, const uint8_t& aBlue) {
+ // We get aControllerIdx from GamepadPlatformService::AddGamepad(),
+ // It begins from 1 and is stored at Gamepad.id.
+ const Gamepad* gamepad = MOZ_FIND_AND_VALIDATE(
+ aGamepadHandle, list_item.mHandle == aGamepadHandle, mGamepads);
+ if (!gamepad) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ RefPtr<GamepadRemapper> remapper = gamepad->mRemapper;
+ if (!remapper ||
+ MOZ_IS_VALID(aLightColorIndex,
+ remapper->GetLightIndicatorCount() <= aLightColorIndex)) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ std::vector<uint8_t> report;
+ remapper->GetLightColorReport(aRed, aGreen, aBlue, report);
+ gamepad->WriteOutputReport(report);
+}
+
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+void StartGamepadMonitoring() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ if (gService) {
+ return;
+ }
+
+ gService = new DarwinGamepadService();
+ gService->Startup();
+}
+
+void StopGamepadMonitoring() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ if (!gService) {
+ return;
+ }
+
+ // Calling Shutdown() will delete gService as well
+ gService->Shutdown();
+}
+
+void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue) {
+ MOZ_ASSERT(gService);
+ if (!gService) {
+ return;
+ }
+ gService->SetLightIndicatorColor(aGamepadHandle, aLightColorIndex, aRed,
+ aGreen, aBlue);
+}
+
+} // namespace dom
+} // namespace mozilla