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