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