1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/hal/windows/WindowsGamepad.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,782 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include <algorithm> 1.9 +#include <cstddef> 1.10 + 1.11 +#include <stdio.h> 1.12 +#ifndef UNICODE 1.13 +#define UNICODE 1.14 +#endif 1.15 +#include <windows.h> 1.16 +#define DIRECTINPUT_VERSION 0x0800 1.17 +#include <dinput.h> 1.18 + 1.19 +#include "nsIComponentManager.h" 1.20 +#include "nsIObserver.h" 1.21 +#include "nsIObserverService.h" 1.22 +#include "nsITimer.h" 1.23 +#include "nsTArray.h" 1.24 +#include "nsThreadUtils.h" 1.25 +#include "mozilla/dom/GamepadService.h" 1.26 +#include "mozilla/Mutex.h" 1.27 +#include "mozilla/Services.h" 1.28 + 1.29 +namespace { 1.30 + 1.31 +using mozilla::dom::GamepadService; 1.32 +using mozilla::Mutex; 1.33 +using mozilla::MutexAutoLock; 1.34 + 1.35 +const unsigned kMaxAxes = 32; 1.36 +const LONG kMaxAxisValue = 65535; 1.37 +const DWORD BUTTON_DOWN_MASK = 0x80; 1.38 +// Multiple devices-changed notifications can be sent when a device 1.39 +// is connected, because USB devices consist of multiple logical devices. 1.40 +// Therefore, we wait a bit after receiving one before looking for 1.41 +// device changes. 1.42 +const uint32_t kDevicesChangedStableDelay = 200; 1.43 + 1.44 +class WindowsGamepadService; 1.45 +WindowsGamepadService* gService = nullptr; 1.46 + 1.47 +typedef struct { 1.48 + float x,y; 1.49 +} HatState; 1.50 + 1.51 +struct Gamepad { 1.52 + // From DirectInput, unique to this device+computer combination. 1.53 + GUID guidInstance; 1.54 + // The ID assigned by the base GamepadService 1.55 + int globalID; 1.56 + // A somewhat unique string consisting of the USB vendor/product IDs, 1.57 + // and the controller name. 1.58 + char idstring[128]; 1.59 + // USB vendor and product IDs 1.60 + int vendorID; 1.61 + int productID; 1.62 + // Information about the physical device. 1.63 + DWORD axes[kMaxAxes]; 1.64 + int numAxes; 1.65 + int numHats; 1.66 + int numButtons; 1.67 + // The human-readable device name. 1.68 + char name[128]; 1.69 + // The DirectInput device. 1.70 + nsRefPtr<IDirectInputDevice8> device; 1.71 + // A handle that DirectInput signals when there is new data from 1.72 + // the device. 1.73 + HANDLE event; 1.74 + // The state of any POV hats on the device. 1.75 + HatState hatState[4]; 1.76 + // Used during rescan to find devices that were disconnected. 1.77 + bool present; 1.78 + // Passed back from the main thread to indicate a device can 1.79 + // now be removed. 1.80 + bool remove; 1.81 +}; 1.82 + 1.83 +// Given DWORD |hatPos| representing the position of the POV hat per: 1.84 +// http://msdn.microsoft.com/en-us/library/ee418260%28v=VS.85%29.aspx 1.85 +// fill |axes| with the position of the x and y axes. 1.86 +// 1.87 +//XXX: ostensibly the values could be arbitrary degrees for a hat with 1.88 +// full rotation, but we'll punt on that for now. This should handle 1.89 +// 8-way D-pads exposed as POV hats. 1.90 +static void 1.91 +HatPosToAxes(DWORD hatPos, HatState& axes) { 1.92 + // hatPos is in hundredths of a degree clockwise from north. 1.93 + if (LOWORD(hatPos) == 0xFFFF) { 1.94 + // centered 1.95 + axes.x = axes.y = 0.0; 1.96 + } 1.97 + else if (hatPos == 0) { 1.98 + // Up 1.99 + axes.x = 0.0; 1.100 + axes.y = -1.0; 1.101 + } 1.102 + else if (hatPos == 45 * DI_DEGREES) { 1.103 + // Up-right 1.104 + axes.x = 1.0; 1.105 + axes.y = -1.0; 1.106 + } 1.107 + else if (hatPos == 90 * DI_DEGREES) { 1.108 + // Right 1.109 + axes.x = 1.0; 1.110 + axes.y = 0.0; 1.111 + } 1.112 + else if (hatPos == 135 * DI_DEGREES) { 1.113 + // Down-right 1.114 + axes.x = 1.0; 1.115 + axes.y = 1.0; 1.116 + } 1.117 + else if (hatPos == 180 * DI_DEGREES) { 1.118 + // Down 1.119 + axes.x = 0.0; 1.120 + axes.y = 1.0; 1.121 + } 1.122 + else if (hatPos == 225 * DI_DEGREES) { 1.123 + // Down-left 1.124 + axes.x = -1.0; 1.125 + axes.y = 1.0; 1.126 + } 1.127 + else if (hatPos == 270 * DI_DEGREES) { 1.128 + // Left 1.129 + axes.x = -1.0; 1.130 + axes.y = 0.0; 1.131 + } 1.132 + else if (hatPos == 315 * DI_DEGREES) { 1.133 + // Up-left 1.134 + axes.x = -1.0; 1.135 + axes.y = -1.0; 1.136 + } 1.137 +} 1.138 + 1.139 +class Observer : public nsIObserver { 1.140 +public: 1.141 + NS_DECL_ISUPPORTS 1.142 + NS_DECL_NSIOBSERVER 1.143 + 1.144 + Observer(WindowsGamepadService& svc) : mSvc(svc), 1.145 + mObserving(true) { 1.146 + nsresult rv; 1.147 + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); 1.148 + nsCOMPtr<nsIObserverService> observerService = 1.149 + mozilla::services::GetObserverService(); 1.150 + observerService->AddObserver(this, 1.151 + NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, 1.152 + false); 1.153 + } 1.154 + 1.155 + void Stop() { 1.156 + if (mTimer) { 1.157 + mTimer->Cancel(); 1.158 + } 1.159 + if (mObserving) { 1.160 + nsCOMPtr<nsIObserverService> observerService = 1.161 + mozilla::services::GetObserverService(); 1.162 + observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); 1.163 + mObserving = false; 1.164 + } 1.165 + } 1.166 + 1.167 + virtual ~Observer() { 1.168 + Stop(); 1.169 + } 1.170 + 1.171 + void SetDeviceChangeTimer() { 1.172 + // set stable timer, since we will get multiple devices-changed 1.173 + // notifications at once 1.174 + if (mTimer) { 1.175 + mTimer->Cancel(); 1.176 + mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT); 1.177 + } 1.178 + } 1.179 + 1.180 +private: 1.181 + // Gamepad service owns us, we just hold a reference back to it. 1.182 + WindowsGamepadService& mSvc; 1.183 + nsCOMPtr<nsITimer> mTimer; 1.184 + bool mObserving; 1.185 +}; 1.186 + 1.187 +NS_IMPL_ISUPPORTS(Observer, nsIObserver); 1.188 + 1.189 +class WindowsGamepadService { 1.190 +public: 1.191 + WindowsGamepadService(); 1.192 + virtual ~WindowsGamepadService() { 1.193 + Cleanup(); 1.194 + CloseHandle(mThreadExitEvent); 1.195 + CloseHandle(mThreadRescanEvent); 1.196 + if (dinput) { 1.197 + dinput->Release(); 1.198 + dinput = nullptr; 1.199 + } 1.200 + } 1.201 + 1.202 + enum DeviceChangeType { 1.203 + DeviceChangeNotification, 1.204 + DeviceChangeStable 1.205 + }; 1.206 + void DevicesChanged(DeviceChangeType type); 1.207 + void Startup(); 1.208 + void Shutdown(); 1.209 + void SetGamepadID(int localID, int globalID); 1.210 + void RemoveGamepad(int localID); 1.211 + 1.212 +private: 1.213 + void ScanForDevices(); 1.214 + void Cleanup(); 1.215 + void CleanupGamepad(Gamepad& gamepad); 1.216 + // Callback for enumerating axes on a device 1.217 + static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, 1.218 + LPVOID pvRef); 1.219 + // Callback for enumerating devices via DInput 1.220 + static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); 1.221 + // Thread function to wait on device events 1.222 + static DWORD WINAPI DInputThread(LPVOID arg); 1.223 + 1.224 + // Used to signal the background thread to exit. 1.225 + HANDLE mThreadExitEvent; 1.226 + // Used to signal the background thread to rescan devices. 1.227 + HANDLE mThreadRescanEvent; 1.228 + HANDLE mThread; 1.229 + 1.230 + // List of connected devices. 1.231 + nsTArray<Gamepad> mGamepads; 1.232 + // Used to lock mutation of mGamepads. 1.233 + Mutex mMutex; 1.234 + // List of event handles used for signaling. 1.235 + nsTArray<HANDLE> mEvents; 1.236 + 1.237 + LPDIRECTINPUT8 dinput; 1.238 + 1.239 + nsRefPtr<Observer> mObserver; 1.240 +}; 1.241 + 1.242 +// Used to post events from the background thread to the foreground thread. 1.243 +class GamepadEvent : public nsRunnable { 1.244 +public: 1.245 + typedef enum { 1.246 + Axis, 1.247 + Button, 1.248 + HatX, 1.249 + HatY, 1.250 + HatXY, 1.251 + Unknown 1.252 + } Type; 1.253 + 1.254 + GamepadEvent(const Gamepad& gamepad, 1.255 + Type type, 1.256 + int which, 1.257 + DWORD data) : mGlobalID(gamepad.globalID), 1.258 + mGamepadAxes(gamepad.numAxes), 1.259 + mType(type), 1.260 + mWhich(which), 1.261 + mData(data) { 1.262 + } 1.263 + 1.264 + NS_IMETHOD Run() { 1.265 + nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService()); 1.266 + if (!gamepadsvc) { 1.267 + return NS_OK; 1.268 + } 1.269 + 1.270 + switch (mType) { 1.271 + case Button: 1.272 + gamepadsvc->NewButtonEvent(mGlobalID, mWhich, mData & BUTTON_DOWN_MASK); 1.273 + break; 1.274 + case Axis: { 1.275 + float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f; 1.276 + gamepadsvc->NewAxisMoveEvent(mGlobalID, mWhich, adjustedData); 1.277 + } 1.278 + case HatX: 1.279 + case HatY: 1.280 + case HatXY: { 1.281 + // Synthesize 2 axes per POV hat for convenience. 1.282 + HatState hatState; 1.283 + HatPosToAxes(mData, hatState); 1.284 + int xAxis = mGamepadAxes + 2 * mWhich; 1.285 + int yAxis = mGamepadAxes + 2 * mWhich + 1; 1.286 + //TODO: ostensibly we could not fire an event if one axis hasn't 1.287 + // changed, but it's a pain to track that. 1.288 + if (mType == HatX || mType == HatXY) { 1.289 + gamepadsvc->NewAxisMoveEvent(mGlobalID, xAxis, hatState.x); 1.290 + } 1.291 + if (mType == HatY || mType == HatXY) { 1.292 + gamepadsvc->NewAxisMoveEvent(mGlobalID, yAxis, hatState.y); 1.293 + } 1.294 + break; 1.295 + } 1.296 + case Unknown: 1.297 + break; 1.298 + } 1.299 + return NS_OK; 1.300 + } 1.301 + 1.302 + int mGlobalID; 1.303 + int mGamepadAxes; 1.304 + // Type of event 1.305 + Type mType; 1.306 + // Which button/axis is involved 1.307 + int mWhich; 1.308 + // Data specific to event 1.309 + DWORD mData; 1.310 +}; 1.311 + 1.312 +class GamepadChangeEvent : public nsRunnable { 1.313 +public: 1.314 + enum Type { 1.315 + Added, 1.316 + Removed 1.317 + }; 1.318 + GamepadChangeEvent(Gamepad& gamepad, 1.319 + int localID, 1.320 + Type type) : mLocalID(localID), 1.321 + mName(gamepad.idstring), 1.322 + mGlobalID(gamepad.globalID), 1.323 + mGamepadButtons(gamepad.numButtons), 1.324 + mGamepadAxes(gamepad.numAxes), 1.325 + mGamepadHats(gamepad.numHats), 1.326 + mType(type) { 1.327 + } 1.328 + 1.329 + NS_IMETHOD Run() { 1.330 + nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService()); 1.331 + if (!gamepadsvc) { 1.332 + return NS_OK; 1.333 + } 1.334 + if (mType == Added) { 1.335 + int globalID = gamepadsvc->AddGamepad(mName.get(), 1.336 + mozilla::dom::NoMapping, 1.337 + mGamepadButtons, 1.338 + mGamepadAxes + 1.339 + mGamepadHats*2); 1.340 + if (gService) { 1.341 + gService->SetGamepadID(mLocalID, globalID); 1.342 + } 1.343 + } else { 1.344 + gamepadsvc->RemoveGamepad(mGlobalID); 1.345 + if (gService) { 1.346 + gService->RemoveGamepad(mLocalID); 1.347 + } 1.348 + } 1.349 + return NS_OK; 1.350 + } 1.351 + 1.352 +private: 1.353 + // ID in WindowsGamepadService::mGamepads 1.354 + int mLocalID; 1.355 + nsCString mName; 1.356 + int mGamepadButtons; 1.357 + int mGamepadAxes; 1.358 + int mGamepadHats; 1.359 + // ID from GamepadService 1.360 + uint32_t mGlobalID; 1.361 + Type mType; 1.362 +}; 1.363 + 1.364 +WindowsGamepadService::WindowsGamepadService() 1.365 + : mThreadExitEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)), 1.366 + mThreadRescanEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)), 1.367 + mThread(nullptr), 1.368 + mMutex("Windows Gamepad Service"), 1.369 + dinput(nullptr) { 1.370 + mObserver = new Observer(*this); 1.371 + // Initialize DirectInput 1.372 + CoInitialize(nullptr); 1.373 + if (CoCreateInstance(CLSID_DirectInput8, 1.374 + nullptr, 1.375 + CLSCTX_INPROC_SERVER, 1.376 + IID_IDirectInput8W, 1.377 + (LPVOID*)&dinput) == S_OK) { 1.378 + if (dinput->Initialize(GetModuleHandle(nullptr), 1.379 + DIRECTINPUT_VERSION) != DI_OK) { 1.380 + dinput->Release(); 1.381 + dinput = nullptr; 1.382 + } 1.383 + } 1.384 +} 1.385 + 1.386 +// static 1.387 +BOOL CALLBACK 1.388 +WindowsGamepadService::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, 1.389 + LPVOID pvRef) { 1.390 + // Ensure that all axes are using the same range. 1.391 + Gamepad* gamepad = reinterpret_cast<Gamepad*>(pvRef); 1.392 + DIPROPRANGE dp; 1.393 + dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); 1.394 + dp.diph.dwSize = sizeof(DIPROPRANGE); 1.395 + dp.diph.dwHow = DIPH_BYID; 1.396 + dp.diph.dwObj = lpddoi->dwType; 1.397 + dp.lMin = 0; 1.398 + dp.lMax = kMaxAxisValue; 1.399 + gamepad->device->SetProperty(DIPROP_RANGE, &dp.diph); 1.400 + // Find what the dwOfs of this object in the c_dfDIJoystick data format is. 1.401 + for (DWORD i = 0; i < c_dfDIJoystick.dwNumObjs; i++) { 1.402 + if (c_dfDIJoystick.rgodf[i].pguid && 1.403 + IsEqualGUID(*c_dfDIJoystick.rgodf[i].pguid, lpddoi->guidType) && 1.404 + gamepad->numAxes < kMaxAxes) { 1.405 + gamepad->axes[gamepad->numAxes] = c_dfDIJoystick.rgodf[i].dwOfs; 1.406 + gamepad->numAxes++; 1.407 + break; 1.408 + } 1.409 + } 1.410 + return DIENUM_CONTINUE; 1.411 +} 1.412 + 1.413 +// static 1.414 +BOOL CALLBACK 1.415 +WindowsGamepadService::EnumCallback(LPCDIDEVICEINSTANCE lpddi, 1.416 + LPVOID pvRef) { 1.417 + WindowsGamepadService* self = 1.418 + reinterpret_cast<WindowsGamepadService*>(pvRef); 1.419 + // See if this device is already present in our list. 1.420 + { 1.421 + MutexAutoLock lock(self->mMutex); 1.422 + for (unsigned int i = 0; i < self->mGamepads.Length(); i++) { 1.423 + if (memcmp(&lpddi->guidInstance, &self->mGamepads[i].guidInstance, 1.424 + sizeof(GUID)) == 0) { 1.425 + self->mGamepads[i].present = true; 1.426 + return DIENUM_CONTINUE; 1.427 + } 1.428 + } 1.429 + } 1.430 + 1.431 + Gamepad gamepad = {}; 1.432 + if (self->dinput->CreateDevice(lpddi->guidInstance, 1.433 + getter_AddRefs(gamepad.device), 1.434 + nullptr) 1.435 + == DI_OK) { 1.436 + gamepad.present = true; 1.437 + memcpy(&gamepad.guidInstance, &lpddi->guidInstance, sizeof(GUID)); 1.438 + 1.439 + DIDEVICEINSTANCE info; 1.440 + info.dwSize = sizeof(DIDEVICEINSTANCE); 1.441 + if (gamepad.device->GetDeviceInfo(&info) == DI_OK) { 1.442 + WideCharToMultiByte(CP_UTF8, 0, info.tszProductName, -1, 1.443 + gamepad.name, sizeof(gamepad.name), nullptr, nullptr); 1.444 + } 1.445 + // Get vendor id and product id 1.446 + DIPROPDWORD dp; 1.447 + dp.diph.dwSize = sizeof(DIPROPDWORD); 1.448 + dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); 1.449 + dp.diph.dwObj = 0; 1.450 + dp.diph.dwHow = DIPH_DEVICE; 1.451 + if (gamepad.device->GetProperty(DIPROP_VIDPID, &dp.diph) == DI_OK) { 1.452 + sprintf(gamepad.idstring, "%x-%x-%s", 1.453 + LOWORD(dp.dwData), HIWORD(dp.dwData), gamepad.name); 1.454 + } 1.455 + DIDEVCAPS caps; 1.456 + caps.dwSize = sizeof(DIDEVCAPS); 1.457 + if (gamepad.device->GetCapabilities(&caps) == DI_OK) { 1.458 + gamepad.numHats = caps.dwPOVs; 1.459 + gamepad.numButtons = caps.dwButtons; 1.460 + //XXX: handle polled devices? 1.461 + // (caps.dwFlags & DIDC_POLLEDDATAFORMAT || caps.dwFlags & DIDC_POLLEDDEVICE) 1.462 + } 1.463 + // Set min/max range for all axes on the device. 1.464 + // Axes will be gathered in EnumObjectsCallback. 1.465 + gamepad.numAxes = 0; 1.466 + gamepad.device->EnumObjects(EnumObjectsCallback, &gamepad, DIDFT_AXIS); 1.467 + // Set up structure for setting buffer size for buffered data 1.468 + dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); 1.469 + dp.diph.dwSize = sizeof(DIPROPDWORD); 1.470 + dp.diph.dwObj = 0; 1.471 + dp.diph.dwHow = DIPH_DEVICE; 1.472 + dp.dwData = 64; // arbitrary 1.473 + // Create event so DInput can signal us when there's new data. 1.474 + gamepad.event = CreateEventW(nullptr, FALSE, FALSE, nullptr); 1.475 + // Set data format, event notification, and acquire device 1.476 + if (gamepad.device->SetDataFormat(&c_dfDIJoystick) == DI_OK && 1.477 + gamepad.device->SetProperty(DIPROP_BUFFERSIZE, &dp.diph) == DI_OK && 1.478 + gamepad.device->SetEventNotification(gamepad.event) == DI_OK && 1.479 + gamepad.device->Acquire() == DI_OK) { 1.480 + MutexAutoLock lock(self->mMutex); 1.481 + self->mGamepads.AppendElement(gamepad); 1.482 + // Inform the GamepadService 1.483 + int localID = self->mGamepads.Length() - 1; 1.484 + nsRefPtr<GamepadChangeEvent> event = 1.485 + new GamepadChangeEvent(self->mGamepads[localID], 1.486 + localID, 1.487 + GamepadChangeEvent::Added); 1.488 + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 1.489 + } 1.490 + else { 1.491 + if (gamepad.device) { 1.492 + gamepad.device->SetEventNotification(nullptr); 1.493 + } 1.494 + CloseHandle(gamepad.event); 1.495 + } 1.496 + } 1.497 + return DIENUM_CONTINUE; 1.498 +} 1.499 + 1.500 +void 1.501 +WindowsGamepadService::ScanForDevices() { 1.502 + { 1.503 + MutexAutoLock lock(mMutex); 1.504 + for (int i = mGamepads.Length() - 1; i >= 0; i--) { 1.505 + if (mGamepads[i].remove) { 1.506 + 1.507 + // Main thread has already handled this, safe to remove. 1.508 + CleanupGamepad(mGamepads[i]); 1.509 + mGamepads.RemoveElementAt(i); 1.510 + } else { 1.511 + mGamepads[i].present = false; 1.512 + } 1.513 + } 1.514 + } 1.515 + 1.516 + dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, 1.517 + (LPDIENUMDEVICESCALLBACK)EnumCallback, 1.518 + this, 1.519 + DIEDFL_ATTACHEDONLY); 1.520 + 1.521 + // Look for devices that are no longer present and inform the main thread. 1.522 + { 1.523 + MutexAutoLock lock(mMutex); 1.524 + for (int i = mGamepads.Length() - 1; i >= 0; i--) { 1.525 + if (!mGamepads[i].present) { 1.526 + nsRefPtr<GamepadChangeEvent> event = 1.527 + new GamepadChangeEvent(mGamepads[i], 1.528 + i, 1.529 + GamepadChangeEvent::Removed); 1.530 + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 1.531 + } 1.532 + } 1.533 + 1.534 + mEvents.Clear(); 1.535 + for (unsigned int i = 0; i < mGamepads.Length(); i++) { 1.536 + mEvents.AppendElement(mGamepads[i].event); 1.537 + } 1.538 + } 1.539 + 1.540 + 1.541 + // These events must be the last elements in the array, so that 1.542 + // the other elements match mGamepads in order. 1.543 + mEvents.AppendElement(mThreadRescanEvent); 1.544 + mEvents.AppendElement(mThreadExitEvent); 1.545 +} 1.546 + 1.547 +// static 1.548 +DWORD WINAPI 1.549 +WindowsGamepadService::DInputThread(LPVOID arg) { 1.550 + WindowsGamepadService* self = reinterpret_cast<WindowsGamepadService*>(arg); 1.551 + self->ScanForDevices(); 1.552 + 1.553 + while (true) { 1.554 + DWORD result = WaitForMultipleObjects(self->mEvents.Length(), 1.555 + self->mEvents.Elements(), 1.556 + FALSE, 1.557 + INFINITE); 1.558 + if (result == WAIT_FAILED || 1.559 + result == WAIT_OBJECT_0 + self->mEvents.Length() - 1) { 1.560 + // error, or the main thread signaled us to exit 1.561 + break; 1.562 + } 1.563 + 1.564 + unsigned int i = result - WAIT_OBJECT_0; 1.565 + 1.566 + if (i == self->mEvents.Length() - 2) { 1.567 + // Main thread is signaling for a device rescan. 1.568 + self->ScanForDevices(); 1.569 + continue; 1.570 + } 1.571 + 1.572 + { 1.573 + MutexAutoLock lock(self->mMutex); 1.574 + if (i >= self->mGamepads.Length()) { 1.575 + // Something would be terribly wrong here, possibly we got 1.576 + // a WAIT_ABANDONED_x result. 1.577 + continue; 1.578 + } 1.579 + 1.580 + // first query for the number of items in the buffer 1.581 + DWORD items = INFINITE; 1.582 + nsRefPtr<IDirectInputDevice8> device = self->mGamepads[i].device; 1.583 + if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), 1.584 + nullptr, 1.585 + &items, 1.586 + DIGDD_PEEK)== DI_OK) { 1.587 + while (items > 0) { 1.588 + // now read each buffered event 1.589 + //TODO: read more than one event at a time 1.590 + DIDEVICEOBJECTDATA data; 1.591 + DWORD readCount = sizeof(data) / sizeof(DIDEVICEOBJECTDATA); 1.592 + if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), 1.593 + &data, &readCount, 0) == DI_OK) { 1.594 + //TODO: data.dwTimeStamp 1.595 + GamepadEvent::Type type = GamepadEvent::Unknown; 1.596 + int which = -1; 1.597 + if (data.dwOfs >= DIJOFS_BUTTON0 && data.dwOfs < DIJOFS_BUTTON(32)) { 1.598 + type = GamepadEvent::Button; 1.599 + which = data.dwOfs - DIJOFS_BUTTON0; 1.600 + } 1.601 + else if(data.dwOfs >= DIJOFS_X && data.dwOfs < DIJOFS_SLIDER(2)) { 1.602 + // axis/slider 1.603 + type = GamepadEvent::Axis; 1.604 + for (int a = 0; a < self->mGamepads[i].numAxes; a++) { 1.605 + if (self->mGamepads[i].axes[a] == data.dwOfs) { 1.606 + which = a; 1.607 + break; 1.608 + } 1.609 + } 1.610 + } 1.611 + else if (data.dwOfs >= DIJOFS_POV(0) && data.dwOfs < DIJOFS_POV(4)) { 1.612 + HatState hatState; 1.613 + HatPosToAxes(data.dwData, hatState); 1.614 + which = (data.dwOfs - DIJOFS_POV(0)) / sizeof(DWORD); 1.615 + // Only send out axis move events for the axes that moved 1.616 + // in this hat move. 1.617 + if (hatState.x != self->mGamepads[i].hatState[which].x) { 1.618 + type = GamepadEvent::HatX; 1.619 + } 1.620 + if (hatState.y != self->mGamepads[i].hatState[which].y) { 1.621 + if (type == GamepadEvent::HatX) { 1.622 + type = GamepadEvent::HatXY; 1.623 + } 1.624 + else { 1.625 + type = GamepadEvent::HatY; 1.626 + } 1.627 + } 1.628 + self->mGamepads[i].hatState[which].x = hatState.x; 1.629 + self->mGamepads[i].hatState[which].y = hatState.y; 1.630 + } 1.631 + 1.632 + if (type != GamepadEvent::Unknown && which != -1) { 1.633 + nsRefPtr<GamepadEvent> event = 1.634 + new GamepadEvent(self->mGamepads[i], type, which, data.dwData); 1.635 + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 1.636 + } 1.637 + } 1.638 + items--; 1.639 + } 1.640 + } 1.641 + } 1.642 + } 1.643 + return 0; 1.644 +} 1.645 + 1.646 +void 1.647 +WindowsGamepadService::Startup() { 1.648 + mThread = CreateThread(nullptr, 1.649 + 0, 1.650 + DInputThread, 1.651 + this, 1.652 + 0, 1.653 + nullptr); 1.654 +} 1.655 + 1.656 +void 1.657 +WindowsGamepadService::Shutdown() { 1.658 + if (mThread) { 1.659 + SetEvent(mThreadExitEvent); 1.660 + WaitForSingleObject(mThread, INFINITE); 1.661 + CloseHandle(mThread); 1.662 + } 1.663 + Cleanup(); 1.664 +} 1.665 + 1.666 +// This method is called from the main thread. 1.667 +void 1.668 +WindowsGamepadService::SetGamepadID(int localID, int globalID) { 1.669 + MutexAutoLock lock(mMutex); 1.670 + mGamepads[localID].globalID = globalID; 1.671 +} 1.672 + 1.673 +// This method is called from the main thread. 1.674 +void WindowsGamepadService::RemoveGamepad(int localID) { 1.675 + MutexAutoLock lock(mMutex); 1.676 + mGamepads[localID].remove = true; 1.677 + // Signal background thread to remove device. 1.678 + DevicesChanged(DeviceChangeStable); 1.679 +} 1.680 + 1.681 +void 1.682 +WindowsGamepadService::Cleanup() { 1.683 + for (unsigned int i = 0; i < mGamepads.Length(); i++) { 1.684 + CleanupGamepad(mGamepads[i]); 1.685 + } 1.686 + mGamepads.Clear(); 1.687 +} 1.688 + 1.689 +void 1.690 +WindowsGamepadService::CleanupGamepad(Gamepad& gamepad) { 1.691 + gamepad.device->Unacquire(); 1.692 + gamepad.device->SetEventNotification(nullptr); 1.693 + CloseHandle(gamepad.event); 1.694 +} 1.695 + 1.696 +void 1.697 +WindowsGamepadService::DevicesChanged(DeviceChangeType type) { 1.698 + if (type == DeviceChangeNotification) { 1.699 + mObserver->SetDeviceChangeTimer(); 1.700 + } else if (type == DeviceChangeStable) { 1.701 + SetEvent(mThreadRescanEvent); 1.702 + } 1.703 +} 1.704 + 1.705 +NS_IMETHODIMP 1.706 +Observer::Observe(nsISupports* aSubject, 1.707 + const char* aTopic, 1.708 + const char16_t* aData) { 1.709 + if (strcmp(aTopic, "timer-callback") == 0) { 1.710 + mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable); 1.711 + } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) { 1.712 + Stop(); 1.713 + } 1.714 + return NS_OK; 1.715 +} 1.716 + 1.717 +HWND sHWnd = nullptr; 1.718 + 1.719 +static 1.720 +LRESULT CALLBACK 1.721 +GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 1.722 + const unsigned int DBT_DEVICEARRIVAL = 0x8000; 1.723 + const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004; 1.724 + const unsigned int DBT_DEVNODES_CHANGED = 0x7; 1.725 + 1.726 + if (msg == WM_DEVICECHANGE && 1.727 + (wParam == DBT_DEVICEARRIVAL || 1.728 + wParam == DBT_DEVICEREMOVECOMPLETE || 1.729 + wParam == DBT_DEVNODES_CHANGED)) { 1.730 + if (gService) { 1.731 + gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification); 1.732 + } 1.733 + } 1.734 + return DefWindowProc(hwnd, msg, wParam, lParam); 1.735 +} 1.736 + 1.737 +} // namespace 1.738 + 1.739 +namespace mozilla { 1.740 +namespace hal_impl { 1.741 + 1.742 +void StartMonitoringGamepadStatus() 1.743 +{ 1.744 + if (gService) 1.745 + return; 1.746 + 1.747 + gService = new WindowsGamepadService(); 1.748 + gService->Startup(); 1.749 + 1.750 + if (sHWnd == nullptr) { 1.751 + WNDCLASSW wc; 1.752 + HMODULE hSelf = GetModuleHandle(nullptr); 1.753 + 1.754 + if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) { 1.755 + ZeroMemory(&wc, sizeof(WNDCLASSW)); 1.756 + wc.hInstance = hSelf; 1.757 + wc.lpfnWndProc = GamepadWindowProc; 1.758 + wc.lpszClassName = L"MozillaGamepadClass"; 1.759 + RegisterClassW(&wc); 1.760 + } 1.761 + 1.762 + sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", 1.763 + 0, 0, 0, 0, 0, 1.764 + nullptr, nullptr, hSelf, nullptr); 1.765 + } 1.766 +} 1.767 + 1.768 +void StopMonitoringGamepadStatus() 1.769 +{ 1.770 + if (!gService) 1.771 + return; 1.772 + 1.773 + if (sHWnd) { 1.774 + DestroyWindow(sHWnd); 1.775 + sHWnd = nullptr; 1.776 + } 1.777 + 1.778 + gService->Shutdown(); 1.779 + delete gService; 1.780 + gService = nullptr; 1.781 +} 1.782 + 1.783 +} // namespace hal_impl 1.784 +} // namespace mozilla 1.785 +