From dd8344455cc426faa9e44206d8bfd20134b3c023 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 12 Dec 2015 22:07:43 +0100 Subject: [PATCH] XInput gamepad control states, battery information, HID properties --- dtool/src/parser-inc/windows.h | 1 + makepanda/makepanda.py | 5 +- panda/src/device/inputDevice.I | 2 + panda/src/device/inputDevice.cxx | 18 ++-- panda/src/device/inputDevice.h | 4 + panda/src/device/inputDeviceManager.cxx | 6 +- panda/src/device/xInputDevice.cxx | 115 ++++++++++++++++++++++-- 7 files changed, 127 insertions(+), 24 deletions(-) diff --git a/dtool/src/parser-inc/windows.h b/dtool/src/parser-inc/windows.h index fe3c46e22e..4a36e5b2d2 100644 --- a/dtool/src/parser-inc/windows.h +++ b/dtool/src/parser-inc/windows.h @@ -42,6 +42,7 @@ typedef unsigned long DWORD; typedef unsigned short WORD; typedef long LONG; typedef long UINT; +typedef unsigned char BYTE; typedef unsigned long ULONG; typedef long long LONGLONG; typedef long HRESULT; diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index d11d0825bd..07d8950606 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -578,6 +578,7 @@ if (COMPILER == "MSVC"): LibName("WINGDI", "gdi32.lib") LibName("ADVAPI", "advapi32.lib") LibName("IPHLPAPI", "iphlpapi.lib") + LibName("SETUPAPI", "setupapi.lib") LibName("GL", "opengl32.lib") LibName("GLES", "libgles_cm.lib") LibName("GLES2", "libGLESv2.lib") @@ -3849,8 +3850,8 @@ if (not RUNTIME): # if (not RUNTIME): - OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', - 'TIFF', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', + OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'TIFF', + 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', 'SETUPAPI', 'SQUISH', 'NVIDIACG', 'VORBIS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI'] TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx') diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 99962b7750..8f04226896 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -21,6 +21,8 @@ INLINE InputDevice:: InputDevice() : _flags(0), _device_class(DC_unknown), + _vendor_id(0), + _product_id(0), _is_connected(false), _event_sequence(0), _enable_pointer_events(false), diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index a878a7027d..4637673672 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -32,6 +32,8 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) : _name(name), _flags(flags), _device_class(dev_class), + _vendor_id(0), + _product_id(0), _is_connected(true), _event_sequence(0), _enable_pointer_events(false), @@ -48,7 +50,7 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) : //////////////////////////////////////////////////////////////////// InputDevice:: InputDevice(const InputDevice ©) { - *this = copy; + nassertv(false); } //////////////////////////////////////////////////////////////////// @@ -58,16 +60,7 @@ InputDevice(const InputDevice ©) { //////////////////////////////////////////////////////////////////// void InputDevice:: operator = (const InputDevice ©) { - LightMutexHolder holder(_lock); - LightMutexHolder holder1(copy._lock); - _name = copy._name; - _flags = copy._flags; - _is_connected = copy._is_connected; - _event_sequence = copy._event_sequence; - _enable_pointer_events = copy._enable_pointer_events; - _pointer_data = copy._pointer_data; - _button_events = copy._button_events; - _pointer_events = copy._pointer_events; + nassertv(false); } //////////////////////////////////////////////////////////////////// @@ -315,10 +308,9 @@ output(ostream &out) const { if (_battery_level > 0 && _max_battery_level > 0) { out << " ["; short i = 0; - for (; i < _battery_level - 1; ++i) { + for (; i < _battery_level; ++i) { out << '='; } - out << '/'; for (; i < _max_battery_level; ++i) { out << ' '; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 407e6cdb77..758ea366f8 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -206,9 +206,13 @@ protected: LightMutex _lock; string _name; + string _serial_number; + string _manufacturer; DeviceClass _device_class; int _flags; int _event_sequence; + short _vendor_id; + short _product_id; bool _is_connected; bool _enable_pointer_events; PointerData _pointer_data; diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index aa799231e3..90f1e1921f 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -143,7 +143,7 @@ add_device(InputDevice *device) { LightMutexHolder holder(_lock); _all_devices.push_back(device); } - throw_event("device-added", device); + throw_event("connect-device", device); } //////////////////////////////////////////////////////////////////// @@ -163,7 +163,7 @@ remove_device(InputDevice *device) { _all_devices.erase(it); } - throw_event("device-removed", device); + throw_event("disconnect-device", device); } //////////////////////////////////////////////////////////////////// @@ -220,7 +220,7 @@ poll() { device_cat.info() << "Removed input device " << *device << "\n"; } - throw_event("device-removed", device.p()); + throw_event("disconnect-device", device.p()); } } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 92ab2928c0..13a2ffdc19 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -18,6 +18,7 @@ #include "gamepadButton.h" #include +#include #ifndef XUSER_MAX_COUNT #define XUSER_MAX_COUNT 4 @@ -27,13 +28,47 @@ #define XINPUT_CAPS_FFB_SUPPORTED 0x0001 #endif +#ifndef BATTERY_DEVTYPE_GAMEPAD +#define BATTERY_DEVTYPE_GAMEPAD 0x00 +#endif + +#ifndef BATTERY_TYPE_DISCONNECTED +#define BATTERY_TYPE_DISCONNECTED 0x00 +#endif + +#ifndef BATTERY_TYPE_WIRED +#define BATTERY_TYPE_WIRED 0x01 +#endif + +#ifndef BATTERY_LEVEL_FULL +#define BATTERY_LEVEL_FULL 0x03 +#endif + +typedef struct _XINPUT_BATTERY_INFORMATION { + BYTE BatteryType; + BYTE BatteryLevel; +} XINPUT_BATTERY_INFORMATION; + +// Undocumented, I figured out how this looks by trial and error. +struct XINPUT_BUSINFO { + WORD VendorID; + WORD ProductID; + WORD RevisionID; + WORD Unknown1; // Unknown - padding? + DWORD InstanceID; + DWORD Unknown2; + //WORD Unknown3; +}; + typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); -//typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); +typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); +typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); static pXInputGetState get_state = NULL; static pXInputGetCapabilities get_capabilities = NULL; -//static pXInputGetBatteryInformation get_battery_information = NULL; +static pXInputGetBatteryInformation get_battery_information = NULL; +static pXInputGetBaseBusInformation get_base_bus_information = NULL; bool XInputDevice::_initialized = false; @@ -73,10 +108,9 @@ XInputDevice(DWORD user_index) : } } -#if 0 if (get_battery_information != NULL) { XINPUT_BATTERY_INFORMATION batt; - if (XInputGetBatteryInformation(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { + if (get_battery_information(_index, 0, &batt) == ERROR_SUCCESS) { if (batt.BatteryType == BATTERY_TYPE_DISCONNECTED) { _is_connected = false; @@ -89,8 +123,52 @@ XInputDevice(DWORD user_index) : } } } -#endif + // Get information about the USB device. + // This is not documented at all. I'm probably the first to try this. + XINPUT_BUSINFO businfo; + if (get_base_bus_information != NULL && + get_base_bus_information(0, &businfo) == ERROR_SUCCESS) { + _vendor_id = businfo.VendorID; + _product_id = businfo.ProductID; + + { + // Reformat the serial number into its original hex string form. + char sn[10]; + sprintf(sn, "%08X", businfo.InstanceID); + _serial_number.assign(sn, 8); + } + + // Get information about the device from Windows. For that, we'll + // first need to construct the device path. Fortunately, we now have + // enough information to do so. + char path[32]; + sprintf(path, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); + + DEVINST inst; + if (CM_Locate_DevNodeA(&inst, path, 0) != 0) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Could not locate device node " << path << "\n"; + } + } else { + // Get the device properties we need. + char buffer[4096]; + ULONG buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { + _name.assign(buffer); + } + buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { + _manufacturer.assign(buffer); + } + } + } else { + // We need something to name it. + _name = "XInput Device"; + } + + _controls.resize(6); _buttons.resize(16); // Get the initial state. @@ -103,6 +181,19 @@ XInputDevice(DWORD user_index) : WORD buttons = state.Gamepad.wButtons; + set_control_map(0, C_left_trigger); + set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); + set_control_map(1, C_right_trigger); + set_control_state(1, state.Gamepad.bRightTrigger / 255.0); + set_control_map(2, C_left_x); + set_control_state(2, state.Gamepad.sThumbLX / 32767.0); + set_control_map(3, C_left_y); + set_control_state(3, state.Gamepad.sThumbLY / 32767.0); + set_control_map(4, C_right_x); + set_control_state(4, state.Gamepad.sThumbRX / 32767.0); + set_control_map(5, C_right_y); + set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + set_button_map(0, GamepadButton::dpad_up()); set_button_state(0, (buttons & XINPUT_GAMEPAD_DPAD_UP) != 0); set_button_map(1, GamepadButton::dpad_down()); @@ -146,6 +237,9 @@ bool XInputDevice:: init_xinput() { _initialized = true; HMODULE module = LoadLibraryA("Xinput1_4.dll"); + if (!module) { + module = LoadLibraryA("Xinput1_3.dll"); + } if (module) { // Undocumented version (XInputGetStateEx) that includes a // state bit for the guide button. @@ -160,11 +254,13 @@ init_xinput() { } get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); + get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); + get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); return true; } device_cat.error() - << "Failed to load Xinput1_4.dll.\n"; + << "Failed to load XInput DLL.\n"; return false; } @@ -207,6 +303,13 @@ do_poll() { } } + set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); + set_control_state(1, state.Gamepad.bRightTrigger / 255.0); + set_control_state(2, state.Gamepad.sThumbLX / 32767.0); + set_control_state(3, state.Gamepad.sThumbLY / 32767.0); + set_control_state(4, state.Gamepad.sThumbRX / 32767.0); + set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; }