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
LightMutexHolder
Similar to MutexHolder, but for a light mutex.
Definition: lightMutexHolder.h:25
throw_event.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
InputDeviceManager::get_global_ptr
static InputDeviceManager * get_global_ptr()
Returns the singleton InputDeviceManager instance.
Definition: inputDeviceManager.I:18
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
winInputDeviceManager.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
winRawInputDevice.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
TextEncoder
This class can be used to convert text between multiple representations, e.g.
Definition: textEncoder.h:33
Thread
A thread; that is, a lightweight process.
Definition: thread.h:46
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.