|
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/. */ |
|
4 |
|
5 #include <algorithm> |
|
6 #include <cstddef> |
|
7 |
|
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> |
|
15 |
|
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" |
|
25 |
|
26 namespace { |
|
27 |
|
28 using mozilla::dom::GamepadService; |
|
29 using mozilla::Mutex; |
|
30 using mozilla::MutexAutoLock; |
|
31 |
|
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; |
|
40 |
|
41 class WindowsGamepadService; |
|
42 WindowsGamepadService* gService = nullptr; |
|
43 |
|
44 typedef struct { |
|
45 float x,y; |
|
46 } HatState; |
|
47 |
|
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 }; |
|
79 |
|
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 } |
|
135 |
|
136 class Observer : public nsIObserver { |
|
137 public: |
|
138 NS_DECL_ISUPPORTS |
|
139 NS_DECL_NSIOBSERVER |
|
140 |
|
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 } |
|
151 |
|
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 } |
|
163 |
|
164 virtual ~Observer() { |
|
165 Stop(); |
|
166 } |
|
167 |
|
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 } |
|
176 |
|
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 }; |
|
183 |
|
184 NS_IMPL_ISUPPORTS(Observer, nsIObserver); |
|
185 |
|
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 } |
|
198 |
|
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); |
|
208 |
|
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); |
|
220 |
|
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; |
|
226 |
|
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; |
|
233 |
|
234 LPDIRECTINPUT8 dinput; |
|
235 |
|
236 nsRefPtr<Observer> mObserver; |
|
237 }; |
|
238 |
|
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; |
|
250 |
|
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 } |
|
260 |
|
261 NS_IMETHOD Run() { |
|
262 nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService()); |
|
263 if (!gamepadsvc) { |
|
264 return NS_OK; |
|
265 } |
|
266 |
|
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 } |
|
298 |
|
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 }; |
|
308 |
|
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 } |
|
325 |
|
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 } |
|
348 |
|
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 }; |
|
360 |
|
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 } |
|
382 |
|
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 } |
|
409 |
|
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 } |
|
427 |
|
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)); |
|
435 |
|
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 } |
|
496 |
|
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) { |
|
503 |
|
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 } |
|
512 |
|
513 dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, |
|
514 (LPDIENUMDEVICESCALLBACK)EnumCallback, |
|
515 this, |
|
516 DIEDFL_ATTACHEDONLY); |
|
517 |
|
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 } |
|
530 |
|
531 mEvents.Clear(); |
|
532 for (unsigned int i = 0; i < mGamepads.Length(); i++) { |
|
533 mEvents.AppendElement(mGamepads[i].event); |
|
534 } |
|
535 } |
|
536 |
|
537 |
|
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 } |
|
543 |
|
544 // static |
|
545 DWORD WINAPI |
|
546 WindowsGamepadService::DInputThread(LPVOID arg) { |
|
547 WindowsGamepadService* self = reinterpret_cast<WindowsGamepadService*>(arg); |
|
548 self->ScanForDevices(); |
|
549 |
|
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 } |
|
560 |
|
561 unsigned int i = result - WAIT_OBJECT_0; |
|
562 |
|
563 if (i == self->mEvents.Length() - 2) { |
|
564 // Main thread is signaling for a device rescan. |
|
565 self->ScanForDevices(); |
|
566 continue; |
|
567 } |
|
568 |
|
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 } |
|
576 |
|
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 } |
|
628 |
|
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 } |
|
642 |
|
643 void |
|
644 WindowsGamepadService::Startup() { |
|
645 mThread = CreateThread(nullptr, |
|
646 0, |
|
647 DInputThread, |
|
648 this, |
|
649 0, |
|
650 nullptr); |
|
651 } |
|
652 |
|
653 void |
|
654 WindowsGamepadService::Shutdown() { |
|
655 if (mThread) { |
|
656 SetEvent(mThreadExitEvent); |
|
657 WaitForSingleObject(mThread, INFINITE); |
|
658 CloseHandle(mThread); |
|
659 } |
|
660 Cleanup(); |
|
661 } |
|
662 |
|
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 } |
|
669 |
|
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 } |
|
677 |
|
678 void |
|
679 WindowsGamepadService::Cleanup() { |
|
680 for (unsigned int i = 0; i < mGamepads.Length(); i++) { |
|
681 CleanupGamepad(mGamepads[i]); |
|
682 } |
|
683 mGamepads.Clear(); |
|
684 } |
|
685 |
|
686 void |
|
687 WindowsGamepadService::CleanupGamepad(Gamepad& gamepad) { |
|
688 gamepad.device->Unacquire(); |
|
689 gamepad.device->SetEventNotification(nullptr); |
|
690 CloseHandle(gamepad.event); |
|
691 } |
|
692 |
|
693 void |
|
694 WindowsGamepadService::DevicesChanged(DeviceChangeType type) { |
|
695 if (type == DeviceChangeNotification) { |
|
696 mObserver->SetDeviceChangeTimer(); |
|
697 } else if (type == DeviceChangeStable) { |
|
698 SetEvent(mThreadRescanEvent); |
|
699 } |
|
700 } |
|
701 |
|
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 } |
|
713 |
|
714 HWND sHWnd = nullptr; |
|
715 |
|
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; |
|
722 |
|
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 } |
|
733 |
|
734 } // namespace |
|
735 |
|
736 namespace mozilla { |
|
737 namespace hal_impl { |
|
738 |
|
739 void StartMonitoringGamepadStatus() |
|
740 { |
|
741 if (gService) |
|
742 return; |
|
743 |
|
744 gService = new WindowsGamepadService(); |
|
745 gService->Startup(); |
|
746 |
|
747 if (sHWnd == nullptr) { |
|
748 WNDCLASSW wc; |
|
749 HMODULE hSelf = GetModuleHandle(nullptr); |
|
750 |
|
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 } |
|
758 |
|
759 sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", |
|
760 0, 0, 0, 0, 0, |
|
761 nullptr, nullptr, hSelf, nullptr); |
|
762 } |
|
763 } |
|
764 |
|
765 void StopMonitoringGamepadStatus() |
|
766 { |
|
767 if (!gService) |
|
768 return; |
|
769 |
|
770 if (sHWnd) { |
|
771 DestroyWindow(sHWnd); |
|
772 sHWnd = nullptr; |
|
773 } |
|
774 |
|
775 gService->Shutdown(); |
|
776 delete gService; |
|
777 gService = nullptr; |
|
778 } |
|
779 |
|
780 } // namespace hal_impl |
|
781 } // namespace mozilla |
|
782 |