author | Chih-Yi Leu <cleu@mozilla.com> |
Tue, 28 Jun 2016 00:25:00 +0200 | |
changeset 302839 | fe54272cd8ff1fbc4afee38b38ca164db02e0172 |
parent 302838 | 2bae08c081185081c42cea4bac6822d109c0b55e |
child 302840 | 342068569153f7399dbf141c6c2b36bce71888fb |
push id | 30376 |
push user | cbook@mozilla.com |
push date | Tue, 28 Jun 2016 14:09:36 +0000 |
treeherder | mozilla-central@e45890951ce7 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | qdot |
bugs | 1221730 |
milestone | 50.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/gamepad/windows/WindowsGamepad.cpp +++ b/dom/gamepad/windows/WindowsGamepad.cpp @@ -11,26 +11,27 @@ #define UNICODE #endif #include <windows.h> #include <hidsdi.h> #include <stdio.h> #include <xinput.h> #include "nsIComponentManager.h" -#include "nsIObserver.h" -#include "nsIObserverService.h" #include "nsITimer.h" #include "nsTArray.h" +#include "nsThreadUtils.h" #include "mozilla/ArrayUtils.h" +#include "mozilla/ipc/BackgroundParent.h" #include "mozilla/dom/GamepadFunctions.h" #include "mozilla/Services.h" namespace { +using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::GamepadFunctions; using mozilla::ArrayLength; // USB HID usage tables, page 1 (Hat switch) const unsigned kUsageDpad = 0x39; // USB HID usage tables, page 1, 0x30 = X const unsigned kFirstAxis = 0x30; @@ -90,17 +91,22 @@ const size_t kNumMappings = ArrayLength( enum GamepadType { kNoGamepad = 0, kRawInputGamepad, kXInputGamepad }; class WindowsGamepadService; -WindowsGamepadService* gService = nullptr; +// This pointer holds a windows gamepad backend service, +// it will be created and destroyed by background thread and +// used by gMonitorThread +WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr; +nsCOMPtr<nsIThread> gMonitorThread = nullptr; +static bool sIsShutdown = false; struct Gamepad { GamepadType type; // Handle to raw input device HANDLE handle; // XInput Index of the user's controller. Passed to XInputGetState. @@ -250,70 +256,16 @@ SupportedUsage(USHORT page, USHORT usage for (unsigned i = 0; i < ArrayLength(kUsagePages); i++) { if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) { return true; } } return false; } -class Observer : public nsIObserver { -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER - - Observer(WindowsGamepadService& svc) : mSvc(svc), - mObserving(true) - { - nsresult rv; - mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - nsCOMPtr<nsIObserverService> observerService = - mozilla::services::GetObserverService(); - observerService->AddObserver(this, - NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, - false); - } - - void Stop() - { - if (mTimer) { - mTimer->Cancel(); - } - if (mObserving) { - nsCOMPtr<nsIObserverService> observerService = - mozilla::services::GetObserverService(); - observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); - mObserving = false; - } - } - - void SetDeviceChangeTimer() - { - // Set stable timer, since we will get multiple devices-changed - // notifications at once - if (mTimer) { - mTimer->Cancel(); - mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT); - } - } - -private: - virtual ~Observer() - { - Stop(); - } - - // Gamepad service owns us, we just hold a reference back to it. - WindowsGamepadService& mSvc; - nsCOMPtr<nsITimer> mTimer; - bool mObserving; -}; - -NS_IMPL_ISUPPORTS(Observer, nsIObserver); - class HIDLoader { public: HIDLoader() : mModule(LoadLibraryW(L"hid.dll")), mHidD_GetProductString(nullptr), mHidP_GetCaps(nullptr), mHidP_GetButtonCaps(nullptr), mHidP_GetValueCaps(nullptr), mHidP_GetUsages(nullptr), @@ -355,69 +307,65 @@ public: decltype(HidP_GetUsages) *mHidP_GetUsages; decltype(HidP_GetUsageValue) *mHidP_GetUsageValue; decltype(HidP_GetScaledUsageValue) *mHidP_GetScaledUsageValue; private: HMODULE mModule; }; -class WindowsGamepadService { -public: - WindowsGamepadService(); +class WindowsGamepadService +{ + public: + WindowsGamepadService() + { + mXInputTimer = do_CreateInstance("@mozilla.org/timer;1"); + mDeviceChangeTimer = do_CreateInstance("@mozilla.org/timer;1"); + } virtual ~WindowsGamepadService() { Cleanup(); } - enum DeviceChangeType { - DeviceChangeNotification, - DeviceChangeStable - }; - void DevicesChanged(DeviceChangeType type); + void DevicesChanged(bool aIsStablizing); void Startup(); void Shutdown(); // Parse gamepad input from a WM_INPUT message. bool HandleRawInput(HRAWINPUT handle); -private: + static void XInputMessageLoopOnceCallback(nsITimer *aTimer, void* aClosure); + static void DevicesChangeCallback(nsITimer *aTimer, void* aService); + + private: void ScanForDevices(); // Look for connected raw input devices. void ScanForRawInputDevices(); // Look for connected XInput devices. bool ScanForXInputDevices(); bool HaveXInputGamepad(int userIndex); - // Timer callback for XInput polling - static void XInputPollTimerCallback(nsITimer* aTimer, void* aClosure); + bool mIsXInputMonitoring; void PollXInput(); void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state); // Get information about a raw input gamepad. bool GetRawGamepad(HANDLE handle); void Cleanup(); // List of connected devices. nsTArray<Gamepad> mGamepads; - RefPtr<Observer> mObserver; - nsCOMPtr<nsITimer> mXInputPollTimer; - HIDLoader mHID; XInputLoader mXInput; + + nsCOMPtr<nsITimer> mXInputTimer; + nsCOMPtr<nsITimer> mDeviceChangeTimer; }; -WindowsGamepadService::WindowsGamepadService() -{ - nsresult rv; - mXInputPollTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - mObserver = new Observer(*this); -} - void WindowsGamepadService::ScanForRawInputDevices() { if (!mHID) { return; } UINT numDevices; @@ -434,16 +382,40 @@ WindowsGamepadService::ScanForRawInputDe for (unsigned i = 0; i < devices.Length(); i++) { if (devices[i].dwType == RIM_TYPEHID) { GetRawGamepad(devices[i].hDevice); } } } +// static +void +WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer *aTimer, + void* aService) +{ + MOZ_ASSERT(aService); + WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService); + self->PollXInput(); + if (self->mIsXInputMonitoring) { + aTimer->Cancel(); + aTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, self, + kXInputPollInterval, nsITimer::TYPE_ONE_SHOT); + } +} + +// static +void +WindowsGamepadService::DevicesChangeCallback(nsITimer *aTimer, void* aService) +{ + MOZ_ASSERT(aService); + WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService); + self->DevicesChanged(false); +} + bool WindowsGamepadService::HaveXInputGamepad(int userIndex) { for (unsigned int i = 0; i < mGamepads.Length(); i++) { if (mGamepads[i].type == kXInputGamepad && mGamepads[i].userIndex == userIndex) { mGamepads[i].present = true; return true; @@ -493,44 +465,36 @@ WindowsGamepadService::ScanForDevices() for (int i = mGamepads.Length() - 1; i >= 0; i--) { mGamepads[i].present = false; } if (mHID) { ScanForRawInputDevices(); } if (mXInput) { - mXInputPollTimer->Cancel(); + mXInputTimer->Cancel(); if (ScanForXInputDevices()) { - mXInputPollTimer->InitWithFuncCallback(XInputPollTimerCallback, - this, - kXInputPollInterval, - nsITimer::TYPE_REPEATING_SLACK); + mIsXInputMonitoring = true; + mXInputTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, this, + kXInputPollInterval, + nsITimer::TYPE_ONE_SHOT); + } else { + mIsXInputMonitoring = false; } } // Look for devices that are no longer present and remove them. for (int i = mGamepads.Length() - 1; i >= 0; i--) { if (!mGamepads[i].present) { RemoveGamepad(mGamepads[i].id); mGamepads.RemoveElementAt(i); } } } -// static -void -WindowsGamepadService::XInputPollTimerCallback(nsITimer* aTimer, - void* aClosure) -{ - WindowsGamepadService* self = - reinterpret_cast<WindowsGamepadService*>(aClosure); - self->PollXInput(); -} - void WindowsGamepadService::PollXInput() { for (unsigned int i = 0; i < mGamepads.Length(); i++) { if (mGamepads[i].type != kXInputGamepad) { continue; } @@ -836,18 +800,17 @@ LONG value; &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) { continue; } new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin, gamepad->axes[i].caps.LogicalMax); - } - else { + } else { ULONG value; if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0, gamepad->axes[i].caps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) { continue; } @@ -873,51 +836,39 @@ void WindowsGamepadService::Shutdown() { Cleanup(); } void WindowsGamepadService::Cleanup() { - if (mXInputPollTimer) { - mXInputPollTimer->Cancel(); + mIsXInputMonitoring = false; + if (mXInputTimer) { + mXInputTimer->Cancel(); + } + if (mDeviceChangeTimer) { + mDeviceChangeTimer->Cancel(); } mGamepads.Clear(); - if (mObserver) { - mObserver->Stop(); - mObserver = nullptr; - } } void -WindowsGamepadService::DevicesChanged(DeviceChangeType type) +WindowsGamepadService::DevicesChanged(bool aIsStablizing) { - if (type == DeviceChangeNotification) { - if (mObserver) { - mObserver->SetDeviceChangeTimer(); - } - } else if (type == DeviceChangeStable) { + if (aIsStablizing) { + mDeviceChangeTimer->Cancel(); + mDeviceChangeTimer->InitWithFuncCallback(DevicesChangeCallback, this, + kDevicesChangedStableDelay, + nsITimer::TYPE_ONE_SHOT); + } else { ScanForDevices(); } } -NS_IMETHODIMP -Observer::Observe(nsISupports* aSubject, - const char* aTopic, - const char16_t* aData) -{ - if (strcmp(aTopic, "timer-callback") == 0) { - mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable); - } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) { - Stop(); - } - return NS_OK; -} - HWND sHWnd = nullptr; bool RegisterRawInput(HWND hwnd, bool enable) { nsTArray<RAWINPUTDEVICE> rid(ArrayLength(kUsagePages)); rid.SetLength(ArrayLength(kUsagePages)); @@ -945,74 +896,141 @@ GamepadWindowProc(HWND hwnd, UINT msg, W const unsigned int DBT_DEVNODES_CHANGED = 0x7; switch (msg) { case WM_DEVICECHANGE: if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE || wParam == DBT_DEVNODES_CHANGED) { if (gService) { - gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification); + gService->DevicesChanged(true); } } break; case WM_INPUT: if (gService) { gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam)); } break; } return DefWindowProc(hwnd, msg, wParam, lParam); } +class WindowGamepadMessageLoopOnceRunnable final : public Runnable +{ +public: + WindowGamepadMessageLoopOnceRunnable() {} + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread); + MSG msg; + while (PeekMessageW(&msg, sHWnd, 0, 0, PM_REMOVE) > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if (!sIsShutdown) { + NS_DispatchToCurrentThread(new WindowGamepadMessageLoopOnceRunnable()); + } + return NS_OK; + } +private: + ~WindowGamepadMessageLoopOnceRunnable() {} +}; + +class StartWindowsGamepadServiceRunnable final : public Runnable +{ +public: + StartWindowsGamepadServiceRunnable() {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread); + gService = new WindowsGamepadService(); + gService->Startup(); + + if (sHWnd == nullptr) { + WNDCLASSW wc; + HMODULE hSelf = GetModuleHandle(nullptr); + + if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) { + ZeroMemory(&wc, sizeof(WNDCLASSW)); + wc.hInstance = hSelf; + wc.lpfnWndProc = GamepadWindowProc; + wc.lpszClassName = L"MozillaGamepadClass"; + RegisterClassW(&wc); + } + + sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", + 0, 0, 0, 0, 0, + nullptr, nullptr, hSelf, nullptr); + RegisterRawInput(sHWnd, true); + } + + // Explicitly start the message loop + NS_DispatchToCurrentThread(new WindowGamepadMessageLoopOnceRunnable()); + + return NS_OK; + } +private: + ~StartWindowsGamepadServiceRunnable() {} +}; + +class StopWindowsGamepadServiceRunnable final : public Runnable +{ + public: + StopWindowsGamepadServiceRunnable() {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread); + if (sHWnd) { + RegisterRawInput(sHWnd, false); + DestroyWindow(sHWnd); + sHWnd = nullptr; + } + + gService->Shutdown(); + delete gService; + gService = nullptr; + + return NS_OK; + } + private: + ~StopWindowsGamepadServiceRunnable() {} +}; + } // namespace namespace mozilla { namespace dom { -void StartGamepadMonitoring() +using namespace mozilla::ipc; + +void +StartGamepadMonitoring() { - if (gService) { + AssertIsOnBackgroundThread(); + + if (gMonitorThread || gService) { return; } - - gService = new WindowsGamepadService(); - gService->Startup(); - - if (sHWnd == nullptr) { - WNDCLASSW wc; - HMODULE hSelf = GetModuleHandle(nullptr); - - if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) { - ZeroMemory(&wc, sizeof(WNDCLASSW)); - wc.hInstance = hSelf; - wc.lpfnWndProc = GamepadWindowProc; - wc.lpszClassName = L"MozillaGamepadClass"; - RegisterClassW(&wc); - } - - sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", - 0, 0, 0, 0, 0, - nullptr, nullptr, hSelf, nullptr); - RegisterRawInput(sHWnd, true); - } + sIsShutdown = false; + NS_NewThread(getter_AddRefs(gMonitorThread)); + gMonitorThread->Dispatch(new StartWindowsGamepadServiceRunnable(), + NS_DISPATCH_NORMAL); } -void StopGamepadMonitoring() +void +StopGamepadMonitoring() { - if (!gService) { + AssertIsOnBackgroundThread(); + + if (sIsShutdown) { return; } - - if (sHWnd) { - RegisterRawInput(sHWnd, false); - DestroyWindow(sHWnd); - sHWnd = nullptr; - } - - gService->Shutdown(); - delete gService; - gService = nullptr; + sIsShutdown = true; + gMonitorThread->Dispatch(new StopWindowsGamepadServiceRunnable(), NS_DISPATCH_NORMAL); + gMonitorThread->Shutdown(); + gMonitorThread = nullptr; } } // namespace dom } // namespace mozilla -