Panda3D

winInputDeviceManager.cxx
Go to the documentation of this file.
1 /**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file winInputDeviceManager.cxx
10 * @author rdb
11 * @date 2018-01-21
12 */
13
14 #include "winInputDeviceManager.h"
15 #include "winRawInputDevice.h"
16 #include "throw_event.h"
17 #include "phidsdi.h"
18
19 #if defined(_WIN32) && !defined(CPPPARSER)
20
21 #ifdef HAVE_THREADS
22 /**
23 *
24 */
25 class InputThread : public Thread {
26 public:
27 InputThread(WinInputDeviceManager *manager) :
28 Thread("input", "input"), _manager(manager) {}
29
30 private:
31 virtual void thread_main();
32
33 WinInputDeviceManager *_manager;
34 };
35 #endif
36
37 /**
38 * Initializes the input device manager by scanning which devices are currently
39 * connected and setting up any platform-dependent structures necessary for
40 * listening for future device connect events.
41 */
42 WinInputDeviceManager::
43 WinInputDeviceManager() :
44 _xinput_device0(0),
45 _xinput_device1(1),
46 _xinput_device2(2),
47 _xinput_device3(3),
48 _message_hwnd(nullptr) {
49
50 // XInput provides four device slots, so we simply create four XInputDevice
51 // objects that are bound to the lifetime of the input manager.
52 _xinput_device0.local_object();
53 _xinput_device1.local_object();
54 _xinput_device2.local_object();
55 _xinput_device3.local_object();
56
57 // This function is only available in Vista and later, so we use a wrapper.
58 HMODULE module = LoadLibraryA("cfgmgr32.dll");
59 if (module) {
60 _CM_Get_DevNode_PropertyW = (pCM_Get_DevNode_Property)GetProcAddress(module, "CM_Get_DevNode_PropertyW");
61 } else {
62 _CM_Get_DevNode_PropertyW = nullptr;
63 }
64
65 // If we have threading enabled, start a thread with a message-only window
66 // loop to listen for input events. We can't actually just let this be
67 // handled by the main window loop, because the main window might actually
68 // have been created in a different thread.
69 #ifdef HAVE_THREADS
70 if (Thread::is_threading_supported()) {
71 PT(Thread) thread = new InputThread(this);
72 thread->start(TP_normal, false);
73 } else
74 #endif
75 {
76 setup_message_loop();
77 }
78 }
79
80 /**
81 * Closes any resources that the device manager was using to listen for events.
82 */
83 WinInputDeviceManager::
84 ~WinInputDeviceManager() {
85 if (_message_hwnd != nullptr) {
86 #ifdef HAVE_THREADS
87 if (Thread::is_threading_supported()) {
88 HWND hwnd = _message_hwnd;
89 if (hwnd) {
90 SendMessage(hwnd, WM_QUIT, 0, 0);
91 }
92 } else
93 #endif
94 {
95 destroy_message_loop();
96 }
97 }
98 }
99
100 /**
101 * Called by the raw input device destructor.
102 */
103 void WinInputDeviceManager::
104 device_destroyed(WinRawInputDevice *device) {
105 LightMutexHolder holder(_lock);
106 // It shouldn't be in here, but let's check to be sure.
107 if (device->_handle != nullptr) {
108 _raw_devices.erase(device->_handle);
109 }
110
111 _raw_devices_by_path.erase(device->_path);
112 }
113
114 /**
115 * Called upon receiving a WM_INPUT message.
116 */
117 void WinInputDeviceManager::
118 on_input(HRAWINPUT handle) {
119 UINT size;
120 if (GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)) < 0) {
121 return;
122 }
123
124 PRAWINPUT data = (PRAWINPUT)alloca(size);
125 if (GetRawInputData(handle, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER)) <= 0) {
126 return;
127 }
128
129 // Look up the device in the map.
130 PT(WinRawInputDevice) device;
131 {
132 LightMutexHolder holder(_lock);
133 auto it = _raw_devices.find(data->header.hDevice);
134 if (it != _raw_devices.end()) {
135 device = it->second;
136 }
137 }
138 if (device != nullptr) {
139 device->on_input(data);
140 }
141 }
142
143 /**
144 * Called upon receiving WM_INPUT_DEVICE_CHANGE with wparam GIDC_ARRIVAL.
145 */
146 void WinInputDeviceManager::
147 on_input_device_arrival(HANDLE handle) {
148 // Get the device path.
149 UINT size;
150 if (GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, nullptr, &size) != 0) {
151 return;
152 }
153
154 char *path = (char *)alloca(size);
155 if (path == nullptr ||
156 GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, (void *)path, &size) < 0) {
157 return;
158 }
159
160 if (device_cat.is_debug()) {
161 device_cat.debug()
162 << "GIDC_ARRIVAL: " << path << "\n";
163 }
164
165 // Get the device info.
166 RID_DEVICE_INFO info;
167 info.cbSize = sizeof(RID_DEVICE_INFO);
168 size = sizeof(RID_DEVICE_INFO);
169 if (GetRawInputDeviceInfoA(handle, RIDI_DEVICEINFO, &info, &size) <= 0) {
170 return;
171 }
172
173 // Strip the \\?\ prefix from the path.
174 while (path[0] == '\\' || path[0] == '?' || path[0] == '.') {
175 ++path;
176 }
177
178 // Now, replace # with \\ in the path, but only up to three components.
179 char *p = path;
180 int i = 0;
181 while (*p != '\0') {
182 if (*p == '#') {
183 if (i++ < 2) {
184 *p = '\\';
185 } else {
186 *p = '\0';
187 break;
188 }
189 }
190 ++p;
191 }
192
193 // Find the device node, which will be something like "HID\VID_0123..."
194 // Then we walk the device tree upward to get the USB node, which which will
195 // be something like a "USB\VID..." node, from which we can fetch the real
196 // USB device information (such as the product name).
197 std::string name, manufacturer;
198 DEVINST inst;
199 CONFIGRET ret = CM_Locate_DevNodeA(&inst, (DEVINSTID_A)path, CM_LOCATE_DEVNODE_PHANTOM);
200 if (ret == CR_SUCCESS) {
201 char buffer[4096];
202 ULONG buflen = 4096;
203 if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) {
204 name.assign(buffer);
205 }
206 buflen = 4096;
207 if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) {
208 if (strcmp(buffer, "(Standard system devices)") != 0) {
209 manufacturer.assign(buffer);
210 }
211 }
212
213 // Now walk the device tree upwards so fetch the bus-reported name of the
214 // parent USB device, which we prefer over the regular device description
215 // that is probably boring like "HID-compliant game controller".
216 DEVINST cur = inst;
217 DEVINST parent;
218 while (CM_Get_Parent(&parent, cur, 0) == CR_SUCCESS) {
219 buflen = 4096;
220 std::string dev_class;
221 if (CM_Get_DevNode_Registry_Property(parent, CM_DRP_CLASS, 0, buffer, &buflen, 0) == CR_SUCCESS) {
222 if (strcmp(buffer, "USB") == 0) {
223 // This is some generic USB device, like a hub. We've gone too far.
224 break;
225 }
226 dev_class.assign(buffer);
227 }
228 cur = parent;
229
230 // While we're at it, maybe this one defines a manufacturer?
231 buflen = 4096;
232 if (manufacturer.empty() &&
233 CM_Get_DevNode_Registry_Property(cur, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) {
234 if (strcmp(buffer, "(Standard system devices)") != 0) {
235 manufacturer.assign(buffer);
236 }
237 }
238
239 // If it's a generic HID device, take the name from the USB bus.
240 // See devpkey.h for the available property keys.
241 static const DEVPROPKEY bus_reported_device_desc = {
242 {0x540b947e, 0x8b40, 0x45bc, {0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2}},
243 4,
244 };
245 DEVPROPTYPE type;
246 buflen = 4096;
247 if (dev_class == "HIDClass" && _CM_Get_DevNode_PropertyW != nullptr &&
248 _CM_Get_DevNode_PropertyW(cur, &bus_reported_device_desc, &type, (PBYTE)buffer, &buflen, 0) == CR_SUCCESS &&
249 type == DEVPROP_TYPE_STRING) {
250
251 // Some devices insert quite some trailing space here.
252 wchar_t *wbuffer = (wchar_t *)buffer;
253 size_t wlen = wcsnlen_s(wbuffer, sizeof(buffer) / sizeof(wchar_t));
254 while (wlen > 0 && iswspace(wbuffer[wlen - 1])) {
255 wbuffer[--wlen] = 0;
256 }
257 TextEncoder encoder;
258 name.assign(encoder.encode_wtext(std::wstring(wbuffer, wlen)));
259 break;
260 } else {
261 buflen = 4096;
262 if (CM_Get_DevNode_Registry_Property(cur, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) {
263 // We'll pass if it has this awfully boring name. Is there a
264 // language-independent way to check this?
265 if (strcmp(buffer, "USB Input Device") != 0) {
266 name.assign(buffer);
267 }
268 }
269 }
270 }
271 } else if (device_cat.is_debug()) {
272 // No big deal, we just won't be able to get the name.
273 device_cat.debug()
274 << "Could not locate device node " << path << " (" << ret << ")\n";
275 }
276
277 // Is this an XInput device? If so, handle it via XInput, which allows us
278 // to handle independent left/right triggers as well as vibration output.
279 if (info.dwType == RIM_TYPEHID && strstr(path, "&IG_") != nullptr &&
280 XInputDevice::init_xinput()) {
281 // This is a device we should handle via the XInput API. Check which of
282 // the four players was the lucky one.
283 if (_xinput_device0.check_arrival(info, inst, name, manufacturer)) {
284 add_device(&_xinput_device0);
285 }
286 if (_xinput_device1.check_arrival(info, inst, name, manufacturer)) {
287 add_device(&_xinput_device1);
288 }
289 if (_xinput_device2.check_arrival(info, inst, name, manufacturer)) {
290 add_device(&_xinput_device2);
291 }
292 if (_xinput_device3.check_arrival(info, inst, name, manufacturer)) {
293 add_device(&_xinput_device3);
294 }
295 return;
296 }
297
298 LightMutexHolder holder(_lock);
299
300 // Do we have a device by this path already? This can happen if the
301 // user keeps around a pointer to a disconnected device in the hope that
302 // it will reconnect later.
303 PT(WinRawInputDevice) device;
304 auto it = _raw_devices_by_path.find(path);
305 if (it != _raw_devices_by_path.end()) {
306 device = it->second;
307 } else {
308 device = new WinRawInputDevice(this, path);
309 _raw_devices_by_path[path] = device;
310 }
311
312 if (device->on_arrival(handle, info, move(name))) {
313 _raw_devices[handle] = device;
314 _connected_devices.add_device(device);
315
316 if (device_cat.is_debug()) {
317 device_cat.debug()
318 << "Discovered input device " << *device << "\n";
319 }
320 throw_event("connect-device", device.p());
321 }
322 }
323
324 /**
325 * Called upon receiving WM_INPUT_DEVICE_CHANGE with wparam GIDC_REMOVAL.
326 */
327 void WinInputDeviceManager::
328 on_input_device_removal(HANDLE handle) {
329 // The handle will probably no longer be valid after this, so find the
330 // device and remove it from _raw_devices. However, we keep it in
331 // _raw_devices_by_path in case there's still a pointer around to it.
332
333 // We keep the pointer outside the lock because the input device
334 // destructor calls back to InputDeviceManager.
335 PT(WinRawInputDevice) device;
336 {
337 LightMutexHolder holder(_lock);
338 auto it = _raw_devices.find(handle);
339 if (it != _raw_devices.end()) {
340 device = std::move(it->second);
341 _raw_devices.erase(it);
342 device->on_removal();
343
344 if (_connected_devices.remove_device(device)) {
345 throw_event("disconnect-device", device.p());
346 }
347 if (device_cat.is_debug()) {
348 device_cat.debug()
349 << "Removed input device " << *device << "\n";
350 }
351 }
352 }
353 }
354
355 /**
356 * Polls the system to see if there are any new devices. In some
357 * implementations this is a no-op.
358 */
359 void WinInputDeviceManager::
360 update() {
361 }
362
363 /**
364 * Sets up a Windows message loop. Should be called from the thread that will
365 * be handling the messages.
366 */
367 HWND WinInputDeviceManager::
368 setup_message_loop() {
369 _message_hwnd = 0;
370
371 // Now create a message-only window for the raw input.
372 WNDCLASSEX wc = {};
373 wc.cbSize = sizeof(WNDCLASSEX);
374 wc.lpfnWndProc = window_proc;
375 wc.hInstance = GetModuleHandle(nullptr);
376 wc.lpszClassName = "InputDeviceManager";
377 if (!RegisterClassEx(&wc)) {
378 device_cat.warning()
379 << "Failed to register message-only window class for input device detection.\n";
380 } else {
381 _message_hwnd = CreateWindowEx(0, wc.lpszClassName, "InputDeviceManager", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr);
382 if (!_message_hwnd) {
383 device_cat.warning()
384 << "Failed to create message-only window for input device detection.\n";
385 }
386 }
387
388 // Now listen for raw input devices using the created message loop.
389 RAWINPUTDEVICE rid[4];
390 rid[0].usUsagePage = 1;
391 rid[0].usUsage = 4; // Joysticks
392 rid[0].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
393 rid[0].hwndTarget = _message_hwnd;
394 rid[1].usUsagePage = 1;
395 rid[1].usUsage = 5; // Gamepads
396 rid[1].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
397 rid[1].hwndTarget = _message_hwnd;
398 rid[2].usUsagePage = 1;
399 rid[2].usUsage = 8; // Multi-axis controllers (including 3D mice)
400 rid[2].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
401 rid[2].hwndTarget = _message_hwnd;
402 rid[3].usUsagePage = HID_USAGE_PAGE_DIGITIZER;
403 rid[3].usUsage = 1; // Digitizers
404 rid[3].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
405 rid[3].hwndTarget = _message_hwnd;
406 if (!RegisterRawInputDevices(rid, 4, sizeof(RAWINPUTDEVICE))) {
407 device_cat.warning()
408 << "Failed to register raw input devices.\n";
409 }
410
411 // Do we have any XInput devices plugged in now?
412 int num_xinput = 0;
413 HANDLE xinput_handle;
414 RAWINPUTDEVICELIST devices[64];
415 UINT num_devices = 64;
416 num_devices = GetRawInputDeviceList(devices, &num_devices, sizeof(RAWINPUTDEVICELIST));
417 if (num_devices == (UINT)-1) {
418 num_devices = 0;
419 }
420 for (UINT i = 0; i < num_devices; ++i) {
421 if (devices[i].dwType != RIM_TYPEHID) {
422 continue;
423 }
424 HANDLE handle = devices[i].hDevice;
425 UINT size;
426 if (GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, nullptr, &size) != 0) {
427 continue;
428 }
429
430 char *path = (char *)alloca(size);
431 if (path == nullptr ||
432 GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, (void *)path, &size) < 0) {
433 continue;
434 }
435
436 if (strstr(path, "&IG_") != nullptr) {
437 xinput_handle = handle;
438 ++num_xinput;
439 }
440 }
441 if (num_xinput == 1) {
442 // There's only one XInput device, so we know which one it is.
443 on_input_device_arrival(xinput_handle);
444 } else if (num_xinput > 0) {
445 // Just poll all the XInput devices.
446 _xinput_device0.detect(this);
447 _xinput_device1.detect(this);
448 _xinput_device2.detect(this);
449 _xinput_device3.detect(this);
450 }
451
452 return _message_hwnd;
453 }
454
455 /**
456 * Tears down the message loop. Should be called from the thread that called
457 * setup_message_loop().
458 */
459 void WinInputDeviceManager::
460 destroy_message_loop() {
461 HWND hwnd = nullptr;
462 {
463 LightMutexHolder holder(_lock);
464 std::swap(_message_hwnd, hwnd);
465 }
466
467 if (hwnd) {
468 DestroyWindow(hwnd);
469 }
470 }
471
472 /**
473 * Sends a signal to the thread input thread, asking it to shut itself down.
474 */
475 void WinInputDeviceManager::
476 stop_thread() {
477 #ifdef HAVE_THREADS
478 WinInputDeviceManager *mgr = (WinInputDeviceManager *)_global_ptr;
479 if (mgr != nullptr) {
480 LightMutexHolder holder(mgr->_lock);
481 HWND hwnd = mgr->_message_hwnd;
482 if (hwnd) {
483 PostMessage(hwnd, WM_QUIT, 0, 0);
484 }
485 }
486 #endif
487 }
488
489 /**
490 * Implementation of the message loop.
491 */
492 LRESULT WINAPI WinInputDeviceManager::
493 window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
494 WinInputDeviceManager *mgr;
495 switch (msg) {
496 case WM_INPUT:
497 mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr();
498 if (mgr != nullptr) {
499 mgr->on_input((HRAWINPUT)lparam);
500 }
501 break;
502
503 case WM_INPUT_DEVICE_CHANGE:
504 switch (LOWORD(wparam)) {
505 case GIDC_ARRIVAL:
506 mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr();
507 if (mgr != nullptr) {
508 mgr->on_input_device_arrival((HANDLE)lparam);
509 }
510 break;
511
512 case GIDC_REMOVAL:
513 mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr();
514 if (mgr != nullptr) {
515 mgr->on_input_device_removal((HANDLE)lparam);
516 }
517 break;
518 }
519 break;
520
521 default:
522 break;
523 }
524 return DefWindowProcW(hwnd, msg, wparam, lparam);
525 }
526
527 #ifdef HAVE_THREADS
528 /**
529 * Thread entry point for the input listener thread.
530 */
531 void InputThread::
532 thread_main() {
533 WinInputDeviceManager *manager = _manager;
534 HWND hwnd = manager->setup_message_loop();
535 if (!hwnd) {
536 return;
537 }
538
539 if (device_cat.is_debug()) {
540 device_cat.debug()
541 << "Started input device listener thread.\n";
542 }
543
544 MSG msg;
545 #ifdef SIMPLE_THREADS
546 // In the simple threading case, we can't block the thread waiting for a
547 // message; we yield control back if there are no more messages.
548 while (true) {
549 if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
550 if (msg.message == WM_QUIT) {
551 break;
552 }
553 TranslateMessage(&msg);
554 DispatchMessage(&msg);
555 } else {
556 Thread::force_yield();
557 }
558 }
559 #else
560 while (GetMessage(&msg, nullptr, 0, 0) > 0) {
561 TranslateMessage(&msg);
562 DispatchMessage(&msg);
563 }
564 #endif
565
566 if (device_cat.is_debug()) {
567 device_cat.debug()
568 << "Stopping input device listener thread.\n";
569 }
570
571 manager->destroy_message_loop();
572 }
573 #endif // HAVE_THREADS
574
575 #endif
InputDeviceManager::get_global_ptr
static InputDeviceManager * get_global_ptr()
Returns the singleton InputDeviceManager instance.
Definition: inputDeviceManager.I:18
LightMutexHolder
Similar to MutexHolder, but for a light mutex.
Definition: lightMutexHolder.h:25
TextEncoder
This class can be used to convert text between multiple representations, e.g.
Definition: textEncoder.h:33
TextEncoder::encode_wtext
std::string encode_wtext(const std::wstring &wtext) const
Encodes a wide-text string into a single-char string, according to the current encoding.
Definition: textEncoder.I:481
Thread
A thread; that is, a lightweight process.
Definition: thread.h:46
Thread::is_threading_supported
is_threading_supported
Returns true if threading support has been compiled in and enabled, or false if no threading is avail...
Definition: thread.h:112
Thread::force_yield
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition: thread.I:201
phidsdi.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
throw_event.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
winInputDeviceManager.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
winRawInputDevice.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.