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