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
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
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 {
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.
Definition: textEncoder.I:481
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.