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