tor-browser

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

WindowsGamepad.cpp (36665B)


      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 #include <algorithm>
      8 #include <cstddef>
      9 
     10 #ifndef UNICODE
     11 #  define UNICODE
     12 #endif
     13 // clang-format off
     14 #include <windows.h>
     15 #include <hidsdi.h>
     16 #include <stdio.h>
     17 #include <xinput.h>
     18 // clang-format on
     19 
     20 #include "Gamepad.h"
     21 #include "mozilla/dom/GamepadPlatformService.h"
     22 #include "mozilla/dom/GamepadRemapping.h"
     23 #include "mozilla/ipc/BackgroundParent.h"
     24 #include "nsITimer.h"
     25 #include "nsTArray.h"
     26 #include "nsThreadUtils.h"
     27 #include "nsWindowsHelpers.h"
     28 
     29 namespace {
     30 
     31 using namespace mozilla;
     32 using namespace mozilla::dom;
     33 
     34 // USB HID usage tables, page 1, 0x30 = X
     35 const uint32_t kAxisMinimumUsageNumber = 0x30;
     36 // USB HID usage tables, page 1 (Hat switch)
     37 const uint32_t kAxesLengthCap = 16;
     38 
     39 // USB HID usage tables
     40 const uint32_t kDesktopUsagePage = 0x1;
     41 const uint32_t kButtonUsagePage = 0x9;
     42 
     43 // Multiple devices-changed notifications can be sent when a device
     44 // is connected, because USB devices consist of multiple logical devices.
     45 // Therefore, we wait a bit after receiving one before looking for
     46 // device changes.
     47 const uint32_t kDevicesChangedStableDelay = 200;
     48 // Both DirectInput and XInput are polling-driven here,
     49 // so we need to poll it periodically.
     50 // 4ms, or 250 Hz, is consistent with Chrome's gamepad implementation.
     51 const uint32_t kWindowsGamepadPollInterval = 4;
     52 
     53 const UINT kRawInputError = (UINT)-1;
     54 
     55 // In XInputGetState, we can't get the state of Xbox Guide button.
     56 // We need to go through the undocumented XInputGetStateEx method
     57 // to get that button's state.
     58 const LPCSTR kXInputGetStateExOrdinal = (LPCSTR)100;
     59 // Bitmask for the Guide button in XInputGamepadEx.wButtons.
     60 const int XINPUT_GAMEPAD_Guide = 0x0400;
     61 
     62 #ifndef XUSER_MAX_COUNT
     63 #  define XUSER_MAX_COUNT 4
     64 #endif
     65 
     66 const struct {
     67  int usagePage;
     68  int usage;
     69 } kUsagePages[] = {
     70    // USB HID usage tables, page 1
     71    {kDesktopUsagePage, 4},  // Joystick
     72    {kDesktopUsagePage, 5}   // Gamepad
     73 };
     74 
     75 const struct {
     76  WORD button;
     77  int mapped;
     78 } kXIButtonMap[] = {{XINPUT_GAMEPAD_DPAD_UP, 12},
     79                    {XINPUT_GAMEPAD_DPAD_DOWN, 13},
     80                    {XINPUT_GAMEPAD_DPAD_LEFT, 14},
     81                    {XINPUT_GAMEPAD_DPAD_RIGHT, 15},
     82                    {XINPUT_GAMEPAD_START, 9},
     83                    {XINPUT_GAMEPAD_BACK, 8},
     84                    {XINPUT_GAMEPAD_LEFT_THUMB, 10},
     85                    {XINPUT_GAMEPAD_RIGHT_THUMB, 11},
     86                    {XINPUT_GAMEPAD_LEFT_SHOULDER, 4},
     87                    {XINPUT_GAMEPAD_RIGHT_SHOULDER, 5},
     88                    {XINPUT_GAMEPAD_Guide, 16},
     89                    {XINPUT_GAMEPAD_A, 0},
     90                    {XINPUT_GAMEPAD_B, 1},
     91                    {XINPUT_GAMEPAD_X, 2},
     92                    {XINPUT_GAMEPAD_Y, 3}};
     93 const size_t kNumMappings = std::size(kXIButtonMap);
     94 
     95 enum GamepadType { kNoGamepad = 0, kRawInputGamepad, kXInputGamepad };
     96 
     97 class WindowsGamepadService;
     98 // This pointer holds a windows gamepad backend service,
     99 // it will be created and destroyed by background thread and
    100 // used by gMonitorThread
    101 WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr;
    102 MOZ_RUNINIT nsCOMPtr<nsIThread> gMonitorThread = nullptr;
    103 static bool sIsShutdown = false;
    104 
    105 class Gamepad {
    106 public:
    107  GamepadType type;
    108 
    109  // Handle to raw input device
    110  HANDLE handle;
    111 
    112  // XInput Index of the user's controller. Passed to XInputGetState.
    113  DWORD userIndex;
    114 
    115  // Last-known state of the controller.
    116  XINPUT_STATE state;
    117 
    118  // Handle from the GamepadService
    119  GamepadHandle gamepadHandle;
    120 
    121  // Information about the physical device.
    122  unsigned numAxes;
    123  unsigned numButtons;
    124 
    125  nsTArray<bool> buttons;
    126  struct axisValue {
    127    HIDP_VALUE_CAPS caps;
    128    double value;
    129    bool active;
    130 
    131    axisValue() : value(0.0f), active(false) {}
    132    explicit axisValue(const HIDP_VALUE_CAPS& aCaps)
    133        : caps(aCaps), value(0.0f), active(true) {}
    134  };
    135  nsTArray<axisValue> axes;
    136 
    137  RefPtr<GamepadRemapper> remapper;
    138 
    139  // Used during rescan to find devices that were disconnected.
    140  bool present;
    141 
    142  Gamepad(uint32_t aNumAxes, uint32_t aNumButtons, GamepadType aType)
    143      : type(aType), numAxes(aNumAxes), numButtons(aNumButtons), present(true) {
    144    buttons.SetLength(numButtons);
    145    axes.SetLength(numAxes);
    146  }
    147 
    148 private:
    149  Gamepad() {}
    150 };
    151 
    152 // Drop this in favor of decltype when we require a new enough SDK.
    153 using XInputEnable_func = void(WINAPI*)(BOOL);
    154 
    155 // RAII class to wrap loading the XInput DLL
    156 class XInputLoader {
    157 public:
    158  XInputLoader()
    159      : module(nullptr), mXInputGetState(nullptr), mXInputEnable(nullptr) {
    160    // xinput1_4.dll exists on Windows 8
    161    // xinput9_1_0.dll exists on Windows 7 and Vista
    162    // xinput1_3.dll shipped with the DirectX SDK
    163    const wchar_t* dlls[] = {L"xinput1_4.dll", L"xinput9_1_0.dll",
    164                             L"xinput1_3.dll"};
    165    const size_t kNumDLLs = std::size(dlls);
    166    for (size_t i = 0; i < kNumDLLs; ++i) {
    167      module = LoadLibraryW(dlls[i]);
    168      if (module) {
    169        mXInputEnable = reinterpret_cast<XInputEnable_func>(
    170            GetProcAddress(module, "XInputEnable"));
    171        // Checking if `XInputGetStateEx` is available. If not,
    172        // we will fallback to use `XInputGetState`.
    173        mXInputGetState = reinterpret_cast<decltype(XInputGetState)*>(
    174            GetProcAddress(module, kXInputGetStateExOrdinal));
    175        if (!mXInputGetState) {
    176          mXInputGetState = reinterpret_cast<decltype(XInputGetState)*>(
    177              GetProcAddress(module, "XInputGetState"));
    178        }
    179        MOZ_ASSERT(mXInputGetState &&
    180                   "XInputGetState must be linked successfully.");
    181 
    182        if (mXInputEnable) {
    183          mXInputEnable(TRUE);
    184        }
    185        break;
    186      }
    187    }
    188  }
    189 
    190  ~XInputLoader() {
    191    mXInputEnable = nullptr;
    192    mXInputGetState = nullptr;
    193 
    194    if (module) {
    195      FreeLibrary(module);
    196    }
    197  }
    198 
    199  explicit operator bool() { return module && mXInputGetState; }
    200 
    201  HMODULE module;
    202  decltype(XInputGetState)* mXInputGetState;
    203  XInputEnable_func mXInputEnable;
    204 };
    205 
    206 bool GetPreparsedData(HANDLE handle, nsTArray<uint8_t>& data) {
    207  UINT size;
    208  if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) ==
    209      kRawInputError) {
    210    return false;
    211  }
    212  data.SetLength(size);
    213  return GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, data.Elements(),
    214                               &size) > 0;
    215 }
    216 
    217 /*
    218 * Given an axis value and a minimum and maximum range,
    219 * scale it to be in the range -1.0 .. 1.0.
    220 */
    221 double ScaleAxis(ULONG value, LONG min, LONG max) {
    222  return 2.0 * (value - min) / (max - min) - 1.0;
    223 }
    224 
    225 /*
    226 * Return true if this USB HID usage page and usage are of a type we
    227 * know how to handle.
    228 */
    229 bool SupportedUsage(USHORT page, USHORT usage) {
    230  for (unsigned i = 0; i < std::size(kUsagePages); i++) {
    231    if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) {
    232      return true;
    233    }
    234  }
    235  return false;
    236 }
    237 
    238 class HIDLoader {
    239 public:
    240  HIDLoader()
    241      : mHidD_GetProductString(nullptr),
    242        mHidP_GetCaps(nullptr),
    243        mHidP_GetButtonCaps(nullptr),
    244        mHidP_GetValueCaps(nullptr),
    245        mHidP_GetUsages(nullptr),
    246        mHidP_GetUsageValue(nullptr),
    247        mHidP_GetScaledUsageValue(nullptr),
    248        mModule(LoadLibraryW(L"hid.dll")) {
    249    if (mModule) {
    250      mHidD_GetProductString =
    251          reinterpret_cast<decltype(HidD_GetProductString)*>(
    252              GetProcAddress(mModule, "HidD_GetProductString"));
    253      mHidP_GetCaps = reinterpret_cast<decltype(HidP_GetCaps)*>(
    254          GetProcAddress(mModule, "HidP_GetCaps"));
    255      mHidP_GetButtonCaps = reinterpret_cast<decltype(HidP_GetButtonCaps)*>(
    256          GetProcAddress(mModule, "HidP_GetButtonCaps"));
    257      mHidP_GetValueCaps = reinterpret_cast<decltype(HidP_GetValueCaps)*>(
    258          GetProcAddress(mModule, "HidP_GetValueCaps"));
    259      mHidP_GetUsages = reinterpret_cast<decltype(HidP_GetUsages)*>(
    260          GetProcAddress(mModule, "HidP_GetUsages"));
    261      mHidP_GetUsageValue = reinterpret_cast<decltype(HidP_GetUsageValue)*>(
    262          GetProcAddress(mModule, "HidP_GetUsageValue"));
    263      mHidP_GetScaledUsageValue =
    264          reinterpret_cast<decltype(HidP_GetScaledUsageValue)*>(
    265              GetProcAddress(mModule, "HidP_GetScaledUsageValue"));
    266    }
    267  }
    268 
    269  ~HIDLoader() {
    270    if (mModule) {
    271      FreeLibrary(mModule);
    272    }
    273  }
    274 
    275  explicit operator bool() {
    276    return mModule && mHidD_GetProductString && mHidP_GetCaps &&
    277           mHidP_GetButtonCaps && mHidP_GetValueCaps && mHidP_GetUsages &&
    278           mHidP_GetUsageValue && mHidP_GetScaledUsageValue;
    279  }
    280 
    281  decltype(HidD_GetProductString)* mHidD_GetProductString;
    282  decltype(HidP_GetCaps)* mHidP_GetCaps;
    283  decltype(HidP_GetButtonCaps)* mHidP_GetButtonCaps;
    284  decltype(HidP_GetValueCaps)* mHidP_GetValueCaps;
    285  decltype(HidP_GetUsages)* mHidP_GetUsages;
    286  decltype(HidP_GetUsageValue)* mHidP_GetUsageValue;
    287  decltype(HidP_GetScaledUsageValue)* mHidP_GetScaledUsageValue;
    288 
    289 private:
    290  HMODULE mModule;
    291 };
    292 
    293 HWND sHWnd = nullptr;
    294 
    295 static void DirectInputMessageLoopOnceCallback(nsITimer* aTimer,
    296                                               void* aClosure) {
    297  MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
    298  MSG msg;
    299  while (PeekMessageW(&msg, sHWnd, 0, 0, PM_REMOVE) > 0) {
    300    TranslateMessage(&msg);
    301    DispatchMessage(&msg);
    302  }
    303  aTimer->Cancel();
    304  if (!sIsShutdown) {
    305    aTimer->InitWithNamedFuncCallback(DirectInputMessageLoopOnceCallback,
    306                                      nullptr, kWindowsGamepadPollInterval,
    307                                      nsITimer::TYPE_ONE_SHOT,
    308                                      "DirectInputMessageLoopOnceCallback"_ns);
    309  }
    310 }
    311 
    312 class WindowsGamepadService {
    313 public:
    314  WindowsGamepadService() {
    315    mDirectInputTimer = NS_NewTimer();
    316    mXInputTimer = NS_NewTimer();
    317    mDeviceChangeTimer = NS_NewTimer();
    318  }
    319  virtual ~WindowsGamepadService() { Cleanup(); }
    320 
    321  void DevicesChanged(bool aIsStablizing);
    322 
    323  void StartMessageLoop() {
    324    MOZ_ASSERT(mDirectInputTimer);
    325    mDirectInputTimer->InitWithNamedFuncCallback(
    326        DirectInputMessageLoopOnceCallback, nullptr,
    327        kWindowsGamepadPollInterval, nsITimer::TYPE_ONE_SHOT,
    328        "DirectInputMessageLoopOnceCallback"_ns);
    329  }
    330 
    331  void Startup();
    332  void Shutdown();
    333  // Parse gamepad input from a WM_INPUT message.
    334  bool HandleRawInput(HRAWINPUT handle);
    335  void SetLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
    336                              const Tainted<uint32_t>& aLightColorIndex,
    337                              const uint8_t& aRed, const uint8_t& aGreen,
    338                              const uint8_t& aBlue);
    339  size_t WriteOutputReport(const std::vector<uint8_t>& aReport);
    340  static void XInputMessageLoopOnceCallback(nsITimer* aTimer, void* aClosure);
    341  static void DevicesChangeCallback(nsITimer* aTimer, void* aService);
    342 
    343 private:
    344  void ScanForDevices();
    345  // Look for connected raw input devices.
    346  void ScanForRawInputDevices();
    347  // Look for connected XInput devices.
    348  bool ScanForXInputDevices();
    349  bool HaveXInputGamepad(unsigned int userIndex);
    350 
    351  bool mIsXInputMonitoring;
    352  void PollXInput();
    353  void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state);
    354 
    355  // Get information about a raw input gamepad.
    356  bool GetRawGamepad(HANDLE handle);
    357  void Cleanup();
    358 
    359  // List of connected devices.
    360  nsTArray<Gamepad> mGamepads;
    361 
    362  HIDLoader mHID;
    363  nsAutoHandle mHidHandle;
    364  XInputLoader mXInput;
    365 
    366  nsCOMPtr<nsITimer> mDirectInputTimer;
    367  nsCOMPtr<nsITimer> mXInputTimer;
    368  nsCOMPtr<nsITimer> mDeviceChangeTimer;
    369 };
    370 
    371 void WindowsGamepadService::ScanForRawInputDevices() {
    372  if (!mHID) {
    373    return;
    374  }
    375 
    376  UINT numDevices;
    377  if (GetRawInputDeviceList(nullptr, &numDevices, sizeof(RAWINPUTDEVICELIST)) ==
    378      kRawInputError) {
    379    return;
    380  }
    381  nsTArray<RAWINPUTDEVICELIST> devices(numDevices);
    382  devices.SetLength(numDevices);
    383  if (GetRawInputDeviceList(devices.Elements(), &numDevices,
    384                            sizeof(RAWINPUTDEVICELIST)) == kRawInputError) {
    385    return;
    386  }
    387 
    388  for (unsigned i = 0; i < devices.Length(); i++) {
    389    if (devices[i].dwType == RIM_TYPEHID) {
    390      GetRawGamepad(devices[i].hDevice);
    391    }
    392  }
    393 }
    394 
    395 // static
    396 void WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer* aTimer,
    397                                                          void* aService) {
    398  MOZ_ASSERT(aService);
    399  WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
    400  self->PollXInput();
    401  if (self->mIsXInputMonitoring) {
    402    aTimer->Cancel();
    403    aTimer->InitWithNamedFuncCallback(
    404        XInputMessageLoopOnceCallback, self, kWindowsGamepadPollInterval,
    405        nsITimer::TYPE_ONE_SHOT, "XInputMessageLoopOnceCallback"_ns);
    406  }
    407 }
    408 
    409 // static
    410 void WindowsGamepadService::DevicesChangeCallback(nsITimer* aTimer,
    411                                                  void* aService) {
    412  MOZ_ASSERT(aService);
    413  WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
    414  self->DevicesChanged(false);
    415 }
    416 
    417 bool WindowsGamepadService::HaveXInputGamepad(unsigned int userIndex) {
    418  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
    419    if (mGamepads[i].type == kXInputGamepad &&
    420        mGamepads[i].userIndex == userIndex) {
    421      mGamepads[i].present = true;
    422      return true;
    423    }
    424  }
    425  return false;
    426 }
    427 
    428 bool WindowsGamepadService::ScanForXInputDevices() {
    429  MOZ_ASSERT(mXInput, "XInput should be present!");
    430 
    431  bool found = false;
    432  RefPtr<GamepadPlatformService> service =
    433      GamepadPlatformService::GetParentService();
    434  if (!service) {
    435    return found;
    436  }
    437 
    438  for (unsigned int i = 0; i < XUSER_MAX_COUNT; i++) {
    439    XINPUT_STATE state = {};
    440 
    441    if (!mXInput.mXInputGetState ||
    442        mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
    443      continue;
    444    }
    445 
    446    found = true;
    447    // See if this device is already present in our list.
    448    if (HaveXInputGamepad(i)) {
    449      continue;
    450    }
    451 
    452    // Not already present, add it.
    453    Gamepad gamepad(kStandardGamepadAxes, kStandardGamepadButtons,
    454                    kXInputGamepad);
    455    gamepad.userIndex = i;
    456    gamepad.state = state;
    457    gamepad.gamepadHandle = service->AddGamepad(
    458        "xinput", GamepadMappingType::Standard, GamepadHand::_empty,
    459        kStandardGamepadButtons, kStandardGamepadAxes, 0, 0,
    460        0);  // TODO: Bug 680289, implement gamepad haptics for Windows.
    461    mGamepads.AppendElement(std::move(gamepad));
    462  }
    463 
    464  return found;
    465 }
    466 
    467 void WindowsGamepadService::ScanForDevices() {
    468  RefPtr<GamepadPlatformService> service =
    469      GamepadPlatformService::GetParentService();
    470  if (!service) {
    471    return;
    472  }
    473 
    474  for (int i = mGamepads.Length() - 1; i >= 0; i--) {
    475    mGamepads[i].present = false;
    476  }
    477 
    478  if (mHID) {
    479    ScanForRawInputDevices();
    480  }
    481  if (mXInput) {
    482    mXInputTimer->Cancel();
    483    if (ScanForXInputDevices()) {
    484      mIsXInputMonitoring = true;
    485      mXInputTimer->InitWithNamedFuncCallback(
    486          XInputMessageLoopOnceCallback, this, kWindowsGamepadPollInterval,
    487          nsITimer::TYPE_ONE_SHOT, "XInputMessageLoopOnceCallback"_ns);
    488    } else {
    489      mIsXInputMonitoring = false;
    490    }
    491  }
    492 
    493  // Look for devices that are no longer present and remove them.
    494  for (int i = mGamepads.Length() - 1; i >= 0; i--) {
    495    if (!mGamepads[i].present) {
    496      service->RemoveGamepad(mGamepads[i].gamepadHandle);
    497      mGamepads.RemoveElementAt(i);
    498    }
    499  }
    500 }
    501 
    502 void WindowsGamepadService::PollXInput() {
    503  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
    504    if (mGamepads[i].type != kXInputGamepad) {
    505      continue;
    506    }
    507 
    508    XINPUT_STATE state = {};
    509 
    510    if (!mXInput.mXInputGetState ||
    511        mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
    512      continue;
    513    }
    514 
    515    if (state.dwPacketNumber != mGamepads[i].state.dwPacketNumber) {
    516      CheckXInputChanges(mGamepads[i], state);
    517    }
    518  }
    519 }
    520 
    521 void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad,
    522                                               XINPUT_STATE& state) {
    523  RefPtr<GamepadPlatformService> service =
    524      GamepadPlatformService::GetParentService();
    525  if (!service) {
    526    return;
    527  }
    528  // Handle digital buttons first
    529  for (size_t b = 0; b < kNumMappings; b++) {
    530    if (state.Gamepad.wButtons & kXIButtonMap[b].button &&
    531        !(gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button)) {
    532      // Button pressed
    533      service->NewButtonEvent(gamepad.gamepadHandle, kXIButtonMap[b].mapped,
    534                              true);
    535    } else if (!(state.Gamepad.wButtons & kXIButtonMap[b].button) &&
    536               gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button) {
    537      // Button released
    538      service->NewButtonEvent(gamepad.gamepadHandle, kXIButtonMap[b].mapped,
    539                              false);
    540    }
    541  }
    542 
    543  // Then triggers
    544  if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) {
    545    const bool pressed =
    546        state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
    547    service->NewButtonEvent(gamepad.gamepadHandle, kButtonLeftTrigger, pressed,
    548                            state.Gamepad.bLeftTrigger / 255.0);
    549  }
    550  if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) {
    551    const bool pressed =
    552        state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
    553    service->NewButtonEvent(gamepad.gamepadHandle, kButtonRightTrigger, pressed,
    554                            state.Gamepad.bRightTrigger / 255.0);
    555  }
    556 
    557  // Finally deal with analog sticks
    558  // TODO: bug 1001955 - Support deadzones.
    559  if (state.Gamepad.sThumbLX != gamepad.state.Gamepad.sThumbLX) {
    560    const float div = state.Gamepad.sThumbLX > 0 ? 32767.0 : 32768.0;
    561    service->NewAxisMoveEvent(gamepad.gamepadHandle, kLeftStickXAxis,
    562                              state.Gamepad.sThumbLX / div);
    563  }
    564  if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) {
    565    const float div = state.Gamepad.sThumbLY > 0 ? 32767.0 : 32768.0;
    566    service->NewAxisMoveEvent(gamepad.gamepadHandle, kLeftStickYAxis,
    567                              -1.0 * state.Gamepad.sThumbLY / div);
    568  }
    569  if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) {
    570    const float div = state.Gamepad.sThumbRX > 0 ? 32767.0 : 32768.0;
    571    service->NewAxisMoveEvent(gamepad.gamepadHandle, kRightStickXAxis,
    572                              state.Gamepad.sThumbRX / div);
    573  }
    574  if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) {
    575    const float div = state.Gamepad.sThumbRY > 0 ? 32767.0 : 32768.0;
    576    service->NewAxisMoveEvent(gamepad.gamepadHandle, kRightStickYAxis,
    577                              -1.0 * state.Gamepad.sThumbRY / div);
    578  }
    579  gamepad.state = state;
    580 }
    581 
    582 // Used to sort a list of axes by HID usage.
    583 class HidValueComparator {
    584 public:
    585  bool Equals(const Gamepad::axisValue& c1,
    586              const Gamepad::axisValue& c2) const {
    587    return c1.caps.UsagePage == c2.caps.UsagePage &&
    588           c1.caps.Range.UsageMin == c2.caps.Range.UsageMin;
    589  }
    590  bool LessThan(const Gamepad::axisValue& c1,
    591                const Gamepad::axisValue& c2) const {
    592    if (c1.caps.UsagePage == c2.caps.UsagePage) {
    593      return c1.caps.Range.UsageMin < c2.caps.Range.UsageMin;
    594    }
    595    return c1.caps.UsagePage < c2.caps.UsagePage;
    596  }
    597 };
    598 
    599 // GetRawGamepad() processes its raw data from HID and
    600 // then trying to remapping buttons and axes based on
    601 // the mapping rules that are defined for different gamepad products.
    602 bool WindowsGamepadService::GetRawGamepad(HANDLE handle) {
    603  RefPtr<GamepadPlatformService> service =
    604      GamepadPlatformService::GetParentService();
    605  if (!service) {
    606    return true;
    607  }
    608 
    609  if (!mHID) {
    610    return false;
    611  }
    612 
    613  for (unsigned i = 0; i < mGamepads.Length(); i++) {
    614    if (mGamepads[i].type == kRawInputGamepad &&
    615        mGamepads[i].handle == handle) {
    616      mGamepads[i].present = true;
    617      return true;
    618    }
    619  }
    620 
    621  RID_DEVICE_INFO rdi = {};
    622  UINT size = rdi.cbSize = sizeof(RID_DEVICE_INFO);
    623  if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) ==
    624      kRawInputError) {
    625    return false;
    626  }
    627  // Ensure that this is a device we care about
    628  if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) {
    629    return false;
    630  }
    631 
    632  // Device name is a mostly-opaque string.
    633  if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, nullptr, &size) ==
    634      kRawInputError) {
    635    return false;
    636  }
    637 
    638  nsTArray<wchar_t> devname(size);
    639  devname.SetLength(size);
    640  if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, devname.Elements(),
    641                            &size) == kRawInputError) {
    642    return false;
    643  }
    644 
    645  // Per http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx
    646  // device names containing "IG_" are XInput controllers. Ignore those
    647  // devices since we'll handle them with XInput.
    648  if (wcsstr(devname.Elements(), L"IG_")) {
    649    return false;
    650  }
    651 
    652  // Product string is a human-readable name.
    653  // Per
    654  // http://msdn.microsoft.com/en-us/library/windows/hardware/ff539681%28v=vs.85%29.aspx
    655  // "For USB devices, the maximum string length is 126 wide characters (not
    656  // including the terminating NULL character)."
    657  wchar_t name[128] = {0};
    658  size = sizeof(name);
    659  nsTArray<char> gamepad_name;
    660  // Creating this file with FILE_FLAG_OVERLAPPED to perform
    661  // an asynchronous request in WriteOutputReport.
    662  mHidHandle.own(CreateFile(devname.Elements(), GENERIC_READ | GENERIC_WRITE,
    663                            FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
    664                            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
    665  if (mHidHandle != INVALID_HANDLE_VALUE) {
    666    if (mHID.mHidD_GetProductString(mHidHandle, &name, size)) {
    667      int bytes = WideCharToMultiByte(CP_UTF8, 0, name, -1, nullptr, 0, nullptr,
    668                                      nullptr);
    669      gamepad_name.SetLength(bytes);
    670      WideCharToMultiByte(CP_UTF8, 0, name, -1, gamepad_name.Elements(), bytes,
    671                          nullptr, nullptr);
    672    }
    673  }
    674  if (gamepad_name.Length() == 0 || !gamepad_name[0]) {
    675    const char kUnknown[] = "Unknown Gamepad";
    676    gamepad_name.SetLength(std::size(kUnknown));
    677    strcpy_s(gamepad_name.Elements(), gamepad_name.Length(), kUnknown);
    678  }
    679 
    680  char gamepad_id[256] = {0};
    681  _snprintf_s(gamepad_id, _TRUNCATE, "%04x-%04x-%s", rdi.hid.dwVendorId,
    682              rdi.hid.dwProductId, gamepad_name.Elements());
    683 
    684  nsTArray<uint8_t> preparsedbytes;
    685  if (!GetPreparsedData(handle, preparsedbytes)) {
    686    return false;
    687  }
    688 
    689  PHIDP_PREPARSED_DATA parsed =
    690      reinterpret_cast<PHIDP_PREPARSED_DATA>(preparsedbytes.Elements());
    691  HIDP_CAPS caps;
    692  if (mHID.mHidP_GetCaps(parsed, &caps) != HIDP_STATUS_SUCCESS) {
    693    return false;
    694  }
    695 
    696  // Enumerate buttons.
    697  USHORT count = caps.NumberInputButtonCaps;
    698  nsTArray<HIDP_BUTTON_CAPS> buttonCaps(count);
    699  buttonCaps.SetLength(count);
    700  if (mHID.mHidP_GetButtonCaps(HidP_Input, buttonCaps.Elements(), &count,
    701                               parsed) != HIDP_STATUS_SUCCESS) {
    702    return false;
    703  }
    704  uint32_t numButtons = 0;
    705  for (unsigned i = 0; i < count; i++) {
    706    // Each buttonCaps is typically a range of buttons.
    707    numButtons +=
    708        buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1;
    709  }
    710 
    711  // Enumerate value caps, which represent axes and d-pads.
    712  count = caps.NumberInputValueCaps;
    713  nsTArray<HIDP_VALUE_CAPS> axisCaps(count);
    714  axisCaps.SetLength(count);
    715  if (mHID.mHidP_GetValueCaps(HidP_Input, axisCaps.Elements(), &count,
    716                              parsed) != HIDP_STATUS_SUCCESS) {
    717    return false;
    718  }
    719 
    720  size_t numAxes = 0;
    721  nsTArray<Gamepad::axisValue> axes(kAxesLengthCap);
    722  // We store these value caps and handle the dpad info in GamepadRemapper
    723  // later.
    724  axes.SetLength(kAxesLengthCap);
    725 
    726  // Looking for the exisiting ramapping rule.
    727  bool defaultRemapper = false;
    728  RefPtr<GamepadRemapper> remapper = GetGamepadRemapper(
    729      rdi.hid.dwVendorId, rdi.hid.dwProductId, defaultRemapper);
    730  MOZ_ASSERT(remapper);
    731 
    732  for (size_t i = 0; i < count; i++) {
    733    const size_t axisIndex =
    734        axisCaps[i].Range.UsageMin - kAxisMinimumUsageNumber;
    735    if (axisIndex < kAxesLengthCap && !axes[axisIndex].active) {
    736      axes[axisIndex].caps = axisCaps[i];
    737      axes[axisIndex].active = true;
    738      numAxes = std::max(numAxes, axisIndex + 1);
    739    }
    740  }
    741 
    742  // Not already present, add it.
    743 
    744  remapper->SetAxisCount(numAxes);
    745  remapper->SetButtonCount(numButtons);
    746  Gamepad gamepad(numAxes, numButtons, kRawInputGamepad);
    747  gamepad.handle = handle;
    748 
    749  for (unsigned i = 0; i < gamepad.numAxes; i++) {
    750    gamepad.axes[i] = axes[i];
    751  }
    752 
    753  gamepad.remapper = remapper.forget();
    754  // TODO: Bug 680289, implement gamepad haptics for Windows.
    755  gamepad.gamepadHandle = service->AddGamepad(
    756      gamepad_id, gamepad.remapper->GetMappingType(), GamepadHand::_empty,
    757      gamepad.remapper->GetButtonCount(), gamepad.remapper->GetAxisCount(), 0,
    758      gamepad.remapper->GetLightIndicatorCount(),
    759      gamepad.remapper->GetTouchEventCount());
    760 
    761  nsTArray<GamepadLightIndicatorType> lightTypes;
    762  gamepad.remapper->GetLightIndicators(lightTypes);
    763  for (uint32_t i = 0; i < lightTypes.Length(); ++i) {
    764    if (lightTypes[i] != GamepadLightIndicator::DefaultType()) {
    765      service->NewLightIndicatorTypeEvent(gamepad.gamepadHandle, i,
    766                                          lightTypes[i]);
    767    }
    768  }
    769 
    770  mGamepads.AppendElement(std::move(gamepad));
    771  return true;
    772 }
    773 
    774 bool WindowsGamepadService::HandleRawInput(HRAWINPUT handle) {
    775  if (!mHID) {
    776    return false;
    777  }
    778 
    779  RefPtr<GamepadPlatformService> service =
    780      GamepadPlatformService::GetParentService();
    781  if (!service) {
    782    return false;
    783  }
    784 
    785  // First, get data from the handle
    786  UINT size;
    787  GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
    788  nsTArray<uint8_t> data(size);
    789  data.SetLength(size);
    790  if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size,
    791                      sizeof(RAWINPUTHEADER)) == kRawInputError) {
    792    return false;
    793  }
    794  PRAWINPUT raw = reinterpret_cast<PRAWINPUT>(data.Elements());
    795 
    796  Gamepad* gamepad = nullptr;
    797  for (unsigned i = 0; i < mGamepads.Length(); i++) {
    798    if (mGamepads[i].type == kRawInputGamepad &&
    799        mGamepads[i].handle == raw->header.hDevice) {
    800      gamepad = &mGamepads[i];
    801      break;
    802    }
    803  }
    804  if (gamepad == nullptr) {
    805    return false;
    806  }
    807 
    808  // Second, get the preparsed data
    809  nsTArray<uint8_t> parsedbytes;
    810  if (!GetPreparsedData(raw->header.hDevice, parsedbytes)) {
    811    return false;
    812  }
    813  PHIDP_PREPARSED_DATA parsed =
    814      reinterpret_cast<PHIDP_PREPARSED_DATA>(parsedbytes.Elements());
    815 
    816  // Get all the pressed buttons.
    817  nsTArray<USAGE> usages(gamepad->numButtons);
    818  usages.SetLength(gamepad->numButtons);
    819  ULONG usageLength = gamepad->numButtons;
    820  if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(),
    821                           &usageLength, parsed, (PCHAR)raw->data.hid.bRawData,
    822                           raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
    823    return false;
    824  }
    825 
    826  nsTArray<bool> buttons(gamepad->numButtons);
    827  buttons.SetLength(gamepad->numButtons);
    828  // If we don't zero out the buttons array first, sometimes it can reuse
    829  // values.
    830  memset(buttons.Elements(), 0, gamepad->numButtons * sizeof(bool));
    831 
    832  for (unsigned i = 0; i < usageLength; i++) {
    833    // The button index in usages may be larger than what we detected when
    834    // enumerating gamepads. If so, warn and continue.
    835    //
    836    // Usage ID of 0 is reserved, so it should always be 1 or higher.
    837    if (NS_WARN_IF((usages[i] - 1u) >= buttons.Length())) {
    838      continue;
    839    }
    840    buttons[usages[i] - 1u] = true;
    841  }
    842 
    843  for (unsigned i = 0; i < gamepad->numButtons; i++) {
    844    if (gamepad->buttons[i] != buttons[i]) {
    845      gamepad->remapper->RemapButtonEvent(gamepad->gamepadHandle, i,
    846                                          buttons[i]);
    847      gamepad->buttons[i] = buttons[i];
    848    }
    849  }
    850 
    851  // Get all axis values.
    852  for (unsigned i = 0; i < gamepad->numAxes; i++) {
    853    double new_value;
    854    if (gamepad->axes[i].caps.LogicalMin < 0) {
    855      LONG value;
    856      if (mHID.mHidP_GetScaledUsageValue(
    857              HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
    858              gamepad->axes[i].caps.Range.UsageMin, &value, parsed,
    859              (PCHAR)raw->data.hid.bRawData,
    860              raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
    861        continue;
    862      }
    863      new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
    864                            gamepad->axes[i].caps.LogicalMax);
    865    } else {
    866      ULONG value;
    867      if (mHID.mHidP_GetUsageValue(
    868              HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
    869              gamepad->axes[i].caps.Range.UsageMin, &value, parsed,
    870              (PCHAR)raw->data.hid.bRawData,
    871              raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
    872        continue;
    873      }
    874 
    875      new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
    876                            gamepad->axes[i].caps.LogicalMax);
    877    }
    878    if (gamepad->axes[i].value != new_value) {
    879      gamepad->remapper->RemapAxisMoveEvent(gamepad->gamepadHandle, i,
    880                                            new_value);
    881      gamepad->axes[i].value = new_value;
    882    }
    883  }
    884 
    885  BYTE* rawData = raw->data.hid.bRawData;
    886  gamepad->remapper->ProcessTouchData(gamepad->gamepadHandle, rawData);
    887 
    888  return true;
    889 }
    890 
    891 void WindowsGamepadService::SetLightIndicatorColor(
    892    const Tainted<GamepadHandle>& aGamepadHandle,
    893    const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
    894    const uint8_t& aGreen, const uint8_t& aBlue) {
    895  // We get aControllerIdx from GamepadPlatformService::AddGamepad(),
    896  // It begins from 1 and is stored at Gamepad.id.
    897  const Gamepad* gamepad = (MOZ_FIND_AND_VALIDATE(
    898      aGamepadHandle, list_item.gamepadHandle == aGamepadHandle, mGamepads));
    899  if (!gamepad) {
    900    MOZ_ASSERT(false);
    901    return;
    902  }
    903 
    904  RefPtr<GamepadRemapper> remapper = gamepad->remapper;
    905  if (!remapper ||
    906      MOZ_IS_VALID(aLightColorIndex,
    907                   remapper->GetLightIndicatorCount() <= aLightColorIndex)) {
    908    MOZ_ASSERT(false);
    909    return;
    910  }
    911 
    912  std::vector<uint8_t> report;
    913  remapper->GetLightColorReport(aRed, aGreen, aBlue, report);
    914  WriteOutputReport(report);
    915 }
    916 
    917 size_t WindowsGamepadService::WriteOutputReport(
    918    const std::vector<uint8_t>& aReport) {
    919  DCHECK(static_cast<const void*>(aReport.data()));
    920  DCHECK_GE(aReport.size(), 1U);
    921  if (!mHidHandle) return 0;
    922 
    923  nsAutoHandle eventHandle(::CreateEvent(nullptr, FALSE, FALSE, nullptr));
    924  OVERLAPPED overlapped = {0};
    925  overlapped.hEvent = eventHandle;
    926 
    927  // Doing an asynchronous write to allows us to time out
    928  // if the write takes too long.
    929  DWORD bytesWritten = 0;
    930  BOOL writeSuccess =
    931      ::WriteFile(mHidHandle, static_cast<const void*>(aReport.data()),
    932                  aReport.size(), &bytesWritten, &overlapped);
    933  if (!writeSuccess) {
    934    DWORD error = ::GetLastError();
    935    if (error == ERROR_IO_PENDING) {
    936      // Wait for the write to complete. This causes WriteOutputReport to behave
    937      // synchronously but with a timeout.
    938      DWORD wait_object = ::WaitForSingleObject(overlapped.hEvent, 100);
    939      if (wait_object == WAIT_OBJECT_0) {
    940        if (!::GetOverlappedResult(mHidHandle, &overlapped, &bytesWritten,
    941                                   TRUE)) {
    942          return 0;
    943        }
    944      } else {
    945        // Wait failed, or the timeout was exceeded before the write completed.
    946        // Cancel the write request.
    947        if (::CancelIo(mHidHandle)) {
    948          wait_object = ::WaitForSingleObject(overlapped.hEvent, INFINITE);
    949          MOZ_ASSERT(wait_object == WAIT_OBJECT_0);
    950        }
    951      }
    952    }
    953  }
    954  return writeSuccess ? bytesWritten : 0;
    955 }
    956 
    957 void WindowsGamepadService::Startup() { ScanForDevices(); }
    958 
    959 void WindowsGamepadService::Shutdown() { Cleanup(); }
    960 
    961 void WindowsGamepadService::Cleanup() {
    962  mIsXInputMonitoring = false;
    963  if (mDirectInputTimer) {
    964    mDirectInputTimer->Cancel();
    965  }
    966  if (mXInputTimer) {
    967    mXInputTimer->Cancel();
    968  }
    969  if (mDeviceChangeTimer) {
    970    mDeviceChangeTimer->Cancel();
    971  }
    972 
    973  mGamepads.Clear();
    974 }
    975 
    976 void WindowsGamepadService::DevicesChanged(bool aIsStablizing) {
    977  if (aIsStablizing) {
    978    mDeviceChangeTimer->Cancel();
    979    mDeviceChangeTimer->InitWithNamedFuncCallback(
    980        DevicesChangeCallback, this, kDevicesChangedStableDelay,
    981        nsITimer::TYPE_ONE_SHOT, "DevicesChangeCallback"_ns);
    982  } else {
    983    ScanForDevices();
    984  }
    985 }
    986 
    987 bool RegisterRawInput(HWND hwnd, bool enable) {
    988  nsTArray<RAWINPUTDEVICE> rid(std::size(kUsagePages));
    989  rid.SetLength(std::size(kUsagePages));
    990 
    991  for (unsigned i = 0; i < rid.Length(); i++) {
    992    rid[i].usUsagePage = kUsagePages[i].usagePage;
    993    rid[i].usUsage = kUsagePages[i].usage;
    994    rid[i].dwFlags =
    995        enable ? RIDEV_EXINPUTSINK | RIDEV_DEVNOTIFY : RIDEV_REMOVE;
    996    rid[i].hwndTarget = hwnd;
    997  }
    998 
    999  if (!RegisterRawInputDevices(rid.Elements(), rid.Length(),
   1000                               sizeof(RAWINPUTDEVICE))) {
   1001    return false;
   1002  }
   1003  return true;
   1004 }
   1005 
   1006 static LRESULT CALLBACK GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam,
   1007                                          LPARAM lParam) {
   1008  const unsigned int DBT_DEVICEARRIVAL = 0x8000;
   1009  const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
   1010  const unsigned int DBT_DEVNODES_CHANGED = 0x7;
   1011 
   1012  switch (msg) {
   1013    case WM_DEVICECHANGE:
   1014      if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE ||
   1015          wParam == DBT_DEVNODES_CHANGED) {
   1016        if (gService) {
   1017          gService->DevicesChanged(true);
   1018        }
   1019      }
   1020      break;
   1021    case WM_INPUT:
   1022      if (gService) {
   1023        gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam));
   1024      }
   1025      break;
   1026  }
   1027  return DefWindowProc(hwnd, msg, wParam, lParam);
   1028 }
   1029 
   1030 class StartWindowsGamepadServiceRunnable final : public Runnable {
   1031 public:
   1032  StartWindowsGamepadServiceRunnable()
   1033      : Runnable("StartWindowsGamepadServiceRunnable") {}
   1034 
   1035  NS_IMETHOD Run() override {
   1036    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
   1037    gService = new WindowsGamepadService();
   1038    gService->Startup();
   1039 
   1040    if (sHWnd == nullptr) {
   1041      WNDCLASSW wc;
   1042      HMODULE hSelf = GetModuleHandle(nullptr);
   1043 
   1044      if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
   1045        ZeroMemory(&wc, sizeof(WNDCLASSW));
   1046        wc.hInstance = hSelf;
   1047        wc.lpfnWndProc = GamepadWindowProc;
   1048        wc.lpszClassName = L"MozillaGamepadClass";
   1049        RegisterClassW(&wc);
   1050      }
   1051 
   1052      sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", 0, 0, 0,
   1053                            0, 0, nullptr, nullptr, hSelf, nullptr);
   1054      RegisterRawInput(sHWnd, true);
   1055    }
   1056 
   1057    // Explicitly start the message loop
   1058    gService->StartMessageLoop();
   1059 
   1060    return NS_OK;
   1061  }
   1062 
   1063 private:
   1064  ~StartWindowsGamepadServiceRunnable() {}
   1065 };
   1066 
   1067 class StopWindowsGamepadServiceRunnable final : public Runnable {
   1068 public:
   1069  StopWindowsGamepadServiceRunnable()
   1070      : Runnable("StopWindowsGamepadServiceRunnable") {}
   1071 
   1072  NS_IMETHOD Run() override {
   1073    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
   1074    if (sHWnd) {
   1075      RegisterRawInput(sHWnd, false);
   1076      DestroyWindow(sHWnd);
   1077      sHWnd = nullptr;
   1078    }
   1079 
   1080    gService->Shutdown();
   1081    delete gService;
   1082    gService = nullptr;
   1083 
   1084    return NS_OK;
   1085  }
   1086 
   1087 private:
   1088  ~StopWindowsGamepadServiceRunnable() {}
   1089 };
   1090 
   1091 }  // namespace
   1092 
   1093 namespace mozilla::dom {
   1094 
   1095 using namespace mozilla::ipc;
   1096 
   1097 void StartGamepadMonitoring() {
   1098  AssertIsOnBackgroundThread();
   1099 
   1100  if (gMonitorThread || gService) {
   1101    return;
   1102  }
   1103  sIsShutdown = false;
   1104  NS_NewNamedThread("Gamepad", getter_AddRefs(gMonitorThread));
   1105  gMonitorThread->Dispatch(new StartWindowsGamepadServiceRunnable(),
   1106                           NS_DISPATCH_NORMAL);
   1107 }
   1108 
   1109 void StopGamepadMonitoring() {
   1110  AssertIsOnBackgroundThread();
   1111 
   1112  if (sIsShutdown) {
   1113    return;
   1114  }
   1115  sIsShutdown = true;
   1116  gMonitorThread->Dispatch(new StopWindowsGamepadServiceRunnable(),
   1117                           NS_DISPATCH_NORMAL);
   1118  gMonitorThread->Shutdown();
   1119  gMonitorThread = nullptr;
   1120 }
   1121 
   1122 void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
   1123                                   const Tainted<uint32_t>& aLightColorIndex,
   1124                                   const uint8_t& aRed, const uint8_t& aGreen,
   1125                                   const uint8_t& aBlue) {
   1126  MOZ_ASSERT(gService);
   1127  if (!gService) {
   1128    return;
   1129  }
   1130  gService->SetLightIndicatorColor(aGamepadHandle, aLightColorIndex, aRed,
   1131                                   aGreen, aBlue);
   1132 }
   1133 
   1134 }  // namespace mozilla::dom