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/. */
20 #include "nsThreadUtils.h"
21 #include "nsWindowsHelpers.h"
23 #include "mozilla/ArrayUtils.h"
25 #include "mozilla/ipc/BackgroundParent.h"
26 #include "mozilla/dom/GamepadPlatformService.h"
27 #include "mozilla/dom/GamepadRemapping.h"
32 using namespace mozilla
;
33 using namespace mozilla::dom
;
35 // USB HID usage tables, page 1, 0x30 = X
36 const uint32_t kAxisMinimumUsageNumber
= 0x30;
37 // USB HID usage tables, page 1 (Hat switch)
38 const uint32_t kAxesLengthCap
= 16;
40 // USB HID usage tables
41 const uint32_t kDesktopUsagePage
= 0x1;
42 const uint32_t kButtonUsagePage
= 0x9;
44 // Multiple devices-changed notifications can be sent when a device
45 // is connected, because USB devices consist of multiple logical devices.
46 // Therefore, we wait a bit after receiving one before looking for
48 const uint32_t kDevicesChangedStableDelay
= 200;
49 // Both DirectInput and XInput are polling-driven here,
50 // so we need to poll it periodically.
51 // 4ms, or 250 Hz, is consistent with Chrome's gamepad implementation.
52 const uint32_t kWindowsGamepadPollInterval
= 4;
54 const UINT kRawInputError
= (UINT
)-1;
56 // In XInputGetState, we can't get the state of Xbox Guide button.
57 // We need to go through the undocumented XInputGetStateEx method
58 // to get that button's state.
59 const LPCSTR kXInputGetStateExOrdinal
= (LPCSTR
)100;
60 // Bitmask for the Guide button in XInputGamepadEx.wButtons.
61 const int XINPUT_GAMEPAD_Guide
= 0x0400;
63 #ifndef XUSER_MAX_COUNT
64 # define XUSER_MAX_COUNT 4
71 // USB HID usage tables, page 1
72 {kDesktopUsagePage
, 4}, // Joystick
73 {kDesktopUsagePage
, 5} // Gamepad
79 } kXIButtonMap
[] = {{XINPUT_GAMEPAD_DPAD_UP
, 12},
80 {XINPUT_GAMEPAD_DPAD_DOWN
, 13},
81 {XINPUT_GAMEPAD_DPAD_LEFT
, 14},
82 {XINPUT_GAMEPAD_DPAD_RIGHT
, 15},
83 {XINPUT_GAMEPAD_START
, 9},
84 {XINPUT_GAMEPAD_BACK
, 8},
85 {XINPUT_GAMEPAD_LEFT_THUMB
, 10},
86 {XINPUT_GAMEPAD_RIGHT_THUMB
, 11},
87 {XINPUT_GAMEPAD_LEFT_SHOULDER
, 4},
88 {XINPUT_GAMEPAD_RIGHT_SHOULDER
, 5},
89 {XINPUT_GAMEPAD_Guide
, 16},
90 {XINPUT_GAMEPAD_A
, 0},
91 {XINPUT_GAMEPAD_B
, 1},
92 {XINPUT_GAMEPAD_X
, 2},
93 {XINPUT_GAMEPAD_Y
, 3}};
94 const size_t kNumMappings
= std::size(kXIButtonMap
);
96 enum GamepadType
{ kNoGamepad
= 0, kRawInputGamepad
, kXInputGamepad
};
98 class WindowsGamepadService
;
99 // This pointer holds a windows gamepad backend service,
100 // it will be created and destroyed by background thread and
101 // used by gMonitorThread
102 WindowsGamepadService
* MOZ_NON_OWNING_REF gService
= nullptr;
103 MOZ_RUNINIT nsCOMPtr
<nsIThread
> gMonitorThread
= nullptr;
104 static bool sIsShutdown
= false;
110 // Handle to raw input device
113 // XInput Index of the user's controller. Passed to XInputGetState.
116 // Last-known state of the controller.
119 // Handle from the GamepadService
120 GamepadHandle gamepadHandle
;
122 // Information about the physical device.
126 nsTArray
<bool> buttons
;
128 HIDP_VALUE_CAPS caps
;
132 axisValue() : value(0.0f
), active(false) {}
133 explicit axisValue(const HIDP_VALUE_CAPS
& aCaps
)
134 : caps(aCaps
), value(0.0f
), active(true) {}
136 nsTArray
<axisValue
> axes
;
138 RefPtr
<GamepadRemapper
> remapper
;
140 // Used during rescan to find devices that were disconnected.
143 Gamepad(uint32_t aNumAxes
, uint32_t aNumButtons
, GamepadType aType
)
144 : type(aType
), numAxes(aNumAxes
), numButtons(aNumButtons
), present(true) {
145 buttons
.SetLength(numButtons
);
146 axes
.SetLength(numAxes
);
153 // Drop this in favor of decltype when we require a new enough SDK.
154 using XInputEnable_func
= void(WINAPI
*)(BOOL
);
156 // RAII class to wrap loading the XInput DLL
160 : module(nullptr), mXInputGetState(nullptr), mXInputEnable(nullptr) {
161 // xinput1_4.dll exists on Windows 8
162 // xinput9_1_0.dll exists on Windows 7 and Vista
163 // xinput1_3.dll shipped with the DirectX SDK
164 const wchar_t* dlls
[] = {L
"xinput1_4.dll", L
"xinput9_1_0.dll",
166 const size_t kNumDLLs
= std::size(dlls
);
167 for (size_t i
= 0; i
< kNumDLLs
; ++i
) {
168 module
= LoadLibraryW(dlls
[i
]);
170 mXInputEnable
= reinterpret_cast<XInputEnable_func
>(
171 GetProcAddress(module
, "XInputEnable"));
172 // Checking if `XInputGetStateEx` is available. If not,
173 // we will fallback to use `XInputGetState`.
174 mXInputGetState
= reinterpret_cast<decltype(XInputGetState
)*>(
175 GetProcAddress(module
, kXInputGetStateExOrdinal
));
176 if (!mXInputGetState
) {
177 mXInputGetState
= reinterpret_cast<decltype(XInputGetState
)*>(
178 GetProcAddress(module
, "XInputGetState"));
180 MOZ_ASSERT(mXInputGetState
&&
181 "XInputGetState must be linked successfully.");
192 mXInputEnable
= nullptr;
193 mXInputGetState
= nullptr;
200 explicit operator bool() { return module
&& mXInputGetState
; }
203 decltype(XInputGetState
)* mXInputGetState
;
204 XInputEnable_func mXInputEnable
;
207 bool GetPreparsedData(HANDLE handle
, nsTArray
<uint8_t>& data
) {
209 if (GetRawInputDeviceInfo(handle
, RIDI_PREPARSEDDATA
, nullptr, &size
) ==
213 data
.SetLength(size
);
214 return GetRawInputDeviceInfo(handle
, RIDI_PREPARSEDDATA
, data
.Elements(),
219 * Given an axis value and a minimum and maximum range,
220 * scale it to be in the range -1.0 .. 1.0.
222 double ScaleAxis(ULONG value
, LONG min
, LONG max
) {
223 return 2.0 * (value
- min
) / (max
- min
) - 1.0;
227 * Return true if this USB HID usage page and usage are of a type we
228 * know how to handle.
230 bool SupportedUsage(USHORT page
, USHORT usage
) {
231 for (unsigned i
= 0; i
< std::size(kUsagePages
); i
++) {
232 if (page
== kUsagePages
[i
].usagePage
&& usage
== kUsagePages
[i
].usage
) {
242 : mHidD_GetProductString(nullptr),
243 mHidP_GetCaps(nullptr),
244 mHidP_GetButtonCaps(nullptr),
245 mHidP_GetValueCaps(nullptr),
246 mHidP_GetUsages(nullptr),
247 mHidP_GetUsageValue(nullptr),
248 mHidP_GetScaledUsageValue(nullptr),
249 mModule(LoadLibraryW(L
"hid.dll")) {
251 mHidD_GetProductString
=
252 reinterpret_cast<decltype(HidD_GetProductString
)*>(
253 GetProcAddress(mModule
, "HidD_GetProductString"));
254 mHidP_GetCaps
= reinterpret_cast<decltype(HidP_GetCaps
)*>(
255 GetProcAddress(mModule
, "HidP_GetCaps"));
256 mHidP_GetButtonCaps
= reinterpret_cast<decltype(HidP_GetButtonCaps
)*>(
257 GetProcAddress(mModule
, "HidP_GetButtonCaps"));
258 mHidP_GetValueCaps
= reinterpret_cast<decltype(HidP_GetValueCaps
)*>(
259 GetProcAddress(mModule
, "HidP_GetValueCaps"));
260 mHidP_GetUsages
= reinterpret_cast<decltype(HidP_GetUsages
)*>(
261 GetProcAddress(mModule
, "HidP_GetUsages"));
262 mHidP_GetUsageValue
= reinterpret_cast<decltype(HidP_GetUsageValue
)*>(
263 GetProcAddress(mModule
, "HidP_GetUsageValue"));
264 mHidP_GetScaledUsageValue
=
265 reinterpret_cast<decltype(HidP_GetScaledUsageValue
)*>(
266 GetProcAddress(mModule
, "HidP_GetScaledUsageValue"));
272 FreeLibrary(mModule
);
276 explicit operator bool() {
277 return mModule
&& mHidD_GetProductString
&& mHidP_GetCaps
&&
278 mHidP_GetButtonCaps
&& mHidP_GetValueCaps
&& mHidP_GetUsages
&&
279 mHidP_GetUsageValue
&& mHidP_GetScaledUsageValue
;
282 decltype(HidD_GetProductString
)* mHidD_GetProductString
;
283 decltype(HidP_GetCaps
)* mHidP_GetCaps
;
284 decltype(HidP_GetButtonCaps
)* mHidP_GetButtonCaps
;
285 decltype(HidP_GetValueCaps
)* mHidP_GetValueCaps
;
286 decltype(HidP_GetUsages
)* mHidP_GetUsages
;
287 decltype(HidP_GetUsageValue
)* mHidP_GetUsageValue
;
288 decltype(HidP_GetScaledUsageValue
)* mHidP_GetScaledUsageValue
;
294 HWND sHWnd
= nullptr;
296 static void DirectInputMessageLoopOnceCallback(nsITimer
* aTimer
,
298 MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread
);
300 while (PeekMessageW(&msg
, sHWnd
, 0, 0, PM_REMOVE
) > 0) {
301 TranslateMessage(&msg
);
302 DispatchMessage(&msg
);
306 aTimer
->InitWithNamedFuncCallback(DirectInputMessageLoopOnceCallback
,
307 nullptr, kWindowsGamepadPollInterval
,
308 nsITimer::TYPE_ONE_SHOT
,
309 "DirectInputMessageLoopOnceCallback");
313 class WindowsGamepadService
{
315 WindowsGamepadService() {
316 mDirectInputTimer
= NS_NewTimer();
317 mXInputTimer
= NS_NewTimer();
318 mDeviceChangeTimer
= NS_NewTimer();
320 virtual ~WindowsGamepadService() { Cleanup(); }
322 void DevicesChanged(bool aIsStablizing
);
324 void StartMessageLoop() {
325 MOZ_ASSERT(mDirectInputTimer
);
326 mDirectInputTimer
->InitWithNamedFuncCallback(
327 DirectInputMessageLoopOnceCallback
, nullptr,
328 kWindowsGamepadPollInterval
, nsITimer::TYPE_ONE_SHOT
,
329 "DirectInputMessageLoopOnceCallback");
334 // Parse gamepad input from a WM_INPUT message.
335 bool HandleRawInput(HRAWINPUT handle
);
336 void SetLightIndicatorColor(const Tainted
<GamepadHandle
>& aGamepadHandle
,
337 const Tainted
<uint32_t>& aLightColorIndex
,
338 const uint8_t& aRed
, const uint8_t& aGreen
,
339 const uint8_t& aBlue
);
340 size_t WriteOutputReport(const std::vector
<uint8_t>& aReport
);
341 static void XInputMessageLoopOnceCallback(nsITimer
* aTimer
, void* aClosure
);
342 static void DevicesChangeCallback(nsITimer
* aTimer
, void* aService
);
345 void ScanForDevices();
346 // Look for connected raw input devices.
347 void ScanForRawInputDevices();
348 // Look for connected XInput devices.
349 bool ScanForXInputDevices();
350 bool HaveXInputGamepad(unsigned int userIndex
);
352 bool mIsXInputMonitoring
;
354 void CheckXInputChanges(Gamepad
& gamepad
, XINPUT_STATE
& state
);
356 // Get information about a raw input gamepad.
357 bool GetRawGamepad(HANDLE handle
);
360 // List of connected devices.
361 nsTArray
<Gamepad
> mGamepads
;
364 nsAutoHandle mHidHandle
;
365 XInputLoader mXInput
;
367 nsCOMPtr
<nsITimer
> mDirectInputTimer
;
368 nsCOMPtr
<nsITimer
> mXInputTimer
;
369 nsCOMPtr
<nsITimer
> mDeviceChangeTimer
;
372 void WindowsGamepadService::ScanForRawInputDevices() {
378 if (GetRawInputDeviceList(nullptr, &numDevices
, sizeof(RAWINPUTDEVICELIST
)) ==
382 nsTArray
<RAWINPUTDEVICELIST
> devices(numDevices
);
383 devices
.SetLength(numDevices
);
384 if (GetRawInputDeviceList(devices
.Elements(), &numDevices
,
385 sizeof(RAWINPUTDEVICELIST
)) == kRawInputError
) {
389 for (unsigned i
= 0; i
< devices
.Length(); i
++) {
390 if (devices
[i
].dwType
== RIM_TYPEHID
) {
391 GetRawGamepad(devices
[i
].hDevice
);
397 void WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer
* aTimer
,
399 MOZ_ASSERT(aService
);
400 WindowsGamepadService
* self
= static_cast<WindowsGamepadService
*>(aService
);
402 if (self
->mIsXInputMonitoring
) {
404 aTimer
->InitWithNamedFuncCallback(
405 XInputMessageLoopOnceCallback
, self
, kWindowsGamepadPollInterval
,
406 nsITimer::TYPE_ONE_SHOT
, "XInputMessageLoopOnceCallback");
411 void WindowsGamepadService::DevicesChangeCallback(nsITimer
* aTimer
,
413 MOZ_ASSERT(aService
);
414 WindowsGamepadService
* self
= static_cast<WindowsGamepadService
*>(aService
);
415 self
->DevicesChanged(false);
418 bool WindowsGamepadService::HaveXInputGamepad(unsigned int userIndex
) {
419 for (unsigned int i
= 0; i
< mGamepads
.Length(); i
++) {
420 if (mGamepads
[i
].type
== kXInputGamepad
&&
421 mGamepads
[i
].userIndex
== userIndex
) {
422 mGamepads
[i
].present
= true;
429 bool WindowsGamepadService::ScanForXInputDevices() {
430 MOZ_ASSERT(mXInput
, "XInput should be present!");
433 RefPtr
<GamepadPlatformService
> service
=
434 GamepadPlatformService::GetParentService();
439 for (unsigned int i
= 0; i
< XUSER_MAX_COUNT
; i
++) {
440 XINPUT_STATE state
= {};
442 if (!mXInput
.mXInputGetState
||
443 mXInput
.mXInputGetState(i
, &state
) != ERROR_SUCCESS
) {
448 // See if this device is already present in our list.
449 if (HaveXInputGamepad(i
)) {
453 // Not already present, add it.
454 Gamepad
gamepad(kStandardGamepadAxes
, kStandardGamepadButtons
,
456 gamepad
.userIndex
= i
;
457 gamepad
.state
= state
;
458 gamepad
.gamepadHandle
= service
->AddGamepad(
459 "xinput", GamepadMappingType::Standard
, GamepadHand::_empty
,
460 kStandardGamepadButtons
, kStandardGamepadAxes
, 0, 0,
461 0); // TODO: Bug 680289, implement gamepad haptics for Windows.
462 mGamepads
.AppendElement(std::move(gamepad
));
468 void WindowsGamepadService::ScanForDevices() {
469 RefPtr
<GamepadPlatformService
> service
=
470 GamepadPlatformService::GetParentService();
475 for (int i
= mGamepads
.Length() - 1; i
>= 0; i
--) {
476 mGamepads
[i
].present
= false;
480 ScanForRawInputDevices();
483 mXInputTimer
->Cancel();
484 if (ScanForXInputDevices()) {
485 mIsXInputMonitoring
= true;
486 mXInputTimer
->InitWithNamedFuncCallback(
487 XInputMessageLoopOnceCallback
, this, kWindowsGamepadPollInterval
,
488 nsITimer::TYPE_ONE_SHOT
, "XInputMessageLoopOnceCallback");
490 mIsXInputMonitoring
= false;
494 // Look for devices that are no longer present and remove them.
495 for (int i
= mGamepads
.Length() - 1; i
>= 0; i
--) {
496 if (!mGamepads
[i
].present
) {
497 service
->RemoveGamepad(mGamepads
[i
].gamepadHandle
);
498 mGamepads
.RemoveElementAt(i
);
503 void WindowsGamepadService::PollXInput() {
504 for (unsigned int i
= 0; i
< mGamepads
.Length(); i
++) {
505 if (mGamepads
[i
].type
!= kXInputGamepad
) {
509 XINPUT_STATE state
= {};
511 if (!mXInput
.mXInputGetState
||
512 mXInput
.mXInputGetState(i
, &state
) != ERROR_SUCCESS
) {
516 if (state
.dwPacketNumber
!= mGamepads
[i
].state
.dwPacketNumber
) {
517 CheckXInputChanges(mGamepads
[i
], state
);
522 void WindowsGamepadService::CheckXInputChanges(Gamepad
& gamepad
,
523 XINPUT_STATE
& state
) {
524 RefPtr
<GamepadPlatformService
> service
=
525 GamepadPlatformService::GetParentService();
529 // Handle digital buttons first
530 for (size_t b
= 0; b
< kNumMappings
; b
++) {
531 if (state
.Gamepad
.wButtons
& kXIButtonMap
[b
].button
&&
532 !(gamepad
.state
.Gamepad
.wButtons
& kXIButtonMap
[b
].button
)) {
534 service
->NewButtonEvent(gamepad
.gamepadHandle
, kXIButtonMap
[b
].mapped
,
536 } else if (!(state
.Gamepad
.wButtons
& kXIButtonMap
[b
].button
) &&
537 gamepad
.state
.Gamepad
.wButtons
& kXIButtonMap
[b
].button
) {
539 service
->NewButtonEvent(gamepad
.gamepadHandle
, kXIButtonMap
[b
].mapped
,
545 if (state
.Gamepad
.bLeftTrigger
!= gamepad
.state
.Gamepad
.bLeftTrigger
) {
547 state
.Gamepad
.bLeftTrigger
>= XINPUT_GAMEPAD_TRIGGER_THRESHOLD
;
548 service
->NewButtonEvent(gamepad
.gamepadHandle
, kButtonLeftTrigger
, pressed
,
549 state
.Gamepad
.bLeftTrigger
/ 255.0);
551 if (state
.Gamepad
.bRightTrigger
!= gamepad
.state
.Gamepad
.bRightTrigger
) {
553 state
.Gamepad
.bRightTrigger
>= XINPUT_GAMEPAD_TRIGGER_THRESHOLD
;
554 service
->NewButtonEvent(gamepad
.gamepadHandle
, kButtonRightTrigger
, pressed
,
555 state
.Gamepad
.bRightTrigger
/ 255.0);
558 // Finally deal with analog sticks
559 // TODO: bug 1001955 - Support deadzones.
560 if (state
.Gamepad
.sThumbLX
!= gamepad
.state
.Gamepad
.sThumbLX
) {
561 const float div
= state
.Gamepad
.sThumbLX
> 0 ? 32767.0 : 32768.0;
562 service
->NewAxisMoveEvent(gamepad
.gamepadHandle
, kLeftStickXAxis
,
563 state
.Gamepad
.sThumbLX
/ div
);
565 if (state
.Gamepad
.sThumbLY
!= gamepad
.state
.Gamepad
.sThumbLY
) {
566 const float div
= state
.Gamepad
.sThumbLY
> 0 ? 32767.0 : 32768.0;
567 service
->NewAxisMoveEvent(gamepad
.gamepadHandle
, kLeftStickYAxis
,
568 -1.0 * state
.Gamepad
.sThumbLY
/ div
);
570 if (state
.Gamepad
.sThumbRX
!= gamepad
.state
.Gamepad
.sThumbRX
) {
571 const float div
= state
.Gamepad
.sThumbRX
> 0 ? 32767.0 : 32768.0;
572 service
->NewAxisMoveEvent(gamepad
.gamepadHandle
, kRightStickXAxis
,
573 state
.Gamepad
.sThumbRX
/ div
);
575 if (state
.Gamepad
.sThumbRY
!= gamepad
.state
.Gamepad
.sThumbRY
) {
576 const float div
= state
.Gamepad
.sThumbRY
> 0 ? 32767.0 : 32768.0;
577 service
->NewAxisMoveEvent(gamepad
.gamepadHandle
, kRightStickYAxis
,
578 -1.0 * state
.Gamepad
.sThumbRY
/ div
);
580 gamepad
.state
= state
;
583 // Used to sort a list of axes by HID usage.
584 class HidValueComparator
{
586 bool Equals(const Gamepad::axisValue
& c1
,
587 const Gamepad::axisValue
& c2
) const {
588 return c1
.caps
.UsagePage
== c2
.caps
.UsagePage
&&
589 c1
.caps
.Range
.UsageMin
== c2
.caps
.Range
.UsageMin
;
591 bool LessThan(const Gamepad::axisValue
& c1
,
592 const Gamepad::axisValue
& c2
) const {
593 if (c1
.caps
.UsagePage
== c2
.caps
.UsagePage
) {
594 return c1
.caps
.Range
.UsageMin
< c2
.caps
.Range
.UsageMin
;
596 return c1
.caps
.UsagePage
< c2
.caps
.UsagePage
;
600 // GetRawGamepad() processes its raw data from HID and
601 // then trying to remapping buttons and axes based on
602 // the mapping rules that are defined for different gamepad products.
603 bool WindowsGamepadService::GetRawGamepad(HANDLE handle
) {
604 RefPtr
<GamepadPlatformService
> service
=
605 GamepadPlatformService::GetParentService();
614 for (unsigned i
= 0; i
< mGamepads
.Length(); i
++) {
615 if (mGamepads
[i
].type
== kRawInputGamepad
&&
616 mGamepads
[i
].handle
== handle
) {
617 mGamepads
[i
].present
= true;
622 RID_DEVICE_INFO rdi
= {};
623 UINT size
= rdi
.cbSize
= sizeof(RID_DEVICE_INFO
);
624 if (GetRawInputDeviceInfo(handle
, RIDI_DEVICEINFO
, &rdi
, &size
) ==
628 // Ensure that this is a device we care about
629 if (!SupportedUsage(rdi
.hid
.usUsagePage
, rdi
.hid
.usUsage
)) {
633 // Device name is a mostly-opaque string.
634 if (GetRawInputDeviceInfo(handle
, RIDI_DEVICENAME
, nullptr, &size
) ==
639 nsTArray
<wchar_t> devname(size
);
640 devname
.SetLength(size
);
641 if (GetRawInputDeviceInfo(handle
, RIDI_DEVICENAME
, devname
.Elements(),
642 &size
) == kRawInputError
) {
646 // Per http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx
647 // device names containing "IG_" are XInput controllers. Ignore those
648 // devices since we'll handle them with XInput.
649 if (wcsstr(devname
.Elements(), L
"IG_")) {
653 // Product string is a human-readable name.
655 // http://msdn.microsoft.com/en-us/library/windows/hardware/ff539681%28v=vs.85%29.aspx
656 // "For USB devices, the maximum string length is 126 wide characters (not
657 // including the terminating NULL character)."
658 wchar_t name
[128] = {0};
660 nsTArray
<char> gamepad_name
;
661 // Creating this file with FILE_FLAG_OVERLAPPED to perform
662 // an asynchronous request in WriteOutputReport.
663 mHidHandle
.own(CreateFile(devname
.Elements(), GENERIC_READ
| GENERIC_WRITE
,
664 FILE_SHARE_READ
| FILE_SHARE_WRITE
, nullptr,
665 OPEN_EXISTING
, FILE_FLAG_OVERLAPPED
, nullptr));
666 if (mHidHandle
!= INVALID_HANDLE_VALUE
) {
667 if (mHID
.mHidD_GetProductString(mHidHandle
, &name
, size
)) {
668 int bytes
= WideCharToMultiByte(CP_UTF8
, 0, name
, -1, nullptr, 0, nullptr,
670 gamepad_name
.SetLength(bytes
);
671 WideCharToMultiByte(CP_UTF8
, 0, name
, -1, gamepad_name
.Elements(), bytes
,
675 if (gamepad_name
.Length() == 0 || !gamepad_name
[0]) {
676 const char kUnknown
[] = "Unknown Gamepad";
677 gamepad_name
.SetLength(std::size(kUnknown
));
678 strcpy_s(gamepad_name
.Elements(), gamepad_name
.Length(), kUnknown
);
681 char gamepad_id
[256] = {0};
682 _snprintf_s(gamepad_id
, _TRUNCATE
, "%04x-%04x-%s", rdi
.hid
.dwVendorId
,
683 rdi
.hid
.dwProductId
, gamepad_name
.Elements());
685 nsTArray
<uint8_t> preparsedbytes
;
686 if (!GetPreparsedData(handle
, preparsedbytes
)) {
690 PHIDP_PREPARSED_DATA parsed
=
691 reinterpret_cast<PHIDP_PREPARSED_DATA
>(preparsedbytes
.Elements());
693 if (mHID
.mHidP_GetCaps(parsed
, &caps
) != HIDP_STATUS_SUCCESS
) {
697 // Enumerate buttons.
698 USHORT count
= caps
.NumberInputButtonCaps
;
699 nsTArray
<HIDP_BUTTON_CAPS
> buttonCaps(count
);
700 buttonCaps
.SetLength(count
);
701 if (mHID
.mHidP_GetButtonCaps(HidP_Input
, buttonCaps
.Elements(), &count
,
702 parsed
) != HIDP_STATUS_SUCCESS
) {
705 uint32_t numButtons
= 0;
706 for (unsigned i
= 0; i
< count
; i
++) {
707 // Each buttonCaps is typically a range of buttons.
709 buttonCaps
[i
].Range
.UsageMax
- buttonCaps
[i
].Range
.UsageMin
+ 1;
712 // Enumerate value caps, which represent axes and d-pads.
713 count
= caps
.NumberInputValueCaps
;
714 nsTArray
<HIDP_VALUE_CAPS
> axisCaps(count
);
715 axisCaps
.SetLength(count
);
716 if (mHID
.mHidP_GetValueCaps(HidP_Input
, axisCaps
.Elements(), &count
,
717 parsed
) != HIDP_STATUS_SUCCESS
) {
722 nsTArray
<Gamepad::axisValue
> axes(kAxesLengthCap
);
723 // We store these value caps and handle the dpad info in GamepadRemapper
725 axes
.SetLength(kAxesLengthCap
);
727 // Looking for the exisiting ramapping rule.
728 bool defaultRemapper
= false;
729 RefPtr
<GamepadRemapper
> remapper
= GetGamepadRemapper(
730 rdi
.hid
.dwVendorId
, rdi
.hid
.dwProductId
, defaultRemapper
);
731 MOZ_ASSERT(remapper
);
733 for (size_t i
= 0; i
< count
; i
++) {
734 const size_t axisIndex
=
735 axisCaps
[i
].Range
.UsageMin
- kAxisMinimumUsageNumber
;
736 if (axisIndex
< kAxesLengthCap
&& !axes
[axisIndex
].active
) {
737 axes
[axisIndex
].caps
= axisCaps
[i
];
738 axes
[axisIndex
].active
= true;
739 numAxes
= std::max(numAxes
, axisIndex
+ 1);
743 // Not already present, add it.
745 remapper
->SetAxisCount(numAxes
);
746 remapper
->SetButtonCount(numButtons
);
747 Gamepad
gamepad(numAxes
, numButtons
, kRawInputGamepad
);
748 gamepad
.handle
= handle
;
750 for (unsigned i
= 0; i
< gamepad
.numAxes
; i
++) {
751 gamepad
.axes
[i
] = axes
[i
];
754 gamepad
.remapper
= remapper
.forget();
755 // TODO: Bug 680289, implement gamepad haptics for Windows.
756 gamepad
.gamepadHandle
= service
->AddGamepad(
757 gamepad_id
, gamepad
.remapper
->GetMappingType(), GamepadHand::_empty
,
758 gamepad
.remapper
->GetButtonCount(), gamepad
.remapper
->GetAxisCount(), 0,
759 gamepad
.remapper
->GetLightIndicatorCount(),
760 gamepad
.remapper
->GetTouchEventCount());
762 nsTArray
<GamepadLightIndicatorType
> lightTypes
;
763 gamepad
.remapper
->GetLightIndicators(lightTypes
);
764 for (uint32_t i
= 0; i
< lightTypes
.Length(); ++i
) {
765 if (lightTypes
[i
] != GamepadLightIndicator::DefaultType()) {
766 service
->NewLightIndicatorTypeEvent(gamepad
.gamepadHandle
, i
,
771 mGamepads
.AppendElement(std::move(gamepad
));
775 bool WindowsGamepadService::HandleRawInput(HRAWINPUT handle
) {
780 RefPtr
<GamepadPlatformService
> service
=
781 GamepadPlatformService::GetParentService();
786 // First, get data from the handle
788 GetRawInputData(handle
, RID_INPUT
, nullptr, &size
, sizeof(RAWINPUTHEADER
));
789 nsTArray
<uint8_t> data(size
);
790 data
.SetLength(size
);
791 if (GetRawInputData(handle
, RID_INPUT
, data
.Elements(), &size
,
792 sizeof(RAWINPUTHEADER
)) == kRawInputError
) {
795 PRAWINPUT raw
= reinterpret_cast<PRAWINPUT
>(data
.Elements());
797 Gamepad
* gamepad
= nullptr;
798 for (unsigned i
= 0; i
< mGamepads
.Length(); i
++) {
799 if (mGamepads
[i
].type
== kRawInputGamepad
&&
800 mGamepads
[i
].handle
== raw
->header
.hDevice
) {
801 gamepad
= &mGamepads
[i
];
805 if (gamepad
== nullptr) {
809 // Second, get the preparsed data
810 nsTArray
<uint8_t> parsedbytes
;
811 if (!GetPreparsedData(raw
->header
.hDevice
, parsedbytes
)) {
814 PHIDP_PREPARSED_DATA parsed
=
815 reinterpret_cast<PHIDP_PREPARSED_DATA
>(parsedbytes
.Elements());
817 // Get all the pressed buttons.
818 nsTArray
<USAGE
> usages(gamepad
->numButtons
);
819 usages
.SetLength(gamepad
->numButtons
);
820 ULONG usageLength
= gamepad
->numButtons
;
821 if (mHID
.mHidP_GetUsages(HidP_Input
, kButtonUsagePage
, 0, usages
.Elements(),
822 &usageLength
, parsed
, (PCHAR
)raw
->data
.hid
.bRawData
,
823 raw
->data
.hid
.dwSizeHid
) != HIDP_STATUS_SUCCESS
) {
827 nsTArray
<bool> buttons(gamepad
->numButtons
);
828 buttons
.SetLength(gamepad
->numButtons
);
829 // If we don't zero out the buttons array first, sometimes it can reuse
831 memset(buttons
.Elements(), 0, gamepad
->numButtons
* sizeof(bool));
833 for (unsigned i
= 0; i
< usageLength
; i
++) {
834 // The button index in usages may be larger than what we detected when
835 // enumerating gamepads. If so, warn and continue.
837 // Usage ID of 0 is reserved, so it should always be 1 or higher.
838 if (NS_WARN_IF((usages
[i
] - 1u) >= buttons
.Length())) {
841 buttons
[usages
[i
] - 1u] = true;
844 for (unsigned i
= 0; i
< gamepad
->numButtons
; i
++) {
845 if (gamepad
->buttons
[i
] != buttons
[i
]) {
846 gamepad
->remapper
->RemapButtonEvent(gamepad
->gamepadHandle
, i
,
848 gamepad
->buttons
[i
] = buttons
[i
];
852 // Get all axis values.
853 for (unsigned i
= 0; i
< gamepad
->numAxes
; i
++) {
855 if (gamepad
->axes
[i
].caps
.LogicalMin
< 0) {
857 if (mHID
.mHidP_GetScaledUsageValue(
858 HidP_Input
, gamepad
->axes
[i
].caps
.UsagePage
, 0,
859 gamepad
->axes
[i
].caps
.Range
.UsageMin
, &value
, parsed
,
860 (PCHAR
)raw
->data
.hid
.bRawData
,
861 raw
->data
.hid
.dwSizeHid
) != HIDP_STATUS_SUCCESS
) {
864 new_value
= ScaleAxis(value
, gamepad
->axes
[i
].caps
.LogicalMin
,
865 gamepad
->axes
[i
].caps
.LogicalMax
);
868 if (mHID
.mHidP_GetUsageValue(
869 HidP_Input
, gamepad
->axes
[i
].caps
.UsagePage
, 0,
870 gamepad
->axes
[i
].caps
.Range
.UsageMin
, &value
, parsed
,
871 (PCHAR
)raw
->data
.hid
.bRawData
,
872 raw
->data
.hid
.dwSizeHid
) != HIDP_STATUS_SUCCESS
) {
876 new_value
= ScaleAxis(value
, gamepad
->axes
[i
].caps
.LogicalMin
,
877 gamepad
->axes
[i
].caps
.LogicalMax
);
879 if (gamepad
->axes
[i
].value
!= new_value
) {
880 gamepad
->remapper
->RemapAxisMoveEvent(gamepad
->gamepadHandle
, i
,
882 gamepad
->axes
[i
].value
= new_value
;
886 BYTE
* rawData
= raw
->data
.hid
.bRawData
;
887 gamepad
->remapper
->ProcessTouchData(gamepad
->gamepadHandle
, rawData
);
892 void WindowsGamepadService::SetLightIndicatorColor(
893 const Tainted
<GamepadHandle
>& aGamepadHandle
,
894 const Tainted
<uint32_t>& aLightColorIndex
, const uint8_t& aRed
,
895 const uint8_t& aGreen
, const uint8_t& aBlue
) {
896 // We get aControllerIdx from GamepadPlatformService::AddGamepad(),
897 // It begins from 1 and is stored at Gamepad.id.
898 const Gamepad
* gamepad
= (MOZ_FIND_AND_VALIDATE(
899 aGamepadHandle
, list_item
.gamepadHandle
== aGamepadHandle
, mGamepads
));
905 RefPtr
<GamepadRemapper
> remapper
= gamepad
->remapper
;
907 MOZ_IS_VALID(aLightColorIndex
,
908 remapper
->GetLightIndicatorCount() <= aLightColorIndex
)) {
913 std::vector
<uint8_t> report
;
914 remapper
->GetLightColorReport(aRed
, aGreen
, aBlue
, report
);
915 WriteOutputReport(report
);
918 size_t WindowsGamepadService::WriteOutputReport(
919 const std::vector
<uint8_t>& aReport
) {
920 DCHECK(static_cast<const void*>(aReport
.data()));
921 DCHECK_GE(aReport
.size(), 1U);
922 if (!mHidHandle
) return 0;
924 nsAutoHandle
eventHandle(::CreateEvent(nullptr, FALSE
, FALSE
, nullptr));
925 OVERLAPPED overlapped
= {0};
926 overlapped
.hEvent
= eventHandle
;
928 // Doing an asynchronous write to allows us to time out
929 // if the write takes too long.
930 DWORD bytesWritten
= 0;
932 ::WriteFile(mHidHandle
, static_cast<const void*>(aReport
.data()),
933 aReport
.size(), &bytesWritten
, &overlapped
);
935 DWORD error
= ::GetLastError();
936 if (error
== ERROR_IO_PENDING
) {
937 // Wait for the write to complete. This causes WriteOutputReport to behave
938 // synchronously but with a timeout.
939 DWORD wait_object
= ::WaitForSingleObject(overlapped
.hEvent
, 100);
940 if (wait_object
== WAIT_OBJECT_0
) {
941 if (!::GetOverlappedResult(mHidHandle
, &overlapped
, &bytesWritten
,
946 // Wait failed, or the timeout was exceeded before the write completed.
947 // Cancel the write request.
948 if (::CancelIo(mHidHandle
)) {
949 wait_object
= ::WaitForSingleObject(overlapped
.hEvent
, INFINITE
);
950 MOZ_ASSERT(wait_object
== WAIT_OBJECT_0
);
955 return writeSuccess
? bytesWritten
: 0;
958 void WindowsGamepadService::Startup() { ScanForDevices(); }
960 void WindowsGamepadService::Shutdown() { Cleanup(); }
962 void WindowsGamepadService::Cleanup() {
963 mIsXInputMonitoring
= false;
964 if (mDirectInputTimer
) {
965 mDirectInputTimer
->Cancel();
968 mXInputTimer
->Cancel();
970 if (mDeviceChangeTimer
) {
971 mDeviceChangeTimer
->Cancel();
977 void WindowsGamepadService::DevicesChanged(bool aIsStablizing
) {
979 mDeviceChangeTimer
->Cancel();
980 mDeviceChangeTimer
->InitWithNamedFuncCallback(
981 DevicesChangeCallback
, this, kDevicesChangedStableDelay
,
982 nsITimer::TYPE_ONE_SHOT
, "DevicesChangeCallback");
988 bool RegisterRawInput(HWND hwnd
, bool enable
) {
989 nsTArray
<RAWINPUTDEVICE
> rid(std::size(kUsagePages
));
990 rid
.SetLength(std::size(kUsagePages
));
992 for (unsigned i
= 0; i
< rid
.Length(); i
++) {
993 rid
[i
].usUsagePage
= kUsagePages
[i
].usagePage
;
994 rid
[i
].usUsage
= kUsagePages
[i
].usage
;
996 enable
? RIDEV_EXINPUTSINK
| RIDEV_DEVNOTIFY
: RIDEV_REMOVE
;
997 rid
[i
].hwndTarget
= hwnd
;
1000 if (!RegisterRawInputDevices(rid
.Elements(), rid
.Length(),
1001 sizeof(RAWINPUTDEVICE
))) {
1007 static LRESULT CALLBACK
GamepadWindowProc(HWND hwnd
, UINT msg
, WPARAM wParam
,
1009 const unsigned int DBT_DEVICEARRIVAL
= 0x8000;
1010 const unsigned int DBT_DEVICEREMOVECOMPLETE
= 0x8004;
1011 const unsigned int DBT_DEVNODES_CHANGED
= 0x7;
1014 case WM_DEVICECHANGE
:
1015 if (wParam
== DBT_DEVICEARRIVAL
|| wParam
== DBT_DEVICEREMOVECOMPLETE
||
1016 wParam
== DBT_DEVNODES_CHANGED
) {
1018 gService
->DevicesChanged(true);
1024 gService
->HandleRawInput(reinterpret_cast<HRAWINPUT
>(lParam
));
1028 return DefWindowProc(hwnd
, msg
, wParam
, lParam
);
1031 class StartWindowsGamepadServiceRunnable final
: public Runnable
{
1033 StartWindowsGamepadServiceRunnable()
1034 : Runnable("StartWindowsGamepadServiceRunnable") {}
1036 NS_IMETHOD
Run() override
{
1037 MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread
);
1038 gService
= new WindowsGamepadService();
1039 gService
->Startup();
1041 if (sHWnd
== nullptr) {
1043 HMODULE hSelf
= GetModuleHandle(nullptr);
1045 if (!GetClassInfoW(hSelf
, L
"MozillaGamepadClass", &wc
)) {
1046 ZeroMemory(&wc
, sizeof(WNDCLASSW
));
1047 wc
.hInstance
= hSelf
;
1048 wc
.lpfnWndProc
= GamepadWindowProc
;
1049 wc
.lpszClassName
= L
"MozillaGamepadClass";
1050 RegisterClassW(&wc
);
1053 sHWnd
= CreateWindowW(L
"MozillaGamepadClass", L
"Gamepad Watcher", 0, 0, 0,
1054 0, 0, nullptr, nullptr, hSelf
, nullptr);
1055 RegisterRawInput(sHWnd
, true);
1058 // Explicitly start the message loop
1059 gService
->StartMessageLoop();
1065 ~StartWindowsGamepadServiceRunnable() {}
1068 class StopWindowsGamepadServiceRunnable final
: public Runnable
{
1070 StopWindowsGamepadServiceRunnable()
1071 : Runnable("StopWindowsGamepadServiceRunnable") {}
1073 NS_IMETHOD
Run() override
{
1074 MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread
);
1076 RegisterRawInput(sHWnd
, false);
1077 DestroyWindow(sHWnd
);
1081 gService
->Shutdown();
1089 ~StopWindowsGamepadServiceRunnable() {}
1094 namespace mozilla::dom
{
1096 using namespace mozilla::ipc
;
1098 void StartGamepadMonitoring() {
1099 AssertIsOnBackgroundThread();
1101 if (gMonitorThread
|| gService
) {
1104 sIsShutdown
= false;
1105 NS_NewNamedThread("Gamepad", getter_AddRefs(gMonitorThread
));
1106 gMonitorThread
->Dispatch(new StartWindowsGamepadServiceRunnable(),
1107 NS_DISPATCH_NORMAL
);
1110 void StopGamepadMonitoring() {
1111 AssertIsOnBackgroundThread();
1117 gMonitorThread
->Dispatch(new StopWindowsGamepadServiceRunnable(),
1118 NS_DISPATCH_NORMAL
);
1119 gMonitorThread
->Shutdown();
1120 gMonitorThread
= nullptr;
1123 void SetGamepadLightIndicatorColor(const Tainted
<GamepadHandle
>& aGamepadHandle
,
1124 const Tainted
<uint32_t>& aLightColorIndex
,
1125 const uint8_t& aRed
, const uint8_t& aGreen
,
1126 const uint8_t& aBlue
) {
1127 MOZ_ASSERT(gService
);
1131 gService
->SetLightIndicatorColor(aGamepadHandle
, aLightColorIndex
, aRed
,
1135 } // namespace mozilla::dom