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