Panda3D
Loading...
Searching...
No Matches
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
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 */
25class InputThread : public Thread {
26public:
27 InputThread(WinInputDeviceManager *manager) :
28 Thread("input", "input"), _manager(manager) {}
29
30private:
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 */
42WinInputDeviceManager::
43WinInputDeviceManager() :
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
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 */
83WinInputDeviceManager::
84~WinInputDeviceManager() {
85 if (_message_hwnd != nullptr) {
86#ifdef HAVE_THREADS
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 */
103void WinInputDeviceManager::
104device_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 */
117void WinInputDeviceManager::
118on_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 */
146void WinInputDeviceManager::
147on_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 */
327void WinInputDeviceManager::
328on_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 */
359void WinInputDeviceManager::
360update() {
361}
362
363/**
364 * Sets up a Windows message loop. Should be called from the thread that will
365 * be handling the messages.
366 */
367HWND WinInputDeviceManager::
368setup_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 */
459void WinInputDeviceManager::
460destroy_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 */
475void WinInputDeviceManager::
476stop_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 */
492LRESULT WINAPI WinInputDeviceManager::
493window_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 */
531void InputThread::
532thread_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 {
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
static InputDeviceManager * get_global_ptr()
Returns the singleton InputDeviceManager instance.
Similar to MutexHolder, but for a light mutex.
This class can be used to convert text between multiple representations, e.g.
Definition textEncoder.h:33
std::string encode_wtext(const std::wstring &wtext) const
Encodes a wide-text string into a single-char string, according to the current encoding.
A thread; that is, a lightweight process.
Definition thread.h:46
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
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition thread.I:201
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.