Panda3D
winRawInputDevice.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 winRawInputDevice.cxx
10  * @author rdb
11  * @date 2018-01-19
12  */
13 
14 #include "winRawInputDevice.h"
15 #include "gamepadButton.h"
16 #include "mouseButton.h"
17 
18 #if defined(_WIN32) && !defined(CPPPARSER)
19 
20 #include <CfgMgr32.h>
21 #include <devpkey.h>
22 
23 // Copy definitions from hidusage.h, until we can drop support for the 7.1 SDK
24 typedef USHORT USAGE, *PUSAGE;
25 
26 #define HID_USAGE_PAGE_UNDEFINED ((USAGE) 0x00)
27 #define HID_USAGE_PAGE_GENERIC ((USAGE) 0x01)
28 #define HID_USAGE_PAGE_SIMULATION ((USAGE) 0x02)
29 #define HID_USAGE_PAGE_VR ((USAGE) 0x03)
30 #define HID_USAGE_PAGE_SPORT ((USAGE) 0x04)
31 #define HID_USAGE_PAGE_GAME ((USAGE) 0x05)
32 #define HID_USAGE_PAGE_KEYBOARD ((USAGE) 0x07)
33 #define HID_USAGE_PAGE_LED ((USAGE) 0x08)
34 #define HID_USAGE_PAGE_BUTTON ((USAGE) 0x09)
35 
36 #define HID_USAGE_GENERIC_POINTER ((USAGE) 0x01)
37 #define HID_USAGE_GENERIC_MOUSE ((USAGE) 0x02)
38 #define HID_USAGE_GENERIC_JOYSTICK ((USAGE) 0x04)
39 #define HID_USAGE_GENERIC_GAMEPAD ((USAGE) 0x05)
40 #define HID_USAGE_GENERIC_KEYBOARD ((USAGE) 0x06)
41 #define HID_USAGE_GENERIC_KEYPAD ((USAGE) 0x07)
42 #define HID_USAGE_GENERIC_SYSTEM_CTL ((USAGE) 0x80)
43 
44 #define HID_USAGE_GENERIC_X ((USAGE) 0x30)
45 #define HID_USAGE_GENERIC_Y ((USAGE) 0x31)
46 #define HID_USAGE_GENERIC_Z ((USAGE) 0x32)
47 #define HID_USAGE_GENERIC_RX ((USAGE) 0x33)
48 #define HID_USAGE_GENERIC_RY ((USAGE) 0x34)
49 #define HID_USAGE_GENERIC_RZ ((USAGE) 0x35)
50 #define HID_USAGE_GENERIC_SLIDER ((USAGE) 0x36)
51 #define HID_USAGE_GENERIC_DIAL ((USAGE) 0x37)
52 #define HID_USAGE_GENERIC_WHEEL ((USAGE) 0x38)
53 #define HID_USAGE_GENERIC_HATSWITCH ((USAGE) 0x39)
54 
55 // Copy definitions from hidpi.h, until we can drop support for the 7.1 SDK
56 #define HIDP_STATUS_SUCCESS ((NTSTATUS)(0x11 << 16))
57 
58 typedef enum _HIDP_REPORT_TYPE {
59  HidP_Input,
60  HidP_Output,
61  HidP_Feature
62 } HIDP_REPORT_TYPE;
63 
64 typedef struct _HIDP_BUTTON_CAPS {
65  USAGE UsagePage;
66  UCHAR ReportID;
67  BOOLEAN IsAlias;
68  USHORT BitField;
69  USHORT LinkCollection;
70  USAGE LinkUsage;
71  USAGE LinkUsagePage;
72  BOOLEAN IsRange;
73  BOOLEAN IsStringRange;
74  BOOLEAN IsDesignatorRange;
75  BOOLEAN IsAbsolute;
76  ULONG Reserved[10];
77  union {
78  struct {
79  USAGE UsageMin, UsageMax;
80  USHORT StringMin, StringMax;
81  USHORT DesignatorMin, DesignatorMax;
82  USHORT DataIndexMin, DataIndexMax;
83  } Range;
84  struct {
85  USAGE Usage, Reserved1;
86  USHORT StringIndex, Reserved2;
87  USHORT DesignatorIndex, Reserved3;
88  USHORT DataIndex, Reserved4;
89  } NotRange;
90  };
91 } HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS;
92 
93 typedef struct _HIDP_VALUE_CAPS {
94  USAGE UsagePage;
95  UCHAR ReportID;
96  BOOLEAN IsAlias;
97  USHORT BitField;
98  USHORT LinkCollection;
99  USAGE LinkUsage;
100  USAGE LinkUsagePage;
101  BOOLEAN IsRange;
102  BOOLEAN IsStringRange;
103  BOOLEAN IsDesignatorRange;
104  BOOLEAN IsAbsolute;
105  BOOLEAN HasNull;
106  UCHAR Reserved;
107  USHORT BitSize;
108  USHORT ReportCount;
109  USHORT Reserved2[5];
110  ULONG UnitsExp;
111  ULONG Units;
112  LONG LogicalMin, LogicalMax;
113  LONG PhysicalMin, PhysicalMax;
114  union {
115  struct {
116  USAGE UsageMin, UsageMax;
117  USHORT StringMin, StringMax;
118  USHORT DesignatorMin, DesignatorMax;
119  USHORT DataIndexMin, DataIndexMax;
120  } Range;
121  struct {
122  USAGE Usage, Reserved1;
123  USHORT StringIndex, Reserved2;
124  USHORT DesignatorIndex, Reserved3;
125  USHORT DataIndex, Reserved4;
126  } NotRange;
127  };
128 } HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS;
129 
130 typedef PUCHAR PHIDP_REPORT_DESCRIPTOR;
131 typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
132 
133 typedef struct _HIDP_CAPS {
134  USAGE Usage;
135  USAGE UsagePage;
136  USHORT InputReportByteLength;
137  USHORT OutputReportByteLength;
138  USHORT FeatureReportByteLength;
139  USHORT Reserved[17];
140  USHORT NumberLinkCollectionNodes;
141  USHORT NumberInputButtonCaps;
142  USHORT NumberInputValueCaps;
143  USHORT NumberInputDataIndices;
144  USHORT NumberOutputButtonCaps;
145  USHORT NumberOutputValueCaps;
146  USHORT NumberOutputDataIndices;
147  USHORT NumberFeatureButtonCaps;
148  USHORT NumberFeatureValueCaps;
149  USHORT NumberFeatureDataIndices;
150 } HIDP_CAPS, *PHIDP_CAPS;
151 
152 typedef struct _HIDP_DATA {
153  USHORT DataIndex;
154  USHORT Reserved;
155  union {
156  ULONG RawValue;
157  BOOLEAN On;
158  };
159 } HIDP_DATA, *PHIDP_DATA;
160 
161 typedef LONG NTSTATUS;
162 typedef NTSTATUS (*pHidP_GetCaps)(PHIDP_PREPARSED_DATA, PHIDP_CAPS);
163 typedef NTSTATUS (*pHidP_GetButtonCaps)(HIDP_REPORT_TYPE, PHIDP_BUTTON_CAPS, PUSHORT, PHIDP_PREPARSED_DATA);
164 typedef NTSTATUS (*pHidP_GetValueCaps)(HIDP_REPORT_TYPE, PHIDP_VALUE_CAPS, PUSHORT, PHIDP_PREPARSED_DATA);
165 typedef NTSTATUS (*pHidP_GetData)(HIDP_REPORT_TYPE, PHIDP_DATA, PULONG, PHIDP_PREPARSED_DATA, PCHAR, ULONG);
166 typedef ULONG (*pHidP_MaxDataListLength)(HIDP_REPORT_TYPE, PHIDP_PREPARSED_DATA);
167 
168 static pHidP_GetCaps _HidP_GetCaps = nullptr;
169 static pHidP_GetButtonCaps _HidP_GetButtonCaps = nullptr;
170 static pHidP_GetValueCaps _HidP_GetValueCaps = nullptr;
171 static pHidP_GetData _HidP_GetData = nullptr;
172 static pHidP_MaxDataListLength _HidP_MaxDataListLength = nullptr;
173 
174 /**
175  * Static method to initialize the HID parser library. We load it dynamically
176  * because the Windows 7.1 SDK doesn't ship hid.lib.
177  */
178 static bool init_hidp() {
179  HMODULE module = LoadLibraryA("hid.dll");
180  if (module) {
181  if (device_cat.is_debug()) {
182  device_cat.debug()
183  << "Successfully loaded hid.dll\n";
184  }
185 
186  _HidP_GetCaps = (pHidP_GetCaps)GetProcAddress(module, "HidP_GetCaps");
187  _HidP_GetButtonCaps = (pHidP_GetButtonCaps)GetProcAddress(module, "HidP_GetButtonCaps");
188  _HidP_GetValueCaps = (pHidP_GetValueCaps)GetProcAddress(module, "HidP_GetValueCaps");
189  _HidP_GetData = (pHidP_GetData)GetProcAddress(module, "HidP_GetData");
190  _HidP_MaxDataListLength = (pHidP_MaxDataListLength)GetProcAddress(module, "HidP_MaxDataListLength");
191 
192  if (_HidP_GetCaps == nullptr || _HidP_GetButtonCaps == nullptr ||
193  _HidP_GetValueCaps == nullptr || _HidP_GetData == nullptr ||
194  _HidP_MaxDataListLength == nullptr) {
195  device_cat.error()
196  << "Failed to locate function pointers in hid.dll\n";
197  return false;
198  }
199 
200  return true;
201  }
202 
203  device_cat.error()
204  << "Failed to load hid.dll.\n";
205  return false;
206 }
207 
208 /**
209  * Protected constructor. Given a raw device handle.
210  */
211 WinRawInputDevice::
212 WinRawInputDevice(WinInputDeviceManager *manager, const char *path) :
213  _manager(manager),
214  _path(path),
215  _max_data_count(0),
216  _preparsed(nullptr) {
217 }
218 
219 /**
220  *
221  */
222 WinRawInputDevice::
223 ~WinRawInputDevice() {
224  // Unregister the device from the manager.
225  LightMutexHolder holder(_lock);
226  if (_manager != nullptr) {
227  _manager->device_destroyed(this);
228  }
229  if (_preparsed != nullptr) {
230  free(_preparsed);
231  _preparsed = nullptr;
232  }
233 }
234 
235 /**
236  * Called by InputDeviceManager when this device is connected. Returns true
237  * if the device was connected successfully.
238  */
239 bool WinRawInputDevice::
240 on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
241  using std::hex;
242  using std::dec;
243  using std::swap;
244 
245  LightMutexHolder holder(_lock);
246 
247  _name = std::move(name);
248 
249  switch (info.dwType) {
250  case RIM_TYPEMOUSE:
251  _device_class = DeviceClass::mouse;
252  break;
253 
254  case RIM_TYPEKEYBOARD:
255  _device_class = DeviceClass::keyboard;
256  break;
257 
258  case RIM_TYPEHID:
259  _vendor_id = info.hid.dwVendorId;
260  _product_id = info.hid.dwProductId;
261 
262  // Gamepads
263  if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
264  info.hid.usUsage == HID_USAGE_GENERIC_GAMEPAD) {
265  _device_class = DeviceClass::gamepad;
266 
267  // Flight sticks
268  } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
269  info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) {
270  _device_class = DeviceClass::flight_stick;
271 
272  if (_name == "usb gamepad") {
273  // Well, it claims to be a gamepad...
274  _device_class = DeviceClass::gamepad;
275  }
276  //TODO: better solution for this
277  if (_vendor_id == 0x0079 && _product_id == 0x0006) {
278  // Trust GXT 24
279  _device_class = DeviceClass::gamepad;
280  }
281 
282  // Mice
283  } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
284  info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) {
285  _device_class = DeviceClass::mouse;
286 
287  // Keyboards
288  } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
289  info.hid.usUsage == HID_USAGE_GENERIC_KEYBOARD) {
290  _device_class = DeviceClass::keyboard;
291 
292  // 3Dconnexion SpaceNavigator and friends.
293  } else if (_vendor_id == 0x046d &&
294  (_product_id == 0xc623 ||
295  _product_id == 0xc625 ||
296  _product_id == 0xc626 ||
297  _product_id == 0xc627 ||
298  _product_id == 0xc628 ||
299  _product_id == 0xc629 ||
300  _product_id == 0xc62b)) {
301  _device_class = DeviceClass::spatial_mouse;
302  }
303  break;
304 
305  default:
306  return false;
307  }
308 
309  // Initialize hid.dll, which provides the HID parser functions.
310  static bool hid_initialized = false;
311  if (!hid_initialized) {
312  if (!init_hidp()) {
313  return false;
314  }
315  hid_initialized = true;
316  }
317 
318  // Get the "preparsed data", which we can parse with the HID parser API.
319  UINT size = 0;
320  if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) < 0) {
321  return false;
322  }
323 
324  PHIDP_PREPARSED_DATA buffer = (PHIDP_PREPARSED_DATA)malloc(size);
325  if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, buffer, &size) <= 0) {
326  return false;
327  }
328  _preparsed = buffer;
329 
330  HIDP_CAPS caps;
331  if (_HidP_GetCaps(buffer, &caps) != HIDP_STATUS_SUCCESS) {
332  device_cat.warning()
333  << "Failed to get capabilities from HID preparsed data.\n";
334  return false;
335  }
336 
337  if (device_cat.is_debug()) {
338  device_cat.debug()
339  << "Found " << _device_class << " device \"" << _name << "\" with "
340  << caps.NumberInputDataIndices << " data indices, "
341  << caps.NumberInputButtonCaps << " button caps, "
342  << caps.NumberInputValueCaps << " value caps\n";
343  }
344 
345  // Do we have a button mapping?
346  static const ButtonHandle gamepad_buttons_common[] = {
347  ButtonHandle::none(),
348  GamepadButton::face_a(),
349  GamepadButton::face_b(),
350  GamepadButton::face_x(),
351  GamepadButton::face_y(),
352  GamepadButton::lshoulder(),
353  GamepadButton::rshoulder(),
354  GamepadButton::start(),
355  GamepadButton::back(),
356  GamepadButton::lstick(),
357  GamepadButton::rstick(),
358  };
359  const ButtonHandle *gamepad_buttons = gamepad_buttons_common;
360  if (_vendor_id == 0x0810 && _product_id == 0xe501) {
361  // SNES-style USB gamepad
362  static const ButtonHandle gamepad_buttons_snes[] = {
363  ButtonHandle::none(),
364  GamepadButton::face_x(),
365  GamepadButton::face_a(),
366  GamepadButton::face_b(),
367  GamepadButton::face_y(),
368  GamepadButton::lshoulder(),
369  GamepadButton::rshoulder(),
370  ButtonHandle::none(),
371  ButtonHandle::none(),
372  GamepadButton::back(),
373  GamepadButton::start(),
374  };
375  gamepad_buttons = gamepad_buttons_snes;
376  }
377 
378  // Prepare a mapping of data indices to button/axis indices.
379  _indices.resize(caps.NumberInputDataIndices);
380 
381  _buttons.clear();
382  _axes.clear();
383 
384  USHORT num_button_caps = caps.NumberInputButtonCaps;
385  PHIDP_BUTTON_CAPS button_caps;
386  if (num_button_caps > 0u) {
387  button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS));
388  _HidP_GetButtonCaps(HidP_Input, button_caps, &num_button_caps, buffer);
389  }
390 
391  for (USHORT i = 0; i < num_button_caps; ++i) {
392  HIDP_BUTTON_CAPS &cap = button_caps[i];
393  int upper = 0;
394  if (cap.IsRange) {
395  upper = (cap.Range.UsageMax - cap.Range.UsageMin);
396 
397  if (device_cat.is_debug()) {
398  device_cat.debug()
399  << "Found button range: DataIndex=" << dec
400  << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax
401  << ", ReportID=" << (int)cap.ReportID
402  << ", UsagePage=0x" << hex << cap.UsagePage
403  << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax
404  << dec << "\n";
405  }
406  } else {
407  if (device_cat.is_debug()) {
408  device_cat.debug()
409  << "Found button: DataIndex=" << dec << cap.NotRange.DataIndex
410  << ", ReportID=" << (int)cap.ReportID
411  << ", UsagePage=0x" << hex << cap.UsagePage
412  << ", Usage=0x" << cap.NotRange.Usage
413  << dec << "\n";
414  }
415  }
416 
417  nassertd(cap.Range.DataIndexMin + upper < _indices.size()) continue;
418 
419  // Windows will only tell us which buttons in a report are "on", so we
420  // need to keep track of which buttons exist in which report so that we
421  // can figure out which ones are off.
422  if (cap.ReportID >= _report_buttons.size()) {
423  _report_buttons.resize(cap.ReportID + 1);
424  }
425  for (int j = 0; j <= upper; ++j) {
426  USAGE usage = j + cap.Range.UsageMin;
427  USHORT data_index = j + cap.Range.DataIndexMin;
428  ButtonHandle handle = ButtonHandle::none();
429  switch (cap.UsagePage) {
430  case HID_USAGE_PAGE_BUTTON:
431  if (_device_class == DeviceClass::gamepad) {
432  if (usage < sizeof(gamepad_buttons_common) / sizeof(ButtonHandle)) {
433  handle = gamepad_buttons[usage];
434  }
435  } else if (_device_class == DeviceClass::flight_stick) {
436  if (usage > 0) {
437  handle = GamepadButton::joystick(usage - 1);
438  }
439  } else if (_device_class == DeviceClass::mouse) {
440  // In Panda, wheel and right button are flipped around...
441  int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1);
442  handle = MouseButton::button(button);
443  }
444  break;
445 
446  default:
447  continue;
448  }
449 
450  int button_index = _buttons.size();
451  _report_buttons[cap.ReportID].set_bit(button_index);
452  _indices[data_index] = Index::button(button_index);
453  _buttons.push_back(ButtonState(handle));
454  }
455  }
456 
457  USHORT num_value_caps = caps.NumberInputValueCaps;
458  PHIDP_VALUE_CAPS value_caps;
459  if (num_value_caps > 0u) {
460  value_caps = (PHIDP_VALUE_CAPS)alloca(num_value_caps * sizeof(HIDP_VALUE_CAPS));
461  _HidP_GetValueCaps(HidP_Input, value_caps, &num_value_caps, buffer);
462  }
463 
464  _hat_data_index = -1;
465 
466  for (USHORT i = 0; i < num_value_caps; ++i) {
467  HIDP_VALUE_CAPS &cap = value_caps[i];
468  int upper = 0;
469  if (cap.IsRange) {
470  upper = (cap.Range.UsageMax - cap.Range.UsageMin);
471 
472  if (device_cat.is_debug()) {
473  device_cat.debug()
474  << "Found value range: DataIndex=" << dec
475  << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax
476  << ", ReportID=" << (int)cap.ReportID
477  << ", UsagePage=0x" << hex << cap.UsagePage
478  << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax
479  << dec << ", LogicalMin=" << cap.LogicalMin
480  << ", LogicalMax=" << cap.LogicalMax << "\n";
481  }
482  } else {
483  if (device_cat.is_debug()) {
484  device_cat.debug()
485  << "Found value: DataIndex=" << dec << cap.NotRange.DataIndex
486  << ", ReportID=" << (int)cap.ReportID
487  << ", UsagePage=0x" << hex << cap.UsagePage
488  << ", Usage=0x" << cap.NotRange.Usage
489  << dec << ", LogicalMin=" << cap.LogicalMin
490  << ", LogicalMax=" << cap.LogicalMax << "\n";
491  }
492  }
493 
494  nassertd(cap.Range.DataIndexMin + upper < _indices.size()) continue;
495 
496  for (int j = 0; j <= upper; ++j) {
497  USAGE usage = j + cap.Range.UsageMin;
498  USHORT data_index = j + cap.Range.DataIndexMin;
499  bool is_signed = true;
500 
501  // My gamepads give this odd invalid range.
502  if (cap.LogicalMin == 0 && cap.LogicalMax == -1) {
503  cap.LogicalMax = 65535;
504  is_signed = false;
505  }
506 
507  Axis axis = Axis::none;
508  switch (cap.UsagePage) {
509  case HID_USAGE_PAGE_GENERIC:
510  switch (usage) {
511  case HID_USAGE_GENERIC_X:
512  if (_device_class == DeviceClass::gamepad) {
513  axis = Axis::left_x;
514  } else if (_device_class == DeviceClass::flight_stick) {
515  axis = Axis::roll;
516  } else {
517  axis = Axis::x;
518  }
519  break;
520  case HID_USAGE_GENERIC_Y:
521  if (_device_class == DeviceClass::gamepad) {
522  axis = Axis::left_y;
523  swap(cap.LogicalMin, cap.LogicalMax);
524  } else if (_device_class == DeviceClass::flight_stick) {
525  axis = Axis::pitch;
526  } else {
527  axis = Axis::y;
528  swap(cap.LogicalMin, cap.LogicalMax);
529  }
530  break;
531  case HID_USAGE_GENERIC_Z:
532  if (_device_class == DeviceClass::gamepad) {
533  axis = Axis::left_trigger;
534  } else if (_device_class == DeviceClass::flight_stick) {
535  axis = Axis::throttle;
536  } else {
537  axis = Axis::z;
538  swap(cap.LogicalMin, cap.LogicalMax);
539  }
540  break;
541  case HID_USAGE_GENERIC_RX:
542  if (_device_class == DeviceClass::gamepad) {
543  axis = Axis::right_x;
544  } else {
545  axis = Axis::pitch;
546  }
547  break;
548  case HID_USAGE_GENERIC_RY:
549  if (_device_class == DeviceClass::gamepad) {
550  axis = Axis::right_y;
551  } else {
552  axis = Axis::roll;
553  }
554  swap(cap.LogicalMin, cap.LogicalMax);
555  break;
556  case HID_USAGE_GENERIC_RZ:
557  if (_device_class == DeviceClass::gamepad) {
558  axis = Axis::right_trigger;
559  } else {
560  // Flip to match Panda's convention for heading.
561  axis = Axis::yaw;
562  swap(cap.LogicalMin, cap.LogicalMax);
563  }
564  break;
565  case HID_USAGE_GENERIC_SLIDER:
566  // Flip to match Panda's convention for heading.
567  axis = Axis::rudder;
568  swap(cap.LogicalMin, cap.LogicalMax);
569  break;
570  case HID_USAGE_GENERIC_WHEEL:
571  axis = Axis::wheel;
572  break;
573  case HID_USAGE_GENERIC_HATSWITCH:
574  // This is handled specially.
575  _hat_data_index = data_index;
576  _hat_data_minimum = cap.LogicalMin;
577  continue;
578  }
579  break;
580  }
581 
582  int axis_index;
583  if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) {
584  // T.Flight Hotas X throttle is reversed and can go backwards.
585  axis_index = add_axis(axis, cap.LogicalMax, cap.LogicalMin, true);
586  } else if (!is_signed) {
587  // All axes on the weird XInput-style mappings go from -1 to 1
588  axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax, true);
589  } else {
590  axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax);
591  }
592  _indices[data_index] = Index::axis(axis_index, is_signed);
593  }
594  }
595 
596  // Do we need to emulate a hat switch or directional pad?
597  if (_hat_data_index != -1) {
598  _hat_left_button = (int)_buttons.size();
599  if (_device_class == DeviceClass::gamepad) {
600  _buttons.push_back(ButtonState(GamepadButton::dpad_left()));
601  _buttons.push_back(ButtonState(GamepadButton::dpad_right()));
602  _buttons.push_back(ButtonState(GamepadButton::dpad_down()));
603  _buttons.push_back(ButtonState(GamepadButton::dpad_up()));
604  } else {
605  _buttons.push_back(ButtonState(GamepadButton::hat_left()));
606  _buttons.push_back(ButtonState(GamepadButton::hat_right()));
607  _buttons.push_back(ButtonState(GamepadButton::hat_down()));
608  _buttons.push_back(ButtonState(GamepadButton::hat_up()));
609  }
610  }
611 
612  _max_data_count = _HidP_MaxDataListLength(HidP_Input, buffer);
613 
614  nassertr_always(_max_data_count >= 0, false);
615 
616  _handle = handle;
617  _is_connected = true;
618  return true;
619 }
620 
621 /**
622  * Called by InputDeviceManager when this device is removed.
623  */
624 void WinRawInputDevice::
625 on_removal() {
626  LightMutexHolder holder(_lock);
627  _is_connected = false;
628  _handle = nullptr;
629  if (_preparsed != nullptr) {
630  free(_preparsed);
631  _preparsed = nullptr;
632  }
633  _indices.clear();
634  _report_buttons.clear();
635 }
636 
637 /**
638  * Called by InputDeviceManager when raw input is received for this device.
639  */
640 void WinRawInputDevice::
641 on_input(PRAWINPUT input) {
642  nassertv(input != nullptr);
643  nassertv(_preparsed != nullptr);
644 
645  if (_max_data_count == 0) {
646  return;
647  }
648 
649  BYTE *ptr = input->data.hid.bRawData;
650  if (input->data.hid.dwSizeHid == 0) {
651  return;
652  }
653 
654  LightMutexHolder holder(_lock);
655 
656  if (device_cat.is_spam()) {
657  device_cat.spam()
658  << _name << " received " << input->data.hid.dwCount << " reports of size "
659  << input->data.hid.dwSizeHid << "\n";
660  }
661 
662  for (DWORD i = 0; i < input->data.hid.dwCount; ++i) {
663  process_report((PCHAR)ptr, input->data.hid.dwSizeHid);
664  ptr += input->data.hid.dwSizeHid;
665  }
666 }
667 
668 /**
669  * Processes a single HID report. Assumes the lock is held.
670  */
671 void WinRawInputDevice::
672 process_report(PCHAR ptr, size_t size) {
673  // The first byte is the report identifier. We need it to figure out which
674  // buttons are off, since each report only contains the "on" buttons.
675  UCHAR report_id = ptr[0];
676  BitArray unset_buttons;
677 
678  if (report_id < _report_buttons.size()) {
679  unset_buttons = _report_buttons[report_id];
680  }
681 
682  PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count);
683  nassertv(data != nullptr);
684 
685  ULONG count = _max_data_count;
686  NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, ptr, size);
687  if (status == HIDP_STATUS_SUCCESS) {
688  for (ULONG di = 0; di < count; ++di) {
689  if (data[di].DataIndex != _hat_data_index) {
690  nassertd(data[di].DataIndex < _indices.size()) continue;
691  const Index &idx = _indices[data[di].DataIndex];
692  if (idx._axis >= 0) {
693  if (idx._signed) {
694  axis_changed(idx._axis, (SHORT)data[di].RawValue);
695  } else {
696  axis_changed(idx._axis, data[di].RawValue);
697  }
698  }
699  if (idx._button >= 0) {
700  unset_buttons.clear_bit(idx._button);
701  button_changed(idx._button, (data[di].On != FALSE));
702  }
703  } else {
704  int value = (int)data[di].RawValue - _hat_data_minimum;
705  button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left
706  button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right
707  button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down
708  button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up
709  }
710  }
711 
712  // Now unset the buttons in this report that aren't pressed.
713  int button_index = unset_buttons.get_lowest_on_bit();
714  while (button_index >= 0) {
715  button_changed(button_index, false);
716  unset_buttons.clear_bit(button_index);
717  button_index = unset_buttons.get_lowest_on_bit();
718  }
719  } else if (device_cat.is_spam()) {
720  device_cat.spam()
721  << "Failed to get data from raw device " << _path
722  << " (error 0x" << std::hex << (status & 0xffffffffu) << std::dec << ")\n";
723  }
724 }
725 
726 /**
727  * Polls the input device for new activity, to ensure it contains the latest
728  * events. This will only have any effect for some types of input devices;
729  * others may be updated automatically, and this method will be a no-op.
730  */
731 void WinRawInputDevice::
732 do_poll() {
733 }
734 
735 #endif // _WIN32
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
void clear_bit(int index)
Sets the nth bit off.
Definition: bitArray.I:133
A dynamic array with an unlimited number of bits.
Definition: bitArray.h:39
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static ButtonHandle button(int button_number)
Returns the ButtonHandle associated with the particular numbered mouse button (zero-based),...
Definition: mouseButton.cxx:32
Similar to MutexHolder, but for a light mutex.
int get_lowest_on_bit() const
Returns the index of the lowest 1 bit in the array.
Definition: bitArray.cxx:332
static ButtonHandle joystick(int button_number)
Returns the ButtonHandle associated with the particular numbered joystick button (zero-based),...