Panda3D
xInputDevice.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 xInputDevice.cxx
10  * @author rdb
11  * @date 2015-07-15
12  */
13 
14 #include "xInputDevice.h"
15 
16 #if defined(_WIN32) && !defined(CPPPARSER)
17 
18 #include "gamepadButton.h"
19 #include "inputDeviceManager.h"
20 #include "string_utils.h"
21 
22 #include <XInput.h>
23 #include <CfgMgr32.h>
24 
25 #ifndef XUSER_MAX_COUNT
26 #define XUSER_MAX_COUNT 4
27 #endif
28 
29 #ifndef XINPUT_CAPS_FFB_SUPPORTED
30 #define XINPUT_CAPS_FFB_SUPPORTED 0x0001
31 #endif
32 #ifndef XINPUT_CAPS_NO_NAVIGATION
33 #define XINPUT_CAPS_NO_NAVIGATION 0x0010
34 #endif
35 
36 #ifndef BATTERY_DEVTYPE_GAMEPAD
37 #define BATTERY_DEVTYPE_GAMEPAD 0x00
38 #endif
39 
40 #ifndef XINPUT_DEVSUBTYPE_WHEEL
41 #define XINPUT_DEVSUBTYPE_WHEEL 0x02
42 #endif
43 #ifndef XINPUT_DEVSUBTYPE_ARCADE_STICK
44 #define XINPUT_DEVSUBTYPE_ARCADE_STICK 0x03
45 #endif
46 #ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK
47 #define XINPUT_DEVSUBTYPE_FLIGHT_STICK 0x04
48 #endif
49 #ifndef XINPUT_DEVSUBTYPE_DANCE_PAD
50 #define XINPUT_DEVSUBTYPE_DANCE_PAD 0x05
51 #endif
52 #ifndef XINPUT_DEVSUBTYPE_GUITAR
53 #define XINPUT_DEVSUBTYPE_GUITAR 0x06
54 #endif
55 #ifndef XINPUT_DEVSUBTYPE_DRUM_KIT
56 #define XINPUT_DEVSUBTYPE_DRUM_KIT 0x08
57 #endif
58 
59 #ifndef BATTERY_TYPE_DISCONNECTED
60 #define BATTERY_TYPE_DISCONNECTED 0x00
61 #endif
62 
63 #ifndef BATTERY_TYPE_WIRED
64 #define BATTERY_TYPE_WIRED 0x01
65 #endif
66 
67 #ifndef BATTERY_LEVEL_FULL
68 #define BATTERY_LEVEL_FULL 0x03
69 #endif
70 
71 typedef struct _XINPUT_BATTERY_INFORMATION {
72  BYTE BatteryType;
73  BYTE BatteryLevel;
74 } XINPUT_BATTERY_INFORMATION;
75 
76 // Undocumented, I figured out how this looks by trial and error.
77 typedef struct _XINPUT_BUSINFO {
78  WORD VendorID;
79  WORD ProductID;
80  WORD RevisionID;
81  WORD Unknown1; // Unknown - padding?
82  DWORD InstanceID;
83  DWORD Unknown2;
84  WORD Unknown3;
85 } XINPUT_BUSINFO;
86 
87 typedef struct _XINPUT_CAPABILITIES_EX {
88  BYTE Type;
89  BYTE SubType;
90  WORD Flags;
91  XINPUT_GAMEPAD Gamepad;
92  XINPUT_VIBRATION Vibration;
93 
94  // The following fields are undocumented.
95  WORD VendorID;
96  WORD ProductID;
97  WORD RevisionID;
98  WORD Unknown1;
99  WORD Unknown2;
100 } XINPUT_CAPABILITIES_EX;
101 
102 typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *);
103 typedef DWORD (*pXInputSetState)(DWORD, XINPUT_VIBRATION *);
104 typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *);
105 typedef DWORD (*pXInputGetCapabilitiesEx)(DWORD, DWORD, DWORD, XINPUT_CAPABILITIES_EX *);
106 typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *);
107 typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *);
108 
109 static pXInputGetState get_state = nullptr;
110 static pXInputSetState set_state = nullptr;
111 static pXInputGetCapabilities get_capabilities = nullptr;
112 static pXInputGetCapabilitiesEx get_capabilities_ex = nullptr;
113 static pXInputGetBatteryInformation get_battery_information = nullptr;
114 static pXInputGetBaseBusInformation get_base_bus_information = nullptr;
115 
116 bool XInputDevice::_initialized = false;
117 
118 /**
119  * Protected constructor. user_index is a number 0-3.
120  */
121 XInputDevice::
122 XInputDevice(DWORD user_index) :
123  _index(user_index),
124  _last_packet(-1),
125  _last_buttons(0) {
126 
127  nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT);
128 
129  _axes.resize(6);
130  _buttons.resize(16);
131 }
132 
133 /**
134  *
135  */
136 XInputDevice::
137 ~XInputDevice() {
138  do_set_vibration(0, 0);
139 }
140 
141 /**
142  * Called when a new input device arrives in the InputDeviceManager. This
143  * method checks whether it matches this XInput device.
144  */
145 bool XInputDevice::
146 check_arrival(const RID_DEVICE_INFO &info, DEVINST inst,
147  const std::string &name, const std::string &manufacturer) {
148  LightMutexHolder holder(_lock);
149  if (_is_connected) {
150  return false;
151  }
152 
153  if (!_initialized) {
154  nassertr_always(init_xinput(), false);
155  }
156 
157  XINPUT_CAPABILITIES_EX caps = {0};
158  XINPUT_STATE state;
159  if ((get_capabilities_ex && get_capabilities_ex(1, _index, 0, &caps) != ERROR_SUCCESS) &&
160  get_capabilities(_index, 0, (XINPUT_CAPABILITIES *)&caps) != ERROR_SUCCESS) {
161  return false;
162  }
163 
164  if (get_state(_index, &state) != ERROR_SUCCESS) {
165  return false;
166  }
167 
168  // Extra check for VID/PID if we have it, just to be sure.
169  if ((caps.VendorID != 0 && caps.VendorID != info.hid.dwVendorId) ||
170  (caps.ProductID != 0 && caps.ProductID != info.hid.dwProductId)) {
171  return false;
172  }
173 
174  // Yes, take the name and manufacturer.
175  if (!name.empty()) {
176  _name = name;
177  } else {
178  _name = "XInput Device #";
179  _name += format_string(_index + 1);
180  }
181  _manufacturer = manufacturer;
182 
183  if (inst && caps.ProductID == 0 && caps.RevisionID != 0) {
184  // XInput does not report a product ID for the Xbox 360 wireless adapter.
185  // Instead, we check that the RevisionID matches.
186  char buffer[4096];
187  ULONG buflen = sizeof(buffer);
188  if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_HARDWAREID, 0, buffer, &buflen, 0) == CR_SUCCESS) {
189  std::string ids(buffer, buflen);
190  char revstr[16];
191  sprintf(revstr, "REV_%04x", caps.RevisionID);
192  if (ids.find(revstr) == std::string::npos) {
193  return false;
194  }
195  }
196  }
197 
198  _is_connected = true;
199  init_device(caps, state);
200  _vendor_id = info.hid.dwVendorId;
201  _product_id = info.hid.dwProductId;
202  return true;
203 }
204 
205 /**
206  * Called periodically by the InputDeviceManager to detect whether the device
207  * is currently connected.
208  * Returns true if the device wasn't connected, but now is.
209  */
210 void XInputDevice::
211 detect(InputDeviceManager *mgr) {
212  if (!_initialized) {
213  nassertv_always(init_xinput());
214  }
215 
216  bool connected = false;
217 
218  XINPUT_CAPABILITIES_EX caps = {0};
219  XINPUT_STATE state;
220  if (((get_capabilities_ex && get_capabilities_ex(1, _index, 0, &caps) == ERROR_SUCCESS) ||
221  get_capabilities(_index, 0, (XINPUT_CAPABILITIES *)&caps) == ERROR_SUCCESS) &&
222  get_state(_index, &state) == ERROR_SUCCESS) {
223  connected = true;
224  } else {
225  connected = false;
226  }
227 
228  LightMutexHolder holder(_lock);
229  if (connected == _is_connected) {
230  // Nothing changed.
231  return;
232  }
233  _is_connected = connected;
234 
235  if (connected) {
236  _name = "XInput Device #";
237  _name += format_string(_index + 1);
238  _vendor_id = caps.VendorID;
239  _product_id = caps.ProductID;
240  init_device(caps, state);
241  mgr->add_device(this);
242  } else {
243  mgr->remove_device(this);
244  }
245 }
246 
247 /**
248  * Static method to initialize the XInput library.
249  */
250 bool XInputDevice::
251 init_xinput() {
252  if (_initialized) {
253  return true;
254  }
255 
256  if (device_cat.is_debug()) {
257  device_cat.debug() << "Initializing XInput library.\n";
258  }
259 
260  _initialized = true;
261  const char *dll_name = "Xinput1_4.dll";
262  HMODULE module = LoadLibraryA(dll_name);
263 
264  // If we didn't find XInput 1.4, fall back to the older 1.3 version.
265  if (!module) {
266  if (device_cat.is_debug()) {
267  device_cat.debug()
268  << "Xinput1_4.dll not found, falling back to Xinput1_3.dll\n";
269  }
270 
271  dll_name = "Xinput1_3.dll";
272  module = LoadLibraryA(dll_name);
273  }
274 
275  if (module) {
276  if (device_cat.is_debug()) {
277  device_cat.debug()
278  << "Successfully loaded " << dll_name << "\n";
279  }
280 
281  // Undocumented version (XInputGetStateEx) that includes a
282  // state bit for the guide button.
283  get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100));
284  if (get_state == nullptr) {
285  get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState");
286  if (get_state == nullptr) {
287  device_cat.error()
288  << "Failed to find function XInputGetState in " << dll_name << ".\n";
289  return false;
290  }
291  }
292 
293  set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState");
294  if (set_state == nullptr) {
295  device_cat.error()
296  << "Failed to find function XInputSetState in " << dll_name << ".\n";
297  return false;
298  }
299 
300  get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities");
301  if (get_capabilities == nullptr) {
302  device_cat.error()
303  << "Failed to find function XInputGetCapabilities in " << dll_name << ".\n";
304  return false;
305  }
306 
307  get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation");
308  get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104));
309  get_capabilities_ex = (pXInputGetCapabilitiesEx)GetProcAddress(module, MAKEINTRESOURCE(108));
310  return true;
311  }
312 
313  device_cat.error()
314  << "Failed to load Xinput1_4.dll or Xinput1_3.dll.\n";
315  return false;
316 }
317 
318 /**
319  * Initializes the device. Called when the device was just connected.
320  * Assumes either the lock is held or this is called from the constructor.
321  */
322 void XInputDevice::
323 init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) {
324  nassertv(_initialized);
325  // It seems that the Xbox One controller is reported as having a DevType of
326  // zero, at least when I tested in with XInput 1.3 on Windows 7.
327  //if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) {
328 
329  // For subtypes and mappings, see this page:
330  // https://msdn.microsoft.com/en-us/library/windows/desktop/hh405050.aspx
331  switch (caps.SubType) {
332  default:
333  case XINPUT_DEVSUBTYPE_GAMEPAD:
334  _device_class = DeviceClass::gamepad;
335  _axes[0].axis = Axis::left_trigger;
336  _axes[1].axis = Axis::right_trigger;
337  _axes[2].axis = Axis::left_x;
338  _axes[3].axis = Axis::left_y;
339  _axes[4].axis = Axis::right_x;
340  _axes[5].axis = Axis::right_y;
341  break;
342 
343  case XINPUT_DEVSUBTYPE_WHEEL:
344  _device_class = DeviceClass::steering_wheel;
345  _axes[0].axis = Axis::brake;
346  _axes[1].axis = Axis::accelerator;
347  _axes[2].axis = Axis::wheel;
348  _axes[3].axis = Axis::none;
349  _axes[4].axis = Axis::none;
350  _axes[5].axis = Axis::none;
351  break;
352 
353  case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
354  _device_class = DeviceClass::flight_stick;
355  _axes[0].axis = Axis::yaw;
356  _axes[1].axis = Axis::throttle;
357  _axes[2].axis = Axis::roll;
358  _axes[3].axis = Axis::pitch;
359  _axes[4].axis = Axis::none;
360  _axes[5].axis = Axis::none;
361  break;
362 
363  case XINPUT_DEVSUBTYPE_DANCE_PAD:
364  _device_class = DeviceClass::dance_pad;
365  _axes[0].axis = Axis::none;
366  _axes[1].axis = Axis::none;
367  _axes[2].axis = Axis::none;
368  _axes[3].axis = Axis::none;
369  _axes[4].axis = Axis::none;
370  _axes[5].axis = Axis::none;
371  break;
372  }
373 
374  _axes[0]._scale = 1.0 / 255.0;
375  _axes[1]._scale = 1.0 / 255.0;
376  _axes[2]._scale = 1.0 / 32767.5;
377  _axes[3]._scale = 1.0 / 32767.5;
378  _axes[4]._scale = 1.0 / 32767.5;
379  _axes[5]._scale = 1.0 / 32767.5;
380 
381  _axes[2]._bias = 0.5 / 32767.5;
382  _axes[3]._bias = 0.5 / 32767.5;
383  _axes[4]._bias = 0.5 / 32767.5;
384  _axes[5]._bias = 0.5 / 32767.5;
385 
386  if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) {
387  _buttons[0].handle = ButtonHandle::none();
388  _buttons[1].handle = ButtonHandle::none();
389  _buttons[2].handle = ButtonHandle::none();
390  _buttons[3].handle = ButtonHandle::none();
391  _buttons[4].handle = ButtonHandle::none();
392  _buttons[5].handle = ButtonHandle::none();
393  } else {
394  _buttons[0].handle = GamepadButton::dpad_up();
395  _buttons[1].handle = GamepadButton::dpad_down();
396  _buttons[2].handle = GamepadButton::dpad_left();
397  _buttons[3].handle = GamepadButton::dpad_right();
398  _buttons[4].handle = GamepadButton::start();
399  _buttons[5].handle = GamepadButton::back();
400  }
401  _buttons[6].handle = GamepadButton::lstick();
402  _buttons[7].handle = GamepadButton::rstick();
403  _buttons[8].handle = GamepadButton::lshoulder();
404  _buttons[9].handle = GamepadButton::rshoulder();
405  _buttons[10].handle = GamepadButton::guide();
406  _buttons[11].handle = GamepadButton::face_a();
407  _buttons[12].handle = GamepadButton::face_b();
408  _buttons[13].handle = GamepadButton::face_x();
409  _buttons[14].handle = GamepadButton::face_y();
410 
411  if (caps.Vibration.wLeftMotorSpeed != 0 ||
412  caps.Vibration.wRightMotorSpeed != 0) {
413  enable_feature(Feature::vibration);
414  }
415 
416  if (get_battery_information != nullptr) {
417  XINPUT_BATTERY_INFORMATION batt;
418  if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) {
419  if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED &&
420  batt.BatteryType != BATTERY_TYPE_WIRED) {
421  // This device has a battery. Report the battery level.
422  enable_feature(Feature::battery);
423  _battery_data.level = batt.BatteryLevel;
424  _battery_data.max_level = BATTERY_LEVEL_FULL;
425  }
426  }
427  }
428 
429  WORD buttons = state.Gamepad.wButtons;
430  WORD mask = 1;
431  for (int i = 0; i < 16; ++i) {
432  // Set the state without triggering a button event.
433  _buttons[i]._state = (buttons & mask) ? S_down : S_up;
434  mask <<= 1;
435  if (i == 10) {
436  // XInput skips 0x0800.
437  mask <<= 1;
438  }
439  }
440 
441  axis_changed(0, state.Gamepad.bLeftTrigger);
442  axis_changed(1, state.Gamepad.bRightTrigger);
443  axis_changed(2, state.Gamepad.sThumbLX);
444  axis_changed(3, state.Gamepad.sThumbLY);
445  axis_changed(4, state.Gamepad.sThumbRX);
446  axis_changed(5, state.Gamepad.sThumbRY);
447 
448  _last_buttons = buttons;
449  _last_packet = state.dwPacketNumber;
450 }
451 
452 /**
453  * Sets the vibration strength. The first argument controls a low-frequency
454  * motor, if present, and the latter controls a high-frequency motor.
455  * The values are within the 0-1 range.
456  */
457 void XInputDevice::
458 do_set_vibration(double strong, double weak) {
459  nassertv_always(_is_connected);
460 
461  XINPUT_VIBRATION vibration;
462  vibration.wLeftMotorSpeed = strong * 0xffff;
463  vibration.wRightMotorSpeed = weak * 0xffff;
464  set_state(_index, &vibration);
465 }
466 
467 /**
468  * Polls the input device for new activity, to ensure it contains the latest
469  * events. This will only have any effect for some types of input devices;
470  * others may be updated automatically, and this method will be a no-op.
471  */
472 void XInputDevice::
473 do_poll() {
474  // Not sure why someone would call this on a disconnected device.
475  if (!_is_connected) {
476  return;
477  }
478 
479  XINPUT_STATE state;
480 
481  if (get_state(_index, &state) != ERROR_SUCCESS) {
482  // Device was disconnected.
483  if (_is_connected) {
484  _is_connected = false;
486  mgr->remove_device(this);
487  }
488  return;
489  }
490 
491  if (state.dwPacketNumber == _last_packet) {
492  // No change since last time we asked.
493  return;
494  }
495 
496  // Did any buttons change state?
497  WORD changed_buttons = _last_buttons ^ state.Gamepad.wButtons;
498 
499  WORD mask = 1;
500  for (int i = 0; i < 16; ++i) {
501  if (changed_buttons & mask) {
502  button_changed(i, (state.Gamepad.wButtons & mask) != 0);
503  }
504  mask <<= 1;
505  if (i == 10) {
506  // XInput skips 0x0800.
507  mask <<= 1;
508  }
509  }
510 
511  axis_changed(0, state.Gamepad.bLeftTrigger);
512  axis_changed(1, state.Gamepad.bRightTrigger);
513  axis_changed(2, state.Gamepad.sThumbLX);
514  axis_changed(3, state.Gamepad.sThumbLY);
515  axis_changed(4, state.Gamepad.sThumbRX);
516  axis_changed(5, state.Gamepad.sThumbRY);
517 
518  _last_buttons = state.Gamepad.wButtons;
519  _last_packet = state.dwPacketNumber;
520 }
521 
522 #endif // _WIN32
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class keeps track of all the devices on a system, and sends out events when a device has been ho...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static InputDeviceManager * get_global_ptr()
Returns the singleton InputDeviceManager instance.
void remove_device(InputDevice *device)
Called when a device has been removed, or when a device should otherwise no longer be tracked.
Similar to MutexHolder, but for a light mutex.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void add_device(InputDevice *device)
Called when a new device has been discovered.