hal/windows/WindowsGamepad.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include <algorithm>
michael@0 6 #include <cstddef>
michael@0 7
michael@0 8 #include <stdio.h>
michael@0 9 #ifndef UNICODE
michael@0 10 #define UNICODE
michael@0 11 #endif
michael@0 12 #include <windows.h>
michael@0 13 #define DIRECTINPUT_VERSION 0x0800
michael@0 14 #include <dinput.h>
michael@0 15
michael@0 16 #include "nsIComponentManager.h"
michael@0 17 #include "nsIObserver.h"
michael@0 18 #include "nsIObserverService.h"
michael@0 19 #include "nsITimer.h"
michael@0 20 #include "nsTArray.h"
michael@0 21 #include "nsThreadUtils.h"
michael@0 22 #include "mozilla/dom/GamepadService.h"
michael@0 23 #include "mozilla/Mutex.h"
michael@0 24 #include "mozilla/Services.h"
michael@0 25
michael@0 26 namespace {
michael@0 27
michael@0 28 using mozilla::dom::GamepadService;
michael@0 29 using mozilla::Mutex;
michael@0 30 using mozilla::MutexAutoLock;
michael@0 31
michael@0 32 const unsigned kMaxAxes = 32;
michael@0 33 const LONG kMaxAxisValue = 65535;
michael@0 34 const DWORD BUTTON_DOWN_MASK = 0x80;
michael@0 35 // Multiple devices-changed notifications can be sent when a device
michael@0 36 // is connected, because USB devices consist of multiple logical devices.
michael@0 37 // Therefore, we wait a bit after receiving one before looking for
michael@0 38 // device changes.
michael@0 39 const uint32_t kDevicesChangedStableDelay = 200;
michael@0 40
michael@0 41 class WindowsGamepadService;
michael@0 42 WindowsGamepadService* gService = nullptr;
michael@0 43
michael@0 44 typedef struct {
michael@0 45 float x,y;
michael@0 46 } HatState;
michael@0 47
michael@0 48 struct Gamepad {
michael@0 49 // From DirectInput, unique to this device+computer combination.
michael@0 50 GUID guidInstance;
michael@0 51 // The ID assigned by the base GamepadService
michael@0 52 int globalID;
michael@0 53 // A somewhat unique string consisting of the USB vendor/product IDs,
michael@0 54 // and the controller name.
michael@0 55 char idstring[128];
michael@0 56 // USB vendor and product IDs
michael@0 57 int vendorID;
michael@0 58 int productID;
michael@0 59 // Information about the physical device.
michael@0 60 DWORD axes[kMaxAxes];
michael@0 61 int numAxes;
michael@0 62 int numHats;
michael@0 63 int numButtons;
michael@0 64 // The human-readable device name.
michael@0 65 char name[128];
michael@0 66 // The DirectInput device.
michael@0 67 nsRefPtr<IDirectInputDevice8> device;
michael@0 68 // A handle that DirectInput signals when there is new data from
michael@0 69 // the device.
michael@0 70 HANDLE event;
michael@0 71 // The state of any POV hats on the device.
michael@0 72 HatState hatState[4];
michael@0 73 // Used during rescan to find devices that were disconnected.
michael@0 74 bool present;
michael@0 75 // Passed back from the main thread to indicate a device can
michael@0 76 // now be removed.
michael@0 77 bool remove;
michael@0 78 };
michael@0 79
michael@0 80 // Given DWORD |hatPos| representing the position of the POV hat per:
michael@0 81 // http://msdn.microsoft.com/en-us/library/ee418260%28v=VS.85%29.aspx
michael@0 82 // fill |axes| with the position of the x and y axes.
michael@0 83 //
michael@0 84 //XXX: ostensibly the values could be arbitrary degrees for a hat with
michael@0 85 // full rotation, but we'll punt on that for now. This should handle
michael@0 86 // 8-way D-pads exposed as POV hats.
michael@0 87 static void
michael@0 88 HatPosToAxes(DWORD hatPos, HatState& axes) {
michael@0 89 // hatPos is in hundredths of a degree clockwise from north.
michael@0 90 if (LOWORD(hatPos) == 0xFFFF) {
michael@0 91 // centered
michael@0 92 axes.x = axes.y = 0.0;
michael@0 93 }
michael@0 94 else if (hatPos == 0) {
michael@0 95 // Up
michael@0 96 axes.x = 0.0;
michael@0 97 axes.y = -1.0;
michael@0 98 }
michael@0 99 else if (hatPos == 45 * DI_DEGREES) {
michael@0 100 // Up-right
michael@0 101 axes.x = 1.0;
michael@0 102 axes.y = -1.0;
michael@0 103 }
michael@0 104 else if (hatPos == 90 * DI_DEGREES) {
michael@0 105 // Right
michael@0 106 axes.x = 1.0;
michael@0 107 axes.y = 0.0;
michael@0 108 }
michael@0 109 else if (hatPos == 135 * DI_DEGREES) {
michael@0 110 // Down-right
michael@0 111 axes.x = 1.0;
michael@0 112 axes.y = 1.0;
michael@0 113 }
michael@0 114 else if (hatPos == 180 * DI_DEGREES) {
michael@0 115 // Down
michael@0 116 axes.x = 0.0;
michael@0 117 axes.y = 1.0;
michael@0 118 }
michael@0 119 else if (hatPos == 225 * DI_DEGREES) {
michael@0 120 // Down-left
michael@0 121 axes.x = -1.0;
michael@0 122 axes.y = 1.0;
michael@0 123 }
michael@0 124 else if (hatPos == 270 * DI_DEGREES) {
michael@0 125 // Left
michael@0 126 axes.x = -1.0;
michael@0 127 axes.y = 0.0;
michael@0 128 }
michael@0 129 else if (hatPos == 315 * DI_DEGREES) {
michael@0 130 // Up-left
michael@0 131 axes.x = -1.0;
michael@0 132 axes.y = -1.0;
michael@0 133 }
michael@0 134 }
michael@0 135
michael@0 136 class Observer : public nsIObserver {
michael@0 137 public:
michael@0 138 NS_DECL_ISUPPORTS
michael@0 139 NS_DECL_NSIOBSERVER
michael@0 140
michael@0 141 Observer(WindowsGamepadService& svc) : mSvc(svc),
michael@0 142 mObserving(true) {
michael@0 143 nsresult rv;
michael@0 144 mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
michael@0 145 nsCOMPtr<nsIObserverService> observerService =
michael@0 146 mozilla::services::GetObserverService();
michael@0 147 observerService->AddObserver(this,
michael@0 148 NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
michael@0 149 false);
michael@0 150 }
michael@0 151
michael@0 152 void Stop() {
michael@0 153 if (mTimer) {
michael@0 154 mTimer->Cancel();
michael@0 155 }
michael@0 156 if (mObserving) {
michael@0 157 nsCOMPtr<nsIObserverService> observerService =
michael@0 158 mozilla::services::GetObserverService();
michael@0 159 observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
michael@0 160 mObserving = false;
michael@0 161 }
michael@0 162 }
michael@0 163
michael@0 164 virtual ~Observer() {
michael@0 165 Stop();
michael@0 166 }
michael@0 167
michael@0 168 void SetDeviceChangeTimer() {
michael@0 169 // set stable timer, since we will get multiple devices-changed
michael@0 170 // notifications at once
michael@0 171 if (mTimer) {
michael@0 172 mTimer->Cancel();
michael@0 173 mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT);
michael@0 174 }
michael@0 175 }
michael@0 176
michael@0 177 private:
michael@0 178 // Gamepad service owns us, we just hold a reference back to it.
michael@0 179 WindowsGamepadService& mSvc;
michael@0 180 nsCOMPtr<nsITimer> mTimer;
michael@0 181 bool mObserving;
michael@0 182 };
michael@0 183
michael@0 184 NS_IMPL_ISUPPORTS(Observer, nsIObserver);
michael@0 185
michael@0 186 class WindowsGamepadService {
michael@0 187 public:
michael@0 188 WindowsGamepadService();
michael@0 189 virtual ~WindowsGamepadService() {
michael@0 190 Cleanup();
michael@0 191 CloseHandle(mThreadExitEvent);
michael@0 192 CloseHandle(mThreadRescanEvent);
michael@0 193 if (dinput) {
michael@0 194 dinput->Release();
michael@0 195 dinput = nullptr;
michael@0 196 }
michael@0 197 }
michael@0 198
michael@0 199 enum DeviceChangeType {
michael@0 200 DeviceChangeNotification,
michael@0 201 DeviceChangeStable
michael@0 202 };
michael@0 203 void DevicesChanged(DeviceChangeType type);
michael@0 204 void Startup();
michael@0 205 void Shutdown();
michael@0 206 void SetGamepadID(int localID, int globalID);
michael@0 207 void RemoveGamepad(int localID);
michael@0 208
michael@0 209 private:
michael@0 210 void ScanForDevices();
michael@0 211 void Cleanup();
michael@0 212 void CleanupGamepad(Gamepad& gamepad);
michael@0 213 // Callback for enumerating axes on a device
michael@0 214 static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi,
michael@0 215 LPVOID pvRef);
michael@0 216 // Callback for enumerating devices via DInput
michael@0 217 static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef);
michael@0 218 // Thread function to wait on device events
michael@0 219 static DWORD WINAPI DInputThread(LPVOID arg);
michael@0 220
michael@0 221 // Used to signal the background thread to exit.
michael@0 222 HANDLE mThreadExitEvent;
michael@0 223 // Used to signal the background thread to rescan devices.
michael@0 224 HANDLE mThreadRescanEvent;
michael@0 225 HANDLE mThread;
michael@0 226
michael@0 227 // List of connected devices.
michael@0 228 nsTArray<Gamepad> mGamepads;
michael@0 229 // Used to lock mutation of mGamepads.
michael@0 230 Mutex mMutex;
michael@0 231 // List of event handles used for signaling.
michael@0 232 nsTArray<HANDLE> mEvents;
michael@0 233
michael@0 234 LPDIRECTINPUT8 dinput;
michael@0 235
michael@0 236 nsRefPtr<Observer> mObserver;
michael@0 237 };
michael@0 238
michael@0 239 // Used to post events from the background thread to the foreground thread.
michael@0 240 class GamepadEvent : public nsRunnable {
michael@0 241 public:
michael@0 242 typedef enum {
michael@0 243 Axis,
michael@0 244 Button,
michael@0 245 HatX,
michael@0 246 HatY,
michael@0 247 HatXY,
michael@0 248 Unknown
michael@0 249 } Type;
michael@0 250
michael@0 251 GamepadEvent(const Gamepad& gamepad,
michael@0 252 Type type,
michael@0 253 int which,
michael@0 254 DWORD data) : mGlobalID(gamepad.globalID),
michael@0 255 mGamepadAxes(gamepad.numAxes),
michael@0 256 mType(type),
michael@0 257 mWhich(which),
michael@0 258 mData(data) {
michael@0 259 }
michael@0 260
michael@0 261 NS_IMETHOD Run() {
michael@0 262 nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
michael@0 263 if (!gamepadsvc) {
michael@0 264 return NS_OK;
michael@0 265 }
michael@0 266
michael@0 267 switch (mType) {
michael@0 268 case Button:
michael@0 269 gamepadsvc->NewButtonEvent(mGlobalID, mWhich, mData & BUTTON_DOWN_MASK);
michael@0 270 break;
michael@0 271 case Axis: {
michael@0 272 float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f;
michael@0 273 gamepadsvc->NewAxisMoveEvent(mGlobalID, mWhich, adjustedData);
michael@0 274 }
michael@0 275 case HatX:
michael@0 276 case HatY:
michael@0 277 case HatXY: {
michael@0 278 // Synthesize 2 axes per POV hat for convenience.
michael@0 279 HatState hatState;
michael@0 280 HatPosToAxes(mData, hatState);
michael@0 281 int xAxis = mGamepadAxes + 2 * mWhich;
michael@0 282 int yAxis = mGamepadAxes + 2 * mWhich + 1;
michael@0 283 //TODO: ostensibly we could not fire an event if one axis hasn't
michael@0 284 // changed, but it's a pain to track that.
michael@0 285 if (mType == HatX || mType == HatXY) {
michael@0 286 gamepadsvc->NewAxisMoveEvent(mGlobalID, xAxis, hatState.x);
michael@0 287 }
michael@0 288 if (mType == HatY || mType == HatXY) {
michael@0 289 gamepadsvc->NewAxisMoveEvent(mGlobalID, yAxis, hatState.y);
michael@0 290 }
michael@0 291 break;
michael@0 292 }
michael@0 293 case Unknown:
michael@0 294 break;
michael@0 295 }
michael@0 296 return NS_OK;
michael@0 297 }
michael@0 298
michael@0 299 int mGlobalID;
michael@0 300 int mGamepadAxes;
michael@0 301 // Type of event
michael@0 302 Type mType;
michael@0 303 // Which button/axis is involved
michael@0 304 int mWhich;
michael@0 305 // Data specific to event
michael@0 306 DWORD mData;
michael@0 307 };
michael@0 308
michael@0 309 class GamepadChangeEvent : public nsRunnable {
michael@0 310 public:
michael@0 311 enum Type {
michael@0 312 Added,
michael@0 313 Removed
michael@0 314 };
michael@0 315 GamepadChangeEvent(Gamepad& gamepad,
michael@0 316 int localID,
michael@0 317 Type type) : mLocalID(localID),
michael@0 318 mName(gamepad.idstring),
michael@0 319 mGlobalID(gamepad.globalID),
michael@0 320 mGamepadButtons(gamepad.numButtons),
michael@0 321 mGamepadAxes(gamepad.numAxes),
michael@0 322 mGamepadHats(gamepad.numHats),
michael@0 323 mType(type) {
michael@0 324 }
michael@0 325
michael@0 326 NS_IMETHOD Run() {
michael@0 327 nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
michael@0 328 if (!gamepadsvc) {
michael@0 329 return NS_OK;
michael@0 330 }
michael@0 331 if (mType == Added) {
michael@0 332 int globalID = gamepadsvc->AddGamepad(mName.get(),
michael@0 333 mozilla::dom::NoMapping,
michael@0 334 mGamepadButtons,
michael@0 335 mGamepadAxes +
michael@0 336 mGamepadHats*2);
michael@0 337 if (gService) {
michael@0 338 gService->SetGamepadID(mLocalID, globalID);
michael@0 339 }
michael@0 340 } else {
michael@0 341 gamepadsvc->RemoveGamepad(mGlobalID);
michael@0 342 if (gService) {
michael@0 343 gService->RemoveGamepad(mLocalID);
michael@0 344 }
michael@0 345 }
michael@0 346 return NS_OK;
michael@0 347 }
michael@0 348
michael@0 349 private:
michael@0 350 // ID in WindowsGamepadService::mGamepads
michael@0 351 int mLocalID;
michael@0 352 nsCString mName;
michael@0 353 int mGamepadButtons;
michael@0 354 int mGamepadAxes;
michael@0 355 int mGamepadHats;
michael@0 356 // ID from GamepadService
michael@0 357 uint32_t mGlobalID;
michael@0 358 Type mType;
michael@0 359 };
michael@0 360
michael@0 361 WindowsGamepadService::WindowsGamepadService()
michael@0 362 : mThreadExitEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)),
michael@0 363 mThreadRescanEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)),
michael@0 364 mThread(nullptr),
michael@0 365 mMutex("Windows Gamepad Service"),
michael@0 366 dinput(nullptr) {
michael@0 367 mObserver = new Observer(*this);
michael@0 368 // Initialize DirectInput
michael@0 369 CoInitialize(nullptr);
michael@0 370 if (CoCreateInstance(CLSID_DirectInput8,
michael@0 371 nullptr,
michael@0 372 CLSCTX_INPROC_SERVER,
michael@0 373 IID_IDirectInput8W,
michael@0 374 (LPVOID*)&dinput) == S_OK) {
michael@0 375 if (dinput->Initialize(GetModuleHandle(nullptr),
michael@0 376 DIRECTINPUT_VERSION) != DI_OK) {
michael@0 377 dinput->Release();
michael@0 378 dinput = nullptr;
michael@0 379 }
michael@0 380 }
michael@0 381 }
michael@0 382
michael@0 383 // static
michael@0 384 BOOL CALLBACK
michael@0 385 WindowsGamepadService::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi,
michael@0 386 LPVOID pvRef) {
michael@0 387 // Ensure that all axes are using the same range.
michael@0 388 Gamepad* gamepad = reinterpret_cast<Gamepad*>(pvRef);
michael@0 389 DIPROPRANGE dp;
michael@0 390 dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
michael@0 391 dp.diph.dwSize = sizeof(DIPROPRANGE);
michael@0 392 dp.diph.dwHow = DIPH_BYID;
michael@0 393 dp.diph.dwObj = lpddoi->dwType;
michael@0 394 dp.lMin = 0;
michael@0 395 dp.lMax = kMaxAxisValue;
michael@0 396 gamepad->device->SetProperty(DIPROP_RANGE, &dp.diph);
michael@0 397 // Find what the dwOfs of this object in the c_dfDIJoystick data format is.
michael@0 398 for (DWORD i = 0; i < c_dfDIJoystick.dwNumObjs; i++) {
michael@0 399 if (c_dfDIJoystick.rgodf[i].pguid &&
michael@0 400 IsEqualGUID(*c_dfDIJoystick.rgodf[i].pguid, lpddoi->guidType) &&
michael@0 401 gamepad->numAxes < kMaxAxes) {
michael@0 402 gamepad->axes[gamepad->numAxes] = c_dfDIJoystick.rgodf[i].dwOfs;
michael@0 403 gamepad->numAxes++;
michael@0 404 break;
michael@0 405 }
michael@0 406 }
michael@0 407 return DIENUM_CONTINUE;
michael@0 408 }
michael@0 409
michael@0 410 // static
michael@0 411 BOOL CALLBACK
michael@0 412 WindowsGamepadService::EnumCallback(LPCDIDEVICEINSTANCE lpddi,
michael@0 413 LPVOID pvRef) {
michael@0 414 WindowsGamepadService* self =
michael@0 415 reinterpret_cast<WindowsGamepadService*>(pvRef);
michael@0 416 // See if this device is already present in our list.
michael@0 417 {
michael@0 418 MutexAutoLock lock(self->mMutex);
michael@0 419 for (unsigned int i = 0; i < self->mGamepads.Length(); i++) {
michael@0 420 if (memcmp(&lpddi->guidInstance, &self->mGamepads[i].guidInstance,
michael@0 421 sizeof(GUID)) == 0) {
michael@0 422 self->mGamepads[i].present = true;
michael@0 423 return DIENUM_CONTINUE;
michael@0 424 }
michael@0 425 }
michael@0 426 }
michael@0 427
michael@0 428 Gamepad gamepad = {};
michael@0 429 if (self->dinput->CreateDevice(lpddi->guidInstance,
michael@0 430 getter_AddRefs(gamepad.device),
michael@0 431 nullptr)
michael@0 432 == DI_OK) {
michael@0 433 gamepad.present = true;
michael@0 434 memcpy(&gamepad.guidInstance, &lpddi->guidInstance, sizeof(GUID));
michael@0 435
michael@0 436 DIDEVICEINSTANCE info;
michael@0 437 info.dwSize = sizeof(DIDEVICEINSTANCE);
michael@0 438 if (gamepad.device->GetDeviceInfo(&info) == DI_OK) {
michael@0 439 WideCharToMultiByte(CP_UTF8, 0, info.tszProductName, -1,
michael@0 440 gamepad.name, sizeof(gamepad.name), nullptr, nullptr);
michael@0 441 }
michael@0 442 // Get vendor id and product id
michael@0 443 DIPROPDWORD dp;
michael@0 444 dp.diph.dwSize = sizeof(DIPROPDWORD);
michael@0 445 dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
michael@0 446 dp.diph.dwObj = 0;
michael@0 447 dp.diph.dwHow = DIPH_DEVICE;
michael@0 448 if (gamepad.device->GetProperty(DIPROP_VIDPID, &dp.diph) == DI_OK) {
michael@0 449 sprintf(gamepad.idstring, "%x-%x-%s",
michael@0 450 LOWORD(dp.dwData), HIWORD(dp.dwData), gamepad.name);
michael@0 451 }
michael@0 452 DIDEVCAPS caps;
michael@0 453 caps.dwSize = sizeof(DIDEVCAPS);
michael@0 454 if (gamepad.device->GetCapabilities(&caps) == DI_OK) {
michael@0 455 gamepad.numHats = caps.dwPOVs;
michael@0 456 gamepad.numButtons = caps.dwButtons;
michael@0 457 //XXX: handle polled devices?
michael@0 458 // (caps.dwFlags & DIDC_POLLEDDATAFORMAT || caps.dwFlags & DIDC_POLLEDDEVICE)
michael@0 459 }
michael@0 460 // Set min/max range for all axes on the device.
michael@0 461 // Axes will be gathered in EnumObjectsCallback.
michael@0 462 gamepad.numAxes = 0;
michael@0 463 gamepad.device->EnumObjects(EnumObjectsCallback, &gamepad, DIDFT_AXIS);
michael@0 464 // Set up structure for setting buffer size for buffered data
michael@0 465 dp.diph.dwHeaderSize = sizeof(DIPROPHEADER);
michael@0 466 dp.diph.dwSize = sizeof(DIPROPDWORD);
michael@0 467 dp.diph.dwObj = 0;
michael@0 468 dp.diph.dwHow = DIPH_DEVICE;
michael@0 469 dp.dwData = 64; // arbitrary
michael@0 470 // Create event so DInput can signal us when there's new data.
michael@0 471 gamepad.event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
michael@0 472 // Set data format, event notification, and acquire device
michael@0 473 if (gamepad.device->SetDataFormat(&c_dfDIJoystick) == DI_OK &&
michael@0 474 gamepad.device->SetProperty(DIPROP_BUFFERSIZE, &dp.diph) == DI_OK &&
michael@0 475 gamepad.device->SetEventNotification(gamepad.event) == DI_OK &&
michael@0 476 gamepad.device->Acquire() == DI_OK) {
michael@0 477 MutexAutoLock lock(self->mMutex);
michael@0 478 self->mGamepads.AppendElement(gamepad);
michael@0 479 // Inform the GamepadService
michael@0 480 int localID = self->mGamepads.Length() - 1;
michael@0 481 nsRefPtr<GamepadChangeEvent> event =
michael@0 482 new GamepadChangeEvent(self->mGamepads[localID],
michael@0 483 localID,
michael@0 484 GamepadChangeEvent::Added);
michael@0 485 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
michael@0 486 }
michael@0 487 else {
michael@0 488 if (gamepad.device) {
michael@0 489 gamepad.device->SetEventNotification(nullptr);
michael@0 490 }
michael@0 491 CloseHandle(gamepad.event);
michael@0 492 }
michael@0 493 }
michael@0 494 return DIENUM_CONTINUE;
michael@0 495 }
michael@0 496
michael@0 497 void
michael@0 498 WindowsGamepadService::ScanForDevices() {
michael@0 499 {
michael@0 500 MutexAutoLock lock(mMutex);
michael@0 501 for (int i = mGamepads.Length() - 1; i >= 0; i--) {
michael@0 502 if (mGamepads[i].remove) {
michael@0 503
michael@0 504 // Main thread has already handled this, safe to remove.
michael@0 505 CleanupGamepad(mGamepads[i]);
michael@0 506 mGamepads.RemoveElementAt(i);
michael@0 507 } else {
michael@0 508 mGamepads[i].present = false;
michael@0 509 }
michael@0 510 }
michael@0 511 }
michael@0 512
michael@0 513 dinput->EnumDevices(DI8DEVCLASS_GAMECTRL,
michael@0 514 (LPDIENUMDEVICESCALLBACK)EnumCallback,
michael@0 515 this,
michael@0 516 DIEDFL_ATTACHEDONLY);
michael@0 517
michael@0 518 // Look for devices that are no longer present and inform the main thread.
michael@0 519 {
michael@0 520 MutexAutoLock lock(mMutex);
michael@0 521 for (int i = mGamepads.Length() - 1; i >= 0; i--) {
michael@0 522 if (!mGamepads[i].present) {
michael@0 523 nsRefPtr<GamepadChangeEvent> event =
michael@0 524 new GamepadChangeEvent(mGamepads[i],
michael@0 525 i,
michael@0 526 GamepadChangeEvent::Removed);
michael@0 527 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
michael@0 528 }
michael@0 529 }
michael@0 530
michael@0 531 mEvents.Clear();
michael@0 532 for (unsigned int i = 0; i < mGamepads.Length(); i++) {
michael@0 533 mEvents.AppendElement(mGamepads[i].event);
michael@0 534 }
michael@0 535 }
michael@0 536
michael@0 537
michael@0 538 // These events must be the last elements in the array, so that
michael@0 539 // the other elements match mGamepads in order.
michael@0 540 mEvents.AppendElement(mThreadRescanEvent);
michael@0 541 mEvents.AppendElement(mThreadExitEvent);
michael@0 542 }
michael@0 543
michael@0 544 // static
michael@0 545 DWORD WINAPI
michael@0 546 WindowsGamepadService::DInputThread(LPVOID arg) {
michael@0 547 WindowsGamepadService* self = reinterpret_cast<WindowsGamepadService*>(arg);
michael@0 548 self->ScanForDevices();
michael@0 549
michael@0 550 while (true) {
michael@0 551 DWORD result = WaitForMultipleObjects(self->mEvents.Length(),
michael@0 552 self->mEvents.Elements(),
michael@0 553 FALSE,
michael@0 554 INFINITE);
michael@0 555 if (result == WAIT_FAILED ||
michael@0 556 result == WAIT_OBJECT_0 + self->mEvents.Length() - 1) {
michael@0 557 // error, or the main thread signaled us to exit
michael@0 558 break;
michael@0 559 }
michael@0 560
michael@0 561 unsigned int i = result - WAIT_OBJECT_0;
michael@0 562
michael@0 563 if (i == self->mEvents.Length() - 2) {
michael@0 564 // Main thread is signaling for a device rescan.
michael@0 565 self->ScanForDevices();
michael@0 566 continue;
michael@0 567 }
michael@0 568
michael@0 569 {
michael@0 570 MutexAutoLock lock(self->mMutex);
michael@0 571 if (i >= self->mGamepads.Length()) {
michael@0 572 // Something would be terribly wrong here, possibly we got
michael@0 573 // a WAIT_ABANDONED_x result.
michael@0 574 continue;
michael@0 575 }
michael@0 576
michael@0 577 // first query for the number of items in the buffer
michael@0 578 DWORD items = INFINITE;
michael@0 579 nsRefPtr<IDirectInputDevice8> device = self->mGamepads[i].device;
michael@0 580 if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
michael@0 581 nullptr,
michael@0 582 &items,
michael@0 583 DIGDD_PEEK)== DI_OK) {
michael@0 584 while (items > 0) {
michael@0 585 // now read each buffered event
michael@0 586 //TODO: read more than one event at a time
michael@0 587 DIDEVICEOBJECTDATA data;
michael@0 588 DWORD readCount = sizeof(data) / sizeof(DIDEVICEOBJECTDATA);
michael@0 589 if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
michael@0 590 &data, &readCount, 0) == DI_OK) {
michael@0 591 //TODO: data.dwTimeStamp
michael@0 592 GamepadEvent::Type type = GamepadEvent::Unknown;
michael@0 593 int which = -1;
michael@0 594 if (data.dwOfs >= DIJOFS_BUTTON0 && data.dwOfs < DIJOFS_BUTTON(32)) {
michael@0 595 type = GamepadEvent::Button;
michael@0 596 which = data.dwOfs - DIJOFS_BUTTON0;
michael@0 597 }
michael@0 598 else if(data.dwOfs >= DIJOFS_X && data.dwOfs < DIJOFS_SLIDER(2)) {
michael@0 599 // axis/slider
michael@0 600 type = GamepadEvent::Axis;
michael@0 601 for (int a = 0; a < self->mGamepads[i].numAxes; a++) {
michael@0 602 if (self->mGamepads[i].axes[a] == data.dwOfs) {
michael@0 603 which = a;
michael@0 604 break;
michael@0 605 }
michael@0 606 }
michael@0 607 }
michael@0 608 else if (data.dwOfs >= DIJOFS_POV(0) && data.dwOfs < DIJOFS_POV(4)) {
michael@0 609 HatState hatState;
michael@0 610 HatPosToAxes(data.dwData, hatState);
michael@0 611 which = (data.dwOfs - DIJOFS_POV(0)) / sizeof(DWORD);
michael@0 612 // Only send out axis move events for the axes that moved
michael@0 613 // in this hat move.
michael@0 614 if (hatState.x != self->mGamepads[i].hatState[which].x) {
michael@0 615 type = GamepadEvent::HatX;
michael@0 616 }
michael@0 617 if (hatState.y != self->mGamepads[i].hatState[which].y) {
michael@0 618 if (type == GamepadEvent::HatX) {
michael@0 619 type = GamepadEvent::HatXY;
michael@0 620 }
michael@0 621 else {
michael@0 622 type = GamepadEvent::HatY;
michael@0 623 }
michael@0 624 }
michael@0 625 self->mGamepads[i].hatState[which].x = hatState.x;
michael@0 626 self->mGamepads[i].hatState[which].y = hatState.y;
michael@0 627 }
michael@0 628
michael@0 629 if (type != GamepadEvent::Unknown && which != -1) {
michael@0 630 nsRefPtr<GamepadEvent> event =
michael@0 631 new GamepadEvent(self->mGamepads[i], type, which, data.dwData);
michael@0 632 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
michael@0 633 }
michael@0 634 }
michael@0 635 items--;
michael@0 636 }
michael@0 637 }
michael@0 638 }
michael@0 639 }
michael@0 640 return 0;
michael@0 641 }
michael@0 642
michael@0 643 void
michael@0 644 WindowsGamepadService::Startup() {
michael@0 645 mThread = CreateThread(nullptr,
michael@0 646 0,
michael@0 647 DInputThread,
michael@0 648 this,
michael@0 649 0,
michael@0 650 nullptr);
michael@0 651 }
michael@0 652
michael@0 653 void
michael@0 654 WindowsGamepadService::Shutdown() {
michael@0 655 if (mThread) {
michael@0 656 SetEvent(mThreadExitEvent);
michael@0 657 WaitForSingleObject(mThread, INFINITE);
michael@0 658 CloseHandle(mThread);
michael@0 659 }
michael@0 660 Cleanup();
michael@0 661 }
michael@0 662
michael@0 663 // This method is called from the main thread.
michael@0 664 void
michael@0 665 WindowsGamepadService::SetGamepadID(int localID, int globalID) {
michael@0 666 MutexAutoLock lock(mMutex);
michael@0 667 mGamepads[localID].globalID = globalID;
michael@0 668 }
michael@0 669
michael@0 670 // This method is called from the main thread.
michael@0 671 void WindowsGamepadService::RemoveGamepad(int localID) {
michael@0 672 MutexAutoLock lock(mMutex);
michael@0 673 mGamepads[localID].remove = true;
michael@0 674 // Signal background thread to remove device.
michael@0 675 DevicesChanged(DeviceChangeStable);
michael@0 676 }
michael@0 677
michael@0 678 void
michael@0 679 WindowsGamepadService::Cleanup() {
michael@0 680 for (unsigned int i = 0; i < mGamepads.Length(); i++) {
michael@0 681 CleanupGamepad(mGamepads[i]);
michael@0 682 }
michael@0 683 mGamepads.Clear();
michael@0 684 }
michael@0 685
michael@0 686 void
michael@0 687 WindowsGamepadService::CleanupGamepad(Gamepad& gamepad) {
michael@0 688 gamepad.device->Unacquire();
michael@0 689 gamepad.device->SetEventNotification(nullptr);
michael@0 690 CloseHandle(gamepad.event);
michael@0 691 }
michael@0 692
michael@0 693 void
michael@0 694 WindowsGamepadService::DevicesChanged(DeviceChangeType type) {
michael@0 695 if (type == DeviceChangeNotification) {
michael@0 696 mObserver->SetDeviceChangeTimer();
michael@0 697 } else if (type == DeviceChangeStable) {
michael@0 698 SetEvent(mThreadRescanEvent);
michael@0 699 }
michael@0 700 }
michael@0 701
michael@0 702 NS_IMETHODIMP
michael@0 703 Observer::Observe(nsISupports* aSubject,
michael@0 704 const char* aTopic,
michael@0 705 const char16_t* aData) {
michael@0 706 if (strcmp(aTopic, "timer-callback") == 0) {
michael@0 707 mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable);
michael@0 708 } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
michael@0 709 Stop();
michael@0 710 }
michael@0 711 return NS_OK;
michael@0 712 }
michael@0 713
michael@0 714 HWND sHWnd = nullptr;
michael@0 715
michael@0 716 static
michael@0 717 LRESULT CALLBACK
michael@0 718 GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
michael@0 719 const unsigned int DBT_DEVICEARRIVAL = 0x8000;
michael@0 720 const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
michael@0 721 const unsigned int DBT_DEVNODES_CHANGED = 0x7;
michael@0 722
michael@0 723 if (msg == WM_DEVICECHANGE &&
michael@0 724 (wParam == DBT_DEVICEARRIVAL ||
michael@0 725 wParam == DBT_DEVICEREMOVECOMPLETE ||
michael@0 726 wParam == DBT_DEVNODES_CHANGED)) {
michael@0 727 if (gService) {
michael@0 728 gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification);
michael@0 729 }
michael@0 730 }
michael@0 731 return DefWindowProc(hwnd, msg, wParam, lParam);
michael@0 732 }
michael@0 733
michael@0 734 } // namespace
michael@0 735
michael@0 736 namespace mozilla {
michael@0 737 namespace hal_impl {
michael@0 738
michael@0 739 void StartMonitoringGamepadStatus()
michael@0 740 {
michael@0 741 if (gService)
michael@0 742 return;
michael@0 743
michael@0 744 gService = new WindowsGamepadService();
michael@0 745 gService->Startup();
michael@0 746
michael@0 747 if (sHWnd == nullptr) {
michael@0 748 WNDCLASSW wc;
michael@0 749 HMODULE hSelf = GetModuleHandle(nullptr);
michael@0 750
michael@0 751 if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
michael@0 752 ZeroMemory(&wc, sizeof(WNDCLASSW));
michael@0 753 wc.hInstance = hSelf;
michael@0 754 wc.lpfnWndProc = GamepadWindowProc;
michael@0 755 wc.lpszClassName = L"MozillaGamepadClass";
michael@0 756 RegisterClassW(&wc);
michael@0 757 }
michael@0 758
michael@0 759 sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
michael@0 760 0, 0, 0, 0, 0,
michael@0 761 nullptr, nullptr, hSelf, nullptr);
michael@0 762 }
michael@0 763 }
michael@0 764
michael@0 765 void StopMonitoringGamepadStatus()
michael@0 766 {
michael@0 767 if (!gService)
michael@0 768 return;
michael@0 769
michael@0 770 if (sHWnd) {
michael@0 771 DestroyWindow(sHWnd);
michael@0 772 sHWnd = nullptr;
michael@0 773 }
michael@0 774
michael@0 775 gService->Shutdown();
michael@0 776 delete gService;
michael@0 777 gService = nullptr;
michael@0 778 }
michael@0 779
michael@0 780 } // namespace hal_impl
michael@0 781 } // namespace mozilla
michael@0 782

mercurial