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