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