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  // Do we have a button mapping?
338  static const ButtonHandle gamepad_buttons_common[] = {
339  ButtonHandle::none(),
340  GamepadButton::face_a(),
341  GamepadButton::face_b(),
342  GamepadButton::face_x(),
343  GamepadButton::face_y(),
344  GamepadButton::lshoulder(),
345  GamepadButton::rshoulder(),
346  GamepadButton::start(),
347  GamepadButton::back(),
348  GamepadButton::lstick(),
349  GamepadButton::rstick(),
350  };
351  const ButtonHandle *gamepad_buttons = gamepad_buttons_common;
352  if (_vendor_id == 0x0810 && _product_id == 0xe501) {
353  // SNES-style USB gamepad
354  static const ButtonHandle gamepad_buttons_snes[] = {
355  ButtonHandle::none(),
356  GamepadButton::face_x(),
357  GamepadButton::face_a(),
358  GamepadButton::face_b(),
359  GamepadButton::face_y(),
360  GamepadButton::lshoulder(),
361  GamepadButton::rshoulder(),
362  ButtonHandle::none(),
363  ButtonHandle::none(),
364  GamepadButton::back(),
365  GamepadButton::start(),
366  };
367  gamepad_buttons = gamepad_buttons_snes;
368  }
369 
370  // Prepare a mapping of data indices to button/axis indices.
371  _indices.resize(caps.NumberInputDataIndices);
372 
373  _buttons.clear();
374  _axes.clear();
375 
376  USHORT num_button_caps = caps.NumberInputButtonCaps;
377  PHIDP_BUTTON_CAPS button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS));
378  _HidP_GetButtonCaps(HidP_Input, button_caps, &num_button_caps, buffer);
379 
380  for (USHORT i = 0; i < num_button_caps; ++i) {
381  HIDP_BUTTON_CAPS &cap = button_caps[i];
382  int upper = 0;
383  if (cap.IsRange) {
384  upper = (cap.Range.UsageMax - cap.Range.UsageMin);
385 
386  if (device_cat.is_debug()) {
387  device_cat.debug()
388  << "Found button range: DataIndex=" << dec
389  << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax
390  << ", ReportID=" << (int)cap.ReportID
391  << ", UsagePage=0x" << hex << cap.UsagePage
392  << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax
393  << dec << "\n";
394  }
395  } else {
396  if (device_cat.is_debug()) {
397  device_cat.debug()
398  << "Found button: DataIndex=" << dec << cap.NotRange.DataIndex
399  << ", ReportID=" << dec << (int)cap.ReportID
400  << ", UsagePage=0x" << cap.UsagePage
401  << ", Usage=0x" << cap.NotRange.Usage
402  << dec << "\n";
403  }
404  }
405 
406  // Windows will only tell us which buttons in a report are "on", so we
407  // need to keep track of which buttons exist in which report so that we
408  // can figure out which ones are off.
409  if (cap.ReportID >= _report_buttons.size()) {
410  _report_buttons.resize(cap.ReportID + 1);
411  }
412  for (int j = 0; j <= upper; ++j) {
413  USAGE usage = j + cap.Range.UsageMin;
414  USHORT data_index = j + cap.Range.DataIndexMin;
415  ButtonHandle handle = ButtonHandle::none();
416  switch (cap.UsagePage) {
417  case HID_USAGE_PAGE_BUTTON:
418  if (_device_class == DeviceClass::gamepad) {
419  if (usage < sizeof(gamepad_buttons_common) / sizeof(ButtonHandle)) {
420  handle = gamepad_buttons[usage];
421  }
422  } else if (_device_class == DeviceClass::flight_stick) {
423  if (usage > 0) {
424  handle = GamepadButton::joystick(usage - 1);
425  }
426  } else if (_device_class == DeviceClass::mouse) {
427  // In Panda, wheel and right button are flipped around...
428  int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1);
429  handle = MouseButton::button(button);
430  }
431  break;
432  }
433 
434  int button_index = _buttons.size();
435  _report_buttons[cap.ReportID].set_bit(button_index);
436  _indices[data_index] = Index::button(button_index);
437  _buttons.push_back(ButtonState(handle));
438  }
439  }
440 
441  USHORT num_value_caps = caps.NumberInputValueCaps;
442  PHIDP_VALUE_CAPS value_caps = (PHIDP_VALUE_CAPS)alloca(num_value_caps * sizeof(HIDP_VALUE_CAPS));
443  _HidP_GetValueCaps(HidP_Input, value_caps, &num_value_caps, buffer);
444 
445  _hat_data_index = -1;
446 
447  for (USHORT i = 0; i < num_value_caps; ++i) {
448  HIDP_VALUE_CAPS &cap = value_caps[i];
449  int upper = 0;
450  if (cap.IsRange) {
451  upper = (cap.Range.UsageMax - cap.Range.UsageMin);
452 
453  if (device_cat.is_debug()) {
454  device_cat.debug()
455  << "Found value range: DataIndex=" << dec
456  << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax
457  << ", ReportID=" << (int)cap.ReportID
458  << ", UsagePage=0x" << hex << cap.UsagePage
459  << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax
460  << dec << ", LogicalMin=" << cap.LogicalMin
461  << ", LogicalMax=" << cap.LogicalMax << "\n";
462  }
463  } else {
464  if (device_cat.is_debug()) {
465  device_cat.debug()
466  << "Found value: DataIndex=" << dec << cap.NotRange.DataIndex
467  << ", ReportID=" << dec << (int)cap.ReportID
468  << ", UsagePage=0x" << hex << cap.UsagePage
469  << ", Usage=0x" << cap.NotRange.Usage
470  << dec << ", LogicalMin=" << cap.LogicalMin
471  << ", LogicalMax=" << cap.LogicalMax << "\n";
472  }
473  }
474 
475  for (int j = 0; j <= upper; ++j) {
476  USAGE usage = j + cap.Range.UsageMin;
477  USHORT data_index = j + cap.Range.DataIndexMin;
478  bool is_signed = true;
479 
480  // My gamepads give this odd invalid range.
481  if (cap.LogicalMin == 0 && cap.LogicalMax == -1) {
482  cap.LogicalMax = 65535;
483  is_signed = false;
484  }
485 
486  Axis axis = Axis::none;
487  switch (cap.UsagePage) {
488  case HID_USAGE_PAGE_GENERIC:
489  switch (usage) {
490  case HID_USAGE_GENERIC_X:
491  if (_device_class == DeviceClass::gamepad) {
492  axis = Axis::left_x;
493  } else if (_device_class == DeviceClass::flight_stick) {
494  axis = Axis::roll;
495  } else {
496  axis = Axis::x;
497  }
498  break;
499  case HID_USAGE_GENERIC_Y:
500  if (_device_class == DeviceClass::gamepad) {
501  axis = Axis::left_y;
502  swap(cap.LogicalMin, cap.LogicalMax);
503  } else if (_device_class == DeviceClass::flight_stick) {
504  axis = Axis::pitch;
505  } else {
506  axis = Axis::y;
507  swap(cap.LogicalMin, cap.LogicalMax);
508  }
509  break;
510  case HID_USAGE_GENERIC_Z:
511  if (_device_class == DeviceClass::gamepad) {
512  axis = Axis::left_trigger;
513  } else if (_device_class == DeviceClass::flight_stick) {
514  axis = Axis::throttle;
515  } else {
516  axis = Axis::z;
517  swap(cap.LogicalMin, cap.LogicalMax);
518  }
519  break;
520  case HID_USAGE_GENERIC_RX:
521  if (_device_class == DeviceClass::gamepad) {
522  axis = Axis::right_x;
523  } else {
524  axis = Axis::pitch;
525  }
526  break;
527  case HID_USAGE_GENERIC_RY:
528  if (_device_class == DeviceClass::gamepad) {
529  axis = Axis::right_y;
530  } else {
531  axis = Axis::roll;
532  }
533  swap(cap.LogicalMin, cap.LogicalMax);
534  break;
535  case HID_USAGE_GENERIC_RZ:
536  if (_device_class == DeviceClass::gamepad) {
537  axis = Axis::right_trigger;
538  } else {
539  // Flip to match Panda's convention for heading.
540  axis = Axis::yaw;
541  swap(cap.LogicalMin, cap.LogicalMax);
542  }
543  break;
544  case HID_USAGE_GENERIC_SLIDER:
545  // Flip to match Panda's convention for heading.
546  axis = Axis::rudder;
547  swap(cap.LogicalMin, cap.LogicalMax);
548  break;
549  case HID_USAGE_GENERIC_WHEEL:
550  axis = Axis::wheel;
551  break;
552  case HID_USAGE_GENERIC_HATSWITCH:
553  // This is handled specially.
554  _hat_data_index = data_index;
555  _hat_data_minimum = cap.LogicalMin;
556  continue;
557  }
558  break;
559  }
560 
561  int axis_index;
562  if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) {
563  // T.Flight Hotas X throttle is reversed and can go backwards.
564  axis_index = add_axis(axis, cap.LogicalMax, cap.LogicalMin, true);
565  } else if (!is_signed) {
566  // All axes on the weird XInput-style mappings go from -1 to 1
567  axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax, true);
568  } else {
569  axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax);
570  }
571  _indices[data_index] = Index::axis(axis_index, is_signed);
572  }
573  }
574 
575  // Do we need to emulate a hat switch or directional pad?
576  if (_hat_data_index != -1) {
577  _hat_left_button = (int)_buttons.size();
578  if (_device_class == DeviceClass::gamepad) {
579  _buttons.push_back(ButtonState(GamepadButton::dpad_left()));
580  _buttons.push_back(ButtonState(GamepadButton::dpad_right()));
581  _buttons.push_back(ButtonState(GamepadButton::dpad_down()));
582  _buttons.push_back(ButtonState(GamepadButton::dpad_up()));
583  } else {
584  _buttons.push_back(ButtonState(GamepadButton::hat_left()));
585  _buttons.push_back(ButtonState(GamepadButton::hat_right()));
586  _buttons.push_back(ButtonState(GamepadButton::hat_down()));
587  _buttons.push_back(ButtonState(GamepadButton::hat_up()));
588  }
589  }
590 
591  _max_data_count = _HidP_MaxDataListLength(HidP_Input, buffer);
592 
593  _handle = handle;
594  _is_connected = true;
595  return true;
596 }
597 
598 /**
599  * Called by InputDeviceManager when this device is removed.
600  */
601 void WinRawInputDevice::
602 on_removal() {
603  LightMutexHolder holder(_lock);
604  _is_connected = false;
605  _handle = nullptr;
606  if (_preparsed != nullptr) {
607  free(_preparsed);
608  _preparsed = nullptr;
609  }
610  _indices.clear();
611  _report_buttons.clear();
612 }
613 
614 /**
615  * Called by InputDeviceManager when raw input is received for this device.
616  */
617 void WinRawInputDevice::
618 on_input(PRAWINPUT input) {
619  nassertv(input != nullptr);
620  nassertv(_preparsed != nullptr);
621 
622  BYTE *ptr = input->data.hid.bRawData;
623  if (input->data.hid.dwSizeHid == 0) {
624  return;
625  }
626 
627  LightMutexHolder holder(_lock);
628 
629  for (DWORD i = 0; i < input->data.hid.dwCount; ++i) {
630  process_report((PCHAR)ptr, input->data.hid.dwSizeHid);
631  ptr += input->data.hid.dwSizeHid;
632  }
633 }
634 
635 /**
636  * Processes a single HID report. Assumes the lock is held.
637  */
638 void WinRawInputDevice::
639 process_report(PCHAR ptr, size_t size) {
640  // The first byte is the report identifier. We need it to figure out which
641  // buttons are off, since each report only contains the "on" buttons.
642  UCHAR report_id = ptr[0];
643  BitArray unset_buttons;
644 
645  if (report_id < _report_buttons.size()) {
646  unset_buttons = _report_buttons[report_id];
647  }
648 
649  PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count);
650  nassertv(data != nullptr);
651 
652  ULONG count = _max_data_count;
653  NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, ptr, size);
654  if (status == HIDP_STATUS_SUCCESS) {
655  for (ULONG di = 0; di < count; ++di) {
656  if (data[di].DataIndex != _hat_data_index) {
657  const Index &idx = _indices[data[di].DataIndex];
658  if (idx._axis >= 0) {
659  if (idx._signed) {
660  axis_changed(idx._axis, (SHORT)data[di].RawValue);
661  } else {
662  axis_changed(idx._axis, data[di].RawValue);
663  }
664  }
665  if (idx._button >= 0) {
666  unset_buttons.clear_bit(idx._button);
667  button_changed(idx._button, (data[di].On != FALSE));
668  }
669  } else {
670  int value = (int)data[di].RawValue - _hat_data_minimum;
671  button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left
672  button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right
673  button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down
674  button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up
675  }
676  }
677 
678  // Now unset the buttons in this report that aren't pressed.
679  int button_index = unset_buttons.get_lowest_on_bit();
680  while (button_index >= 0) {
681  button_changed(button_index, false);
682  unset_buttons.clear_bit(button_index);
683  button_index = unset_buttons.get_lowest_on_bit();
684  }
685  } else if (device_cat.is_spam()) {
686  device_cat.spam()
687  << "Failed to get data from raw device " << _path
688  << " (error 0x" << std::hex << (status & 0xffffffffu) << std::dec << ")\n";
689  }
690 }
691 
692 /**
693  * Polls the input device for new activity, to ensure it contains the latest
694  * events. This will only have any effect for some types of input devices;
695  * others may be updated automatically, and this method will be a no-op.
696  */
697 void WinRawInputDevice::
698 do_poll() {
699 }
700 
701 #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), if there is one, or ButtonHandle::none() if there is not.
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), if there is one, or ButtonHandle::none() if there is not.