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 (device_cat.is_debug()) {
253  device_cat.debug() << "Initializing XInput library.\n";
254  }
255 
256  _initialized = true;
257  const char *dll_name = "Xinput1_4.dll";
258  HMODULE module = LoadLibraryA(dll_name);
259 
260  // If we didn't find XInput 1.4, fall back to the older 1.3 version.
261  if (!module) {
262  if (device_cat.is_debug()) {
263  device_cat.debug()
264  << "Xinput1_4.dll not found, falling back to Xinput1_3.dll\n";
265  }
266 
267  dll_name = "Xinput1_3.dll";
268  module = LoadLibraryA(dll_name);
269  }
270 
271  if (module) {
272  if (device_cat.is_debug()) {
273  device_cat.debug()
274  << "Successfully loaded " << dll_name << "\n";
275  }
276 
277  // Undocumented version (XInputGetStateEx) that includes a
278  // state bit for the guide button.
279  get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100));
280  if (get_state == nullptr) {
281  get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState");
282  if (get_state == nullptr) {
283  device_cat.error()
284  << "Failed to find function XInputGetState in " << dll_name << ".\n";
285  return false;
286  }
287  }
288 
289  set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState");
290  if (set_state == nullptr) {
291  device_cat.error()
292  << "Failed to find function XInputSetState in " << dll_name << ".\n";
293  return false;
294  }
295 
296  get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities");
297  if (get_capabilities == nullptr) {
298  device_cat.error()
299  << "Failed to find function XInputGetCapabilities in " << dll_name << ".\n";
300  return false;
301  }
302 
303  get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation");
304  get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104));
305  get_capabilities_ex = (pXInputGetCapabilitiesEx)GetProcAddress(module, MAKEINTRESOURCE(108));
306  return true;
307  }
308 
309  device_cat.error()
310  << "Failed to load Xinput1_4.dll or Xinput1_3.dll.\n";
311  return false;
312 }
313 
314 /**
315  * Initializes the device. Called when the device was just connected.
316  * Assumes either the lock is held or this is called from the constructor.
317  */
318 void XInputDevice::
319 init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) {
320  nassertv(_initialized);
321  // It seems that the Xbox One controller is reported as having a DevType of
322  // zero, at least when I tested in with XInput 1.3 on Windows 7.
323  //if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) {
324 
325  // For subtypes and mappings, see this page:
326  // https://msdn.microsoft.com/en-us/library/windows/desktop/hh405050.aspx
327  switch (caps.SubType) {
328  default:
329  case XINPUT_DEVSUBTYPE_GAMEPAD:
330  _device_class = DeviceClass::gamepad;
331  _axes[0].axis = Axis::left_trigger;
332  _axes[1].axis = Axis::right_trigger;
333  _axes[2].axis = Axis::left_x;
334  _axes[3].axis = Axis::left_y;
335  _axes[4].axis = Axis::right_x;
336  _axes[5].axis = Axis::right_y;
337  break;
338 
339  case XINPUT_DEVSUBTYPE_WHEEL:
340  _device_class = DeviceClass::steering_wheel;
341  _axes[0].axis = Axis::brake;
342  _axes[1].axis = Axis::accelerator;
343  _axes[2].axis = Axis::wheel;
344  _axes[3].axis = Axis::none;
345  _axes[4].axis = Axis::none;
346  _axes[5].axis = Axis::none;
347  break;
348 
349  case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
350  _device_class = DeviceClass::flight_stick;
351  _axes[0].axis = Axis::yaw;
352  _axes[1].axis = Axis::throttle;
353  _axes[2].axis = Axis::roll;
354  _axes[3].axis = Axis::pitch;
355  _axes[4].axis = Axis::none;
356  _axes[5].axis = Axis::none;
357  break;
358 
359  case XINPUT_DEVSUBTYPE_DANCE_PAD:
360  _device_class = DeviceClass::dance_pad;
361  _axes[0].axis = Axis::none;
362  _axes[1].axis = Axis::none;
363  _axes[2].axis = Axis::none;
364  _axes[3].axis = Axis::none;
365  _axes[4].axis = Axis::none;
366  _axes[5].axis = Axis::none;
367  break;
368  }
369 
370  _axes[0]._scale = 1.0 / 255.0;
371  _axes[1]._scale = 1.0 / 255.0;
372  _axes[2]._scale = 1.0 / 32767.5;
373  _axes[3]._scale = 1.0 / 32767.5;
374  _axes[4]._scale = 1.0 / 32767.5;
375  _axes[5]._scale = 1.0 / 32767.5;
376 
377  _axes[2]._bias = 0.5 / 32767.5;
378  _axes[3]._bias = 0.5 / 32767.5;
379  _axes[4]._bias = 0.5 / 32767.5;
380  _axes[5]._bias = 0.5 / 32767.5;
381 
382  if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) {
383  _buttons[0].handle = ButtonHandle::none();
384  _buttons[1].handle = ButtonHandle::none();
385  _buttons[2].handle = ButtonHandle::none();
386  _buttons[3].handle = ButtonHandle::none();
387  _buttons[4].handle = ButtonHandle::none();
388  _buttons[5].handle = ButtonHandle::none();
389  } else {
390  _buttons[0].handle = GamepadButton::dpad_up();
391  _buttons[1].handle = GamepadButton::dpad_down();
392  _buttons[2].handle = GamepadButton::dpad_left();
393  _buttons[3].handle = GamepadButton::dpad_right();
394  _buttons[4].handle = GamepadButton::start();
395  _buttons[5].handle = GamepadButton::back();
396  }
397  _buttons[6].handle = GamepadButton::lstick();
398  _buttons[7].handle = GamepadButton::rstick();
399  _buttons[8].handle = GamepadButton::lshoulder();
400  _buttons[9].handle = GamepadButton::rshoulder();
401  _buttons[10].handle = GamepadButton::guide();
402  _buttons[11].handle = GamepadButton::face_a();
403  _buttons[12].handle = GamepadButton::face_b();
404  _buttons[13].handle = GamepadButton::face_x();
405  _buttons[14].handle = GamepadButton::face_y();
406 
407  if (caps.Vibration.wLeftMotorSpeed != 0 ||
408  caps.Vibration.wRightMotorSpeed != 0) {
409  enable_feature(Feature::vibration);
410  }
411 
412  if (get_battery_information != nullptr) {
413  XINPUT_BATTERY_INFORMATION batt;
414  if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) {
415  if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED &&
416  batt.BatteryType != BATTERY_TYPE_WIRED) {
417  // This device has a battery. Report the battery level.
418  enable_feature(Feature::battery);
419  _battery_data.level = batt.BatteryLevel;
420  _battery_data.max_level = BATTERY_LEVEL_FULL;
421  }
422  }
423  }
424 
425  WORD buttons = state.Gamepad.wButtons;
426  WORD mask = 1;
427  for (int i = 0; i < 16; ++i) {
428  // Set the state without triggering a button event.
429  _buttons[i]._state = (buttons & mask) ? S_down : S_up;
430  mask <<= 1;
431  if (i == 10) {
432  // XInput skips 0x0800.
433  mask <<= 1;
434  }
435  }
436 
437  axis_changed(0, state.Gamepad.bLeftTrigger);
438  axis_changed(1, state.Gamepad.bRightTrigger);
439  axis_changed(2, state.Gamepad.sThumbLX);
440  axis_changed(3, state.Gamepad.sThumbLY);
441  axis_changed(4, state.Gamepad.sThumbRX);
442  axis_changed(5, state.Gamepad.sThumbRY);
443 
444  _last_buttons = buttons;
445  _last_packet = state.dwPacketNumber;
446 }
447 
448 /**
449  * Sets the vibration strength. The first argument controls a low-frequency
450  * motor, if present, and the latter controls a high-frequency motor.
451  * The values are within the 0-1 range.
452  */
453 void XInputDevice::
454 do_set_vibration(double strong, double weak) {
455  nassertv_always(_is_connected);
456 
457  XINPUT_VIBRATION vibration;
458  vibration.wLeftMotorSpeed = strong * 0xffff;
459  vibration.wRightMotorSpeed = weak * 0xffff;
460  set_state(_index, &vibration);
461 }
462 
463 /**
464  * Polls the input device for new activity, to ensure it contains the latest
465  * events. This will only have any effect for some types of input devices;
466  * others may be updated automatically, and this method will be a no-op.
467  */
468 void XInputDevice::
469 do_poll() {
470  // Not sure why someone would call this on a disconnected device.
471  if (!_is_connected) {
472  return;
473  }
474 
475  XINPUT_STATE state;
476 
477  if (get_state(_index, &state) != ERROR_SUCCESS) {
478  // Device was disconnected.
479  if (_is_connected) {
480  _is_connected = false;
482  mgr->remove_device(this);
483  }
484  return;
485  }
486 
487  if (state.dwPacketNumber == _last_packet) {
488  // No change since last time we asked.
489  return;
490  }
491 
492  // Did any buttons change state?
493  WORD changed_buttons = _last_buttons ^ state.Gamepad.wButtons;
494 
495  WORD mask = 1;
496  for (int i = 0; i < 16; ++i) {
497  if (changed_buttons & mask) {
498  button_changed(i, (state.Gamepad.wButtons & mask) != 0);
499  }
500  mask <<= 1;
501  if (i == 10) {
502  // XInput skips 0x0800.
503  mask <<= 1;
504  }
505  }
506 
507  axis_changed(0, state.Gamepad.bLeftTrigger);
508  axis_changed(1, state.Gamepad.bRightTrigger);
509  axis_changed(2, state.Gamepad.sThumbLX);
510  axis_changed(3, state.Gamepad.sThumbLY);
511  axis_changed(4, state.Gamepad.sThumbRX);
512  axis_changed(5, state.Gamepad.sThumbRY);
513 
514  _last_buttons = state.Gamepad.wButtons;
515  _last_packet = state.dwPacketNumber;
516 }
517 
518 #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.