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 (WINAPI *pXInputGetState)(DWORD, XINPUT_STATE *);
103 typedef DWORD (WINAPI *pXInputSetState)(DWORD, XINPUT_VIBRATION *);
104 typedef DWORD (WINAPI *pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *);
105 typedef DWORD (WINAPI *pXInputGetCapabilitiesEx)(DWORD, DWORD, DWORD, XINPUT_CAPABILITIES_EX *);
106 typedef DWORD (WINAPI *pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *);
107 typedef DWORD (WINAPI *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
This class keeps track of all the devices on a system, and sends out events when a device has been ho...
void add_device(InputDevice *device)
Called when a new device has been discovered.
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.