michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #ifndef UNICODE michael@0: #define UNICODE michael@0: #endif michael@0: #include michael@0: #define DIRECTINPUT_VERSION 0x0800 michael@0: #include michael@0: michael@0: #include "nsIComponentManager.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsITimer.h" michael@0: #include "nsTArray.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/dom/GamepadService.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: namespace { michael@0: michael@0: using mozilla::dom::GamepadService; michael@0: using mozilla::Mutex; michael@0: using mozilla::MutexAutoLock; michael@0: michael@0: const unsigned kMaxAxes = 32; michael@0: const LONG kMaxAxisValue = 65535; michael@0: const DWORD BUTTON_DOWN_MASK = 0x80; michael@0: // Multiple devices-changed notifications can be sent when a device michael@0: // is connected, because USB devices consist of multiple logical devices. michael@0: // Therefore, we wait a bit after receiving one before looking for michael@0: // device changes. michael@0: const uint32_t kDevicesChangedStableDelay = 200; michael@0: michael@0: class WindowsGamepadService; michael@0: WindowsGamepadService* gService = nullptr; michael@0: michael@0: typedef struct { michael@0: float x,y; michael@0: } HatState; michael@0: michael@0: struct Gamepad { michael@0: // From DirectInput, unique to this device+computer combination. michael@0: GUID guidInstance; michael@0: // The ID assigned by the base GamepadService michael@0: int globalID; michael@0: // A somewhat unique string consisting of the USB vendor/product IDs, michael@0: // and the controller name. michael@0: char idstring[128]; michael@0: // USB vendor and product IDs michael@0: int vendorID; michael@0: int productID; michael@0: // Information about the physical device. michael@0: DWORD axes[kMaxAxes]; michael@0: int numAxes; michael@0: int numHats; michael@0: int numButtons; michael@0: // The human-readable device name. michael@0: char name[128]; michael@0: // The DirectInput device. michael@0: nsRefPtr device; michael@0: // A handle that DirectInput signals when there is new data from michael@0: // the device. michael@0: HANDLE event; michael@0: // The state of any POV hats on the device. michael@0: HatState hatState[4]; michael@0: // Used during rescan to find devices that were disconnected. michael@0: bool present; michael@0: // Passed back from the main thread to indicate a device can michael@0: // now be removed. michael@0: bool remove; michael@0: }; michael@0: michael@0: // Given DWORD |hatPos| representing the position of the POV hat per: michael@0: // http://msdn.microsoft.com/en-us/library/ee418260%28v=VS.85%29.aspx michael@0: // fill |axes| with the position of the x and y axes. michael@0: // michael@0: //XXX: ostensibly the values could be arbitrary degrees for a hat with michael@0: // full rotation, but we'll punt on that for now. This should handle michael@0: // 8-way D-pads exposed as POV hats. michael@0: static void michael@0: HatPosToAxes(DWORD hatPos, HatState& axes) { michael@0: // hatPos is in hundredths of a degree clockwise from north. michael@0: if (LOWORD(hatPos) == 0xFFFF) { michael@0: // centered michael@0: axes.x = axes.y = 0.0; michael@0: } michael@0: else if (hatPos == 0) { michael@0: // Up michael@0: axes.x = 0.0; michael@0: axes.y = -1.0; michael@0: } michael@0: else if (hatPos == 45 * DI_DEGREES) { michael@0: // Up-right michael@0: axes.x = 1.0; michael@0: axes.y = -1.0; michael@0: } michael@0: else if (hatPos == 90 * DI_DEGREES) { michael@0: // Right michael@0: axes.x = 1.0; michael@0: axes.y = 0.0; michael@0: } michael@0: else if (hatPos == 135 * DI_DEGREES) { michael@0: // Down-right michael@0: axes.x = 1.0; michael@0: axes.y = 1.0; michael@0: } michael@0: else if (hatPos == 180 * DI_DEGREES) { michael@0: // Down michael@0: axes.x = 0.0; michael@0: axes.y = 1.0; michael@0: } michael@0: else if (hatPos == 225 * DI_DEGREES) { michael@0: // Down-left michael@0: axes.x = -1.0; michael@0: axes.y = 1.0; michael@0: } michael@0: else if (hatPos == 270 * DI_DEGREES) { michael@0: // Left michael@0: axes.x = -1.0; michael@0: axes.y = 0.0; michael@0: } michael@0: else if (hatPos == 315 * DI_DEGREES) { michael@0: // Up-left michael@0: axes.x = -1.0; michael@0: axes.y = -1.0; michael@0: } michael@0: } michael@0: michael@0: class Observer : public nsIObserver { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: Observer(WindowsGamepadService& svc) : mSvc(svc), michael@0: mObserving(true) { michael@0: nsresult rv; michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: observerService->AddObserver(this, michael@0: NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, michael@0: false); michael@0: } michael@0: michael@0: void Stop() { michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: } michael@0: if (mObserving) { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); michael@0: mObserving = false; michael@0: } michael@0: } michael@0: michael@0: virtual ~Observer() { michael@0: Stop(); michael@0: } michael@0: michael@0: void SetDeviceChangeTimer() { michael@0: // set stable timer, since we will get multiple devices-changed michael@0: // notifications at once michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: // Gamepad service owns us, we just hold a reference back to it. michael@0: WindowsGamepadService& mSvc; michael@0: nsCOMPtr mTimer; michael@0: bool mObserving; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(Observer, nsIObserver); michael@0: michael@0: class WindowsGamepadService { michael@0: public: michael@0: WindowsGamepadService(); michael@0: virtual ~WindowsGamepadService() { michael@0: Cleanup(); michael@0: CloseHandle(mThreadExitEvent); michael@0: CloseHandle(mThreadRescanEvent); michael@0: if (dinput) { michael@0: dinput->Release(); michael@0: dinput = nullptr; michael@0: } michael@0: } michael@0: michael@0: enum DeviceChangeType { michael@0: DeviceChangeNotification, michael@0: DeviceChangeStable michael@0: }; michael@0: void DevicesChanged(DeviceChangeType type); michael@0: void Startup(); michael@0: void Shutdown(); michael@0: void SetGamepadID(int localID, int globalID); michael@0: void RemoveGamepad(int localID); michael@0: michael@0: private: michael@0: void ScanForDevices(); michael@0: void Cleanup(); michael@0: void CleanupGamepad(Gamepad& gamepad); michael@0: // Callback for enumerating axes on a device michael@0: static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, michael@0: LPVOID pvRef); michael@0: // Callback for enumerating devices via DInput michael@0: static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); michael@0: // Thread function to wait on device events michael@0: static DWORD WINAPI DInputThread(LPVOID arg); michael@0: michael@0: // Used to signal the background thread to exit. michael@0: HANDLE mThreadExitEvent; michael@0: // Used to signal the background thread to rescan devices. michael@0: HANDLE mThreadRescanEvent; michael@0: HANDLE mThread; michael@0: michael@0: // List of connected devices. michael@0: nsTArray mGamepads; michael@0: // Used to lock mutation of mGamepads. michael@0: Mutex mMutex; michael@0: // List of event handles used for signaling. michael@0: nsTArray mEvents; michael@0: michael@0: LPDIRECTINPUT8 dinput; michael@0: michael@0: nsRefPtr mObserver; michael@0: }; michael@0: michael@0: // Used to post events from the background thread to the foreground thread. michael@0: class GamepadEvent : public nsRunnable { michael@0: public: michael@0: typedef enum { michael@0: Axis, michael@0: Button, michael@0: HatX, michael@0: HatY, michael@0: HatXY, michael@0: Unknown michael@0: } Type; michael@0: michael@0: GamepadEvent(const Gamepad& gamepad, michael@0: Type type, michael@0: int which, michael@0: DWORD data) : mGlobalID(gamepad.globalID), michael@0: mGamepadAxes(gamepad.numAxes), michael@0: mType(type), michael@0: mWhich(which), michael@0: mData(data) { michael@0: } michael@0: michael@0: NS_IMETHOD Run() { michael@0: nsRefPtr gamepadsvc(GamepadService::GetService()); michael@0: if (!gamepadsvc) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: switch (mType) { michael@0: case Button: michael@0: gamepadsvc->NewButtonEvent(mGlobalID, mWhich, mData & BUTTON_DOWN_MASK); michael@0: break; michael@0: case Axis: { michael@0: float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f; michael@0: gamepadsvc->NewAxisMoveEvent(mGlobalID, mWhich, adjustedData); michael@0: } michael@0: case HatX: michael@0: case HatY: michael@0: case HatXY: { michael@0: // Synthesize 2 axes per POV hat for convenience. michael@0: HatState hatState; michael@0: HatPosToAxes(mData, hatState); michael@0: int xAxis = mGamepadAxes + 2 * mWhich; michael@0: int yAxis = mGamepadAxes + 2 * mWhich + 1; michael@0: //TODO: ostensibly we could not fire an event if one axis hasn't michael@0: // changed, but it's a pain to track that. michael@0: if (mType == HatX || mType == HatXY) { michael@0: gamepadsvc->NewAxisMoveEvent(mGlobalID, xAxis, hatState.x); michael@0: } michael@0: if (mType == HatY || mType == HatXY) { michael@0: gamepadsvc->NewAxisMoveEvent(mGlobalID, yAxis, hatState.y); michael@0: } michael@0: break; michael@0: } michael@0: case Unknown: michael@0: break; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: int mGlobalID; michael@0: int mGamepadAxes; michael@0: // Type of event michael@0: Type mType; michael@0: // Which button/axis is involved michael@0: int mWhich; michael@0: // Data specific to event michael@0: DWORD mData; michael@0: }; michael@0: michael@0: class GamepadChangeEvent : public nsRunnable { michael@0: public: michael@0: enum Type { michael@0: Added, michael@0: Removed michael@0: }; michael@0: GamepadChangeEvent(Gamepad& gamepad, michael@0: int localID, michael@0: Type type) : mLocalID(localID), michael@0: mName(gamepad.idstring), michael@0: mGlobalID(gamepad.globalID), michael@0: mGamepadButtons(gamepad.numButtons), michael@0: mGamepadAxes(gamepad.numAxes), michael@0: mGamepadHats(gamepad.numHats), michael@0: mType(type) { michael@0: } michael@0: michael@0: NS_IMETHOD Run() { michael@0: nsRefPtr gamepadsvc(GamepadService::GetService()); michael@0: if (!gamepadsvc) { michael@0: return NS_OK; michael@0: } michael@0: if (mType == Added) { michael@0: int globalID = gamepadsvc->AddGamepad(mName.get(), michael@0: mozilla::dom::NoMapping, michael@0: mGamepadButtons, michael@0: mGamepadAxes + michael@0: mGamepadHats*2); michael@0: if (gService) { michael@0: gService->SetGamepadID(mLocalID, globalID); michael@0: } michael@0: } else { michael@0: gamepadsvc->RemoveGamepad(mGlobalID); michael@0: if (gService) { michael@0: gService->RemoveGamepad(mLocalID); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: // ID in WindowsGamepadService::mGamepads michael@0: int mLocalID; michael@0: nsCString mName; michael@0: int mGamepadButtons; michael@0: int mGamepadAxes; michael@0: int mGamepadHats; michael@0: // ID from GamepadService michael@0: uint32_t mGlobalID; michael@0: Type mType; michael@0: }; michael@0: michael@0: WindowsGamepadService::WindowsGamepadService() michael@0: : mThreadExitEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)), michael@0: mThreadRescanEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)), michael@0: mThread(nullptr), michael@0: mMutex("Windows Gamepad Service"), michael@0: dinput(nullptr) { michael@0: mObserver = new Observer(*this); michael@0: // Initialize DirectInput michael@0: CoInitialize(nullptr); michael@0: if (CoCreateInstance(CLSID_DirectInput8, michael@0: nullptr, michael@0: CLSCTX_INPROC_SERVER, michael@0: IID_IDirectInput8W, michael@0: (LPVOID*)&dinput) == S_OK) { michael@0: if (dinput->Initialize(GetModuleHandle(nullptr), michael@0: DIRECTINPUT_VERSION) != DI_OK) { michael@0: dinput->Release(); michael@0: dinput = nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // static michael@0: BOOL CALLBACK michael@0: WindowsGamepadService::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, michael@0: LPVOID pvRef) { michael@0: // Ensure that all axes are using the same range. michael@0: Gamepad* gamepad = reinterpret_cast(pvRef); michael@0: DIPROPRANGE dp; michael@0: dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); michael@0: dp.diph.dwSize = sizeof(DIPROPRANGE); michael@0: dp.diph.dwHow = DIPH_BYID; michael@0: dp.diph.dwObj = lpddoi->dwType; michael@0: dp.lMin = 0; michael@0: dp.lMax = kMaxAxisValue; michael@0: gamepad->device->SetProperty(DIPROP_RANGE, &dp.diph); michael@0: // Find what the dwOfs of this object in the c_dfDIJoystick data format is. michael@0: for (DWORD i = 0; i < c_dfDIJoystick.dwNumObjs; i++) { michael@0: if (c_dfDIJoystick.rgodf[i].pguid && michael@0: IsEqualGUID(*c_dfDIJoystick.rgodf[i].pguid, lpddoi->guidType) && michael@0: gamepad->numAxes < kMaxAxes) { michael@0: gamepad->axes[gamepad->numAxes] = c_dfDIJoystick.rgodf[i].dwOfs; michael@0: gamepad->numAxes++; michael@0: break; michael@0: } michael@0: } michael@0: return DIENUM_CONTINUE; michael@0: } michael@0: michael@0: // static michael@0: BOOL CALLBACK michael@0: WindowsGamepadService::EnumCallback(LPCDIDEVICEINSTANCE lpddi, michael@0: LPVOID pvRef) { michael@0: WindowsGamepadService* self = michael@0: reinterpret_cast(pvRef); michael@0: // See if this device is already present in our list. michael@0: { michael@0: MutexAutoLock lock(self->mMutex); michael@0: for (unsigned int i = 0; i < self->mGamepads.Length(); i++) { michael@0: if (memcmp(&lpddi->guidInstance, &self->mGamepads[i].guidInstance, michael@0: sizeof(GUID)) == 0) { michael@0: self->mGamepads[i].present = true; michael@0: return DIENUM_CONTINUE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: Gamepad gamepad = {}; michael@0: if (self->dinput->CreateDevice(lpddi->guidInstance, michael@0: getter_AddRefs(gamepad.device), michael@0: nullptr) michael@0: == DI_OK) { michael@0: gamepad.present = true; michael@0: memcpy(&gamepad.guidInstance, &lpddi->guidInstance, sizeof(GUID)); michael@0: michael@0: DIDEVICEINSTANCE info; michael@0: info.dwSize = sizeof(DIDEVICEINSTANCE); michael@0: if (gamepad.device->GetDeviceInfo(&info) == DI_OK) { michael@0: WideCharToMultiByte(CP_UTF8, 0, info.tszProductName, -1, michael@0: gamepad.name, sizeof(gamepad.name), nullptr, nullptr); michael@0: } michael@0: // Get vendor id and product id michael@0: DIPROPDWORD dp; michael@0: dp.diph.dwSize = sizeof(DIPROPDWORD); michael@0: dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); michael@0: dp.diph.dwObj = 0; michael@0: dp.diph.dwHow = DIPH_DEVICE; michael@0: if (gamepad.device->GetProperty(DIPROP_VIDPID, &dp.diph) == DI_OK) { michael@0: sprintf(gamepad.idstring, "%x-%x-%s", michael@0: LOWORD(dp.dwData), HIWORD(dp.dwData), gamepad.name); michael@0: } michael@0: DIDEVCAPS caps; michael@0: caps.dwSize = sizeof(DIDEVCAPS); michael@0: if (gamepad.device->GetCapabilities(&caps) == DI_OK) { michael@0: gamepad.numHats = caps.dwPOVs; michael@0: gamepad.numButtons = caps.dwButtons; michael@0: //XXX: handle polled devices? michael@0: // (caps.dwFlags & DIDC_POLLEDDATAFORMAT || caps.dwFlags & DIDC_POLLEDDEVICE) michael@0: } michael@0: // Set min/max range for all axes on the device. michael@0: // Axes will be gathered in EnumObjectsCallback. michael@0: gamepad.numAxes = 0; michael@0: gamepad.device->EnumObjects(EnumObjectsCallback, &gamepad, DIDFT_AXIS); michael@0: // Set up structure for setting buffer size for buffered data michael@0: dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); michael@0: dp.diph.dwSize = sizeof(DIPROPDWORD); michael@0: dp.diph.dwObj = 0; michael@0: dp.diph.dwHow = DIPH_DEVICE; michael@0: dp.dwData = 64; // arbitrary michael@0: // Create event so DInput can signal us when there's new data. michael@0: gamepad.event = CreateEventW(nullptr, FALSE, FALSE, nullptr); michael@0: // Set data format, event notification, and acquire device michael@0: if (gamepad.device->SetDataFormat(&c_dfDIJoystick) == DI_OK && michael@0: gamepad.device->SetProperty(DIPROP_BUFFERSIZE, &dp.diph) == DI_OK && michael@0: gamepad.device->SetEventNotification(gamepad.event) == DI_OK && michael@0: gamepad.device->Acquire() == DI_OK) { michael@0: MutexAutoLock lock(self->mMutex); michael@0: self->mGamepads.AppendElement(gamepad); michael@0: // Inform the GamepadService michael@0: int localID = self->mGamepads.Length() - 1; michael@0: nsRefPtr event = michael@0: new GamepadChangeEvent(self->mGamepads[localID], michael@0: localID, michael@0: GamepadChangeEvent::Added); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: else { michael@0: if (gamepad.device) { michael@0: gamepad.device->SetEventNotification(nullptr); michael@0: } michael@0: CloseHandle(gamepad.event); michael@0: } michael@0: } michael@0: return DIENUM_CONTINUE; michael@0: } michael@0: michael@0: void michael@0: WindowsGamepadService::ScanForDevices() { michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: for (int i = mGamepads.Length() - 1; i >= 0; i--) { michael@0: if (mGamepads[i].remove) { michael@0: michael@0: // Main thread has already handled this, safe to remove. michael@0: CleanupGamepad(mGamepads[i]); michael@0: mGamepads.RemoveElementAt(i); michael@0: } else { michael@0: mGamepads[i].present = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, michael@0: (LPDIENUMDEVICESCALLBACK)EnumCallback, michael@0: this, michael@0: DIEDFL_ATTACHEDONLY); michael@0: michael@0: // Look for devices that are no longer present and inform the main thread. michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: for (int i = mGamepads.Length() - 1; i >= 0; i--) { michael@0: if (!mGamepads[i].present) { michael@0: nsRefPtr event = michael@0: new GamepadChangeEvent(mGamepads[i], michael@0: i, michael@0: GamepadChangeEvent::Removed); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: michael@0: mEvents.Clear(); michael@0: for (unsigned int i = 0; i < mGamepads.Length(); i++) { michael@0: mEvents.AppendElement(mGamepads[i].event); michael@0: } michael@0: } michael@0: michael@0: michael@0: // These events must be the last elements in the array, so that michael@0: // the other elements match mGamepads in order. michael@0: mEvents.AppendElement(mThreadRescanEvent); michael@0: mEvents.AppendElement(mThreadExitEvent); michael@0: } michael@0: michael@0: // static michael@0: DWORD WINAPI michael@0: WindowsGamepadService::DInputThread(LPVOID arg) { michael@0: WindowsGamepadService* self = reinterpret_cast(arg); michael@0: self->ScanForDevices(); michael@0: michael@0: while (true) { michael@0: DWORD result = WaitForMultipleObjects(self->mEvents.Length(), michael@0: self->mEvents.Elements(), michael@0: FALSE, michael@0: INFINITE); michael@0: if (result == WAIT_FAILED || michael@0: result == WAIT_OBJECT_0 + self->mEvents.Length() - 1) { michael@0: // error, or the main thread signaled us to exit michael@0: break; michael@0: } michael@0: michael@0: unsigned int i = result - WAIT_OBJECT_0; michael@0: michael@0: if (i == self->mEvents.Length() - 2) { michael@0: // Main thread is signaling for a device rescan. michael@0: self->ScanForDevices(); michael@0: continue; michael@0: } michael@0: michael@0: { michael@0: MutexAutoLock lock(self->mMutex); michael@0: if (i >= self->mGamepads.Length()) { michael@0: // Something would be terribly wrong here, possibly we got michael@0: // a WAIT_ABANDONED_x result. michael@0: continue; michael@0: } michael@0: michael@0: // first query for the number of items in the buffer michael@0: DWORD items = INFINITE; michael@0: nsRefPtr device = self->mGamepads[i].device; michael@0: if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), michael@0: nullptr, michael@0: &items, michael@0: DIGDD_PEEK)== DI_OK) { michael@0: while (items > 0) { michael@0: // now read each buffered event michael@0: //TODO: read more than one event at a time michael@0: DIDEVICEOBJECTDATA data; michael@0: DWORD readCount = sizeof(data) / sizeof(DIDEVICEOBJECTDATA); michael@0: if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), michael@0: &data, &readCount, 0) == DI_OK) { michael@0: //TODO: data.dwTimeStamp michael@0: GamepadEvent::Type type = GamepadEvent::Unknown; michael@0: int which = -1; michael@0: if (data.dwOfs >= DIJOFS_BUTTON0 && data.dwOfs < DIJOFS_BUTTON(32)) { michael@0: type = GamepadEvent::Button; michael@0: which = data.dwOfs - DIJOFS_BUTTON0; michael@0: } michael@0: else if(data.dwOfs >= DIJOFS_X && data.dwOfs < DIJOFS_SLIDER(2)) { michael@0: // axis/slider michael@0: type = GamepadEvent::Axis; michael@0: for (int a = 0; a < self->mGamepads[i].numAxes; a++) { michael@0: if (self->mGamepads[i].axes[a] == data.dwOfs) { michael@0: which = a; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: else if (data.dwOfs >= DIJOFS_POV(0) && data.dwOfs < DIJOFS_POV(4)) { michael@0: HatState hatState; michael@0: HatPosToAxes(data.dwData, hatState); michael@0: which = (data.dwOfs - DIJOFS_POV(0)) / sizeof(DWORD); michael@0: // Only send out axis move events for the axes that moved michael@0: // in this hat move. michael@0: if (hatState.x != self->mGamepads[i].hatState[which].x) { michael@0: type = GamepadEvent::HatX; michael@0: } michael@0: if (hatState.y != self->mGamepads[i].hatState[which].y) { michael@0: if (type == GamepadEvent::HatX) { michael@0: type = GamepadEvent::HatXY; michael@0: } michael@0: else { michael@0: type = GamepadEvent::HatY; michael@0: } michael@0: } michael@0: self->mGamepads[i].hatState[which].x = hatState.x; michael@0: self->mGamepads[i].hatState[which].y = hatState.y; michael@0: } michael@0: michael@0: if (type != GamepadEvent::Unknown && which != -1) { michael@0: nsRefPtr event = michael@0: new GamepadEvent(self->mGamepads[i], type, which, data.dwData); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: items--; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: void michael@0: WindowsGamepadService::Startup() { michael@0: mThread = CreateThread(nullptr, michael@0: 0, michael@0: DInputThread, michael@0: this, michael@0: 0, michael@0: nullptr); michael@0: } michael@0: michael@0: void michael@0: WindowsGamepadService::Shutdown() { michael@0: if (mThread) { michael@0: SetEvent(mThreadExitEvent); michael@0: WaitForSingleObject(mThread, INFINITE); michael@0: CloseHandle(mThread); michael@0: } michael@0: Cleanup(); michael@0: } michael@0: michael@0: // This method is called from the main thread. michael@0: void michael@0: WindowsGamepadService::SetGamepadID(int localID, int globalID) { michael@0: MutexAutoLock lock(mMutex); michael@0: mGamepads[localID].globalID = globalID; michael@0: } michael@0: michael@0: // This method is called from the main thread. michael@0: void WindowsGamepadService::RemoveGamepad(int localID) { michael@0: MutexAutoLock lock(mMutex); michael@0: mGamepads[localID].remove = true; michael@0: // Signal background thread to remove device. michael@0: DevicesChanged(DeviceChangeStable); michael@0: } michael@0: michael@0: void michael@0: WindowsGamepadService::Cleanup() { michael@0: for (unsigned int i = 0; i < mGamepads.Length(); i++) { michael@0: CleanupGamepad(mGamepads[i]); michael@0: } michael@0: mGamepads.Clear(); michael@0: } michael@0: michael@0: void michael@0: WindowsGamepadService::CleanupGamepad(Gamepad& gamepad) { michael@0: gamepad.device->Unacquire(); michael@0: gamepad.device->SetEventNotification(nullptr); michael@0: CloseHandle(gamepad.event); michael@0: } michael@0: michael@0: void michael@0: WindowsGamepadService::DevicesChanged(DeviceChangeType type) { michael@0: if (type == DeviceChangeNotification) { michael@0: mObserver->SetDeviceChangeTimer(); michael@0: } else if (type == DeviceChangeStable) { michael@0: SetEvent(mThreadRescanEvent); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Observer::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) { michael@0: if (strcmp(aTopic, "timer-callback") == 0) { michael@0: mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable); michael@0: } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) { michael@0: Stop(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: HWND sHWnd = nullptr; michael@0: michael@0: static michael@0: LRESULT CALLBACK michael@0: GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { michael@0: const unsigned int DBT_DEVICEARRIVAL = 0x8000; michael@0: const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004; michael@0: const unsigned int DBT_DEVNODES_CHANGED = 0x7; michael@0: michael@0: if (msg == WM_DEVICECHANGE && michael@0: (wParam == DBT_DEVICEARRIVAL || michael@0: wParam == DBT_DEVICEREMOVECOMPLETE || michael@0: wParam == DBT_DEVNODES_CHANGED)) { michael@0: if (gService) { michael@0: gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification); michael@0: } michael@0: } michael@0: return DefWindowProc(hwnd, msg, wParam, lParam); michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: namespace mozilla { michael@0: namespace hal_impl { michael@0: michael@0: void StartMonitoringGamepadStatus() michael@0: { michael@0: if (gService) michael@0: return; michael@0: michael@0: gService = new WindowsGamepadService(); michael@0: gService->Startup(); michael@0: michael@0: if (sHWnd == nullptr) { michael@0: WNDCLASSW wc; michael@0: HMODULE hSelf = GetModuleHandle(nullptr); michael@0: michael@0: if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) { michael@0: ZeroMemory(&wc, sizeof(WNDCLASSW)); michael@0: wc.hInstance = hSelf; michael@0: wc.lpfnWndProc = GamepadWindowProc; michael@0: wc.lpszClassName = L"MozillaGamepadClass"; michael@0: RegisterClassW(&wc); michael@0: } michael@0: michael@0: sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", michael@0: 0, 0, 0, 0, 0, michael@0: nullptr, nullptr, hSelf, nullptr); michael@0: } michael@0: } michael@0: michael@0: void StopMonitoringGamepadStatus() michael@0: { michael@0: if (!gService) michael@0: return; michael@0: michael@0: if (sHWnd) { michael@0: DestroyWindow(sHWnd); michael@0: sHWnd = nullptr; michael@0: } michael@0: michael@0: gService->Shutdown(); michael@0: delete gService; michael@0: gService = nullptr; michael@0: } michael@0: michael@0: } // namespace hal_impl michael@0: } // namespace mozilla michael@0: