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