From 1024eb7b63732309e8dfb9cf59c69a84e4a7d34b Mon Sep 17 00:00:00 2001 From: Peter Tissen Date: Fri, 30 May 2014 21:32:25 +0200 Subject: [PATCH 1/4] initial commit for multiple DInput devices. allow multiple dinput devices but still block all dinput devices if an xinput device is present. This still has some issues with devices not being able to be dynamically added or removed but even many commercial games react to hot-plug DInput events. Also, the axis handling is not really ideal yet but it works for now. --- Windows/DinputDevice.cpp | 141 ++++++++++++++++++++++++++++----------- Windows/DinputDevice.h | 23 ++++++- Windows/WindowsHost.cpp | 10 ++- 3 files changed, 129 insertions(+), 45 deletions(-) diff --git a/Windows/DinputDevice.cpp b/Windows/DinputDevice.cpp index e4033c67da..408f665e9f 100644 --- a/Windows/DinputDevice.cpp +++ b/Windows/DinputDevice.cpp @@ -34,6 +34,11 @@ #undef max #endif +//initialize static members of DinputDevice +unsigned int DinputDevice::pInstances = 0; +LPDIRECTINPUT8 DinputDevice::pDI = NULL; +std::vector DinputDevice::devices; + // In order from 0. There can be 128, but most controllers do not have that many. static const int dinput_buttons[] = { NKCODE_BUTTON_1, @@ -80,9 +85,50 @@ bool IsXInputDevice( const GUID* pGuidProductFromDirectInput ) { return false; } -DinputDevice::DinputDevice() { +LPDIRECTINPUT8 DinputDevice::getPDI() +{ + if (pDI == NULL) + { + if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&pDI, NULL))) + { + pDI = NULL; + } + } + return pDI; +} + +BOOL DinputDevice::DevicesCallback( + LPCDIDEVICEINSTANCE lpddi, + LPVOID pvRef + ) +{ + //check if a device with the same Instance guid is already saved + auto res = std::find_if(devices.begin(), devices.end(), + [lpddi](const DIDEVICEINSTANCE &to_consider){ + return lpddi->guidInstance == to_consider.guidInstance; + }); + if (res == devices.end()) //not yet in the devices list + { + // Ignore if device supports XInput + if (!IsXInputDevice(&lpddi->guidProduct)) { + devices.push_back(*lpddi); + } + } + return DIENUM_CONTINUE; +} + +void DinputDevice::getDevices() +{ + if (devices.empty()) + { + getPDI()->EnumDevices(DI8DEVCLASS_GAMECTRL, &DinputDevice::DevicesCallback, NULL, DIEDFL_ATTACHEDONLY); + } +} + +DinputDevice::DinputDevice(int devnum) { + pInstances++; + pDevNum = devnum; pJoystick = NULL; - pDI = NULL; memset(lastButtons_, 0, sizeof(lastButtons_)); memset(lastPOV_, 0, sizeof(lastPOV_)); last_lX_ = 0; @@ -92,50 +138,42 @@ DinputDevice::DinputDevice() { last_lRy_ = 0; last_lRz_ = 0; - if(FAILED(DirectInput8Create(GetModuleHandle(NULL),DIRECTINPUT_VERSION,IID_IDirectInput8,(void**)&pDI,NULL))) - return; - - if(FAILED(pDI->CreateDevice(GUID_Joystick, &pJoystick, NULL ))) { - pDI->Release(); - pDI = NULL; + if (getPDI() == NULL) + { return; } - if(FAILED(pJoystick->SetDataFormat(&c_dfDIJoystick2))) { + getDevices(); + if ( (devnum > devices.size()) || FAILED(getPDI()->CreateDevice(devices.at(devnum).guidInstance, &pJoystick, NULL))) + { + return; + } + + if (FAILED(pJoystick->SetDataFormat(&c_dfDIJoystick2))) { pJoystick->Release(); pJoystick = NULL; return; } - // Ignore if device supports XInput - DIDEVICEINSTANCE dinfo = {0}; - pJoystick->GetDeviceInfo(&dinfo); - if (IsXInputDevice(&dinfo.guidProduct)) { - pDI->Release(); - pDI = NULL; - pJoystick->Release(); - pJoystick = NULL; - } - - DIPROPRANGE diprg; - diprg.diph.dwSize = sizeof(DIPROPRANGE); + DIPROPRANGE diprg; + diprg.diph.dwSize = sizeof(DIPROPRANGE); diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); - diprg.diph.dwHow = DIPH_DEVICE; - diprg.diph.dwObj = 0; - diprg.lMin = -10000; - diprg.lMax = 10000; + diprg.diph.dwHow = DIPH_DEVICE; + diprg.diph.dwObj = 0; + diprg.lMin = -10000; + diprg.lMax = 10000; analog = FAILED(pJoystick->SetProperty(DIPROP_RANGE, &diprg.diph)) ? false : true; // Other devices suffer if the deadzone is not set. // TODO: The dead zone will be made configurable in the Control dialog. DIPROPDWORD dipw; - dipw.diph.dwSize = sizeof(DIPROPDWORD); + dipw.diph.dwSize = sizeof(DIPROPDWORD); dipw.diph.dwHeaderSize = sizeof(DIPROPHEADER); - dipw.diph.dwHow = DIPH_DEVICE; - dipw.diph.dwObj = 0; + dipw.diph.dwHow = DIPH_DEVICE; + dipw.diph.dwObj = 0; // dwData 1000 is deadzone(0% - 10%) - dipw.dwData = 1000; + dipw.dwData = 1000; analog |= FAILED(pJoystick->SetProperty(DIPROP_DEADZONE, &dipw.diph)) ? false : true; } @@ -146,7 +184,13 @@ DinputDevice::~DinputDevice() { pJoystick = NULL; } - if (pDI) { + pInstances--; + + //the whole instance counter is obviously highly thread-unsafe + //but I don't think creation and destruction operations will be + //happening at the same time and other values like pDI are + //unsafe as well anyway + if (pInstances == 0 && pDI) { pDI->Release(); pDI = NULL; } @@ -182,17 +226,26 @@ int DinputDevice::UpdateState(InputState &input_state) { if (analog) { AxisInput axis; - axis.deviceId = DEVICE_ID_PAD_0; + axis.deviceId = DEVICE_ID_PAD_0 + pDevNum; - SendNativeAxis(DEVICE_ID_PAD_0, js.lX, last_lX_, JOYSTICK_AXIS_X); - SendNativeAxis(DEVICE_ID_PAD_0, js.lY, last_lY_, JOYSTICK_AXIS_Y); - SendNativeAxis(DEVICE_ID_PAD_0, js.lZ, last_lZ_, JOYSTICK_AXIS_Z); - SendNativeAxis(DEVICE_ID_PAD_0, js.lRx, last_lRx_, JOYSTICK_AXIS_RX); - SendNativeAxis(DEVICE_ID_PAD_0, js.lRy, last_lRy_, JOYSTICK_AXIS_RY); - SendNativeAxis(DEVICE_ID_PAD_0, js.lRz, last_lRz_, JOYSTICK_AXIS_RZ); + SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lX, last_lX_, JOYSTICK_AXIS_X); + SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lY, last_lY_, JOYSTICK_AXIS_Y); + SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lZ, last_lZ_, JOYSTICK_AXIS_Z); + SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRx, last_lRx_, JOYSTICK_AXIS_RX); + SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRy, last_lRy_, JOYSTICK_AXIS_RY); + SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRz, last_lRz_, JOYSTICK_AXIS_RZ); } - return UPDATESTATE_SKIP_PAD; + //check if the values have changed from last time and skip polling the rest of the dinput devices if they did + //this doesn't seem to quite work if only the axis have changed + if ((memcmp(js.rgbButtons, pPrevState.rgbButtons, sizeof(BYTE) * 128) != 0) + || (memcmp(js.rgdwPOV, pPrevState.rgdwPOV, sizeof(DWORD) * 4) != 0) + || js.lVX != 0 || js.lVY != 0 || js.lVZ != 0 || js.lVRx != 0 || js.lVRy != 0 || js.lVRz != 0) + { + pPrevState = js; + return UPDATESTATE_SKIP_PAD; + } + return -1; } static float NormalizedDeadzoneFilter(short value) { @@ -215,7 +268,7 @@ void DinputDevice::ApplyButtons(DIJOYSTATE2 &state, InputState &input_state) { bool down = (state.rgbButtons[i] & downMask) == downMask; KeyInput key; - key.deviceId = DEVICE_ID_PAD_0; + key.deviceId = DEVICE_ID_PAD_0 + pDevNum; key.flags = down ? KEY_DOWN : KEY_UP; key.keyCode = dinput_buttons[i]; NativeKey(key); @@ -227,7 +280,7 @@ void DinputDevice::ApplyButtons(DIJOYSTATE2 &state, InputState &input_state) { if (LOWORD(state.rgdwPOV[0]) != lastPOV_[0]) { KeyInput dpad[4]; for (int i = 0; i < 4; ++i) { - dpad[i].deviceId = DEVICE_ID_PAD_0; + dpad[i].deviceId = DEVICE_ID_PAD_0 + pDevNum; dpad[i].flags = KEY_UP; } dpad[0].keyCode = NKCODE_DPAD_UP; @@ -260,3 +313,11 @@ void DinputDevice::ApplyButtons(DIJOYSTATE2 &state, InputState &input_state) { } } +int DinputDevice::getNumPads() +{ + if (devices.empty()) + { + getDevices(); + } + return devices.size(); +} diff --git a/Windows/DinputDevice.h b/Windows/DinputDevice.h index fe2d5d9aee..74a3eb3643 100644 --- a/Windows/DinputDevice.h +++ b/Windows/DinputDevice.h @@ -26,14 +26,33 @@ class DinputDevice : public InputDevice { public: - DinputDevice(); + //instantiates device number devnum as explored by the first call to + //getDevices(), enumerates all devices if not done yet + DinputDevice(int devnum); ~DinputDevice(); virtual int UpdateState(InputState &input_state); virtual bool IsPad() { return true; } + static int getNumPads(); private: void ApplyButtons(DIJOYSTATE2 &state, InputState &input_state); - LPDIRECTINPUT8 pDI; + //unfortunate and unclean way to keep only one DirectInput instance around + static LPDIRECTINPUT8 getPDI(); + //unfortunate and unclean way to keep track of the number of devices and the + //GUIDs of the plugged in devices. This function will only search for devices + //if none have been found yet and will only list plugged in devices + //also, it excludes the devices that are compatible with XInput + static void getDevices(); + //callback for the WinAPI to call + static BOOL DevicesCallback( + LPCDIDEVICEINSTANCE lpddi, + LPVOID pvRef + ); + static unsigned int pInstances; + static std::vector devices; + static LPDIRECTINPUT8 pDI; + int pDevNum; LPDIRECTINPUTDEVICE8 pJoystick; + DIJOYSTATE2 pPrevState; bool analog; BYTE lastButtons_[128]; WORD lastPOV_[4]; diff --git a/Windows/WindowsHost.cpp b/Windows/WindowsHost.cpp index b72c29085d..3a3b751e9a 100644 --- a/Windows/WindowsHost.cpp +++ b/Windows/WindowsHost.cpp @@ -84,10 +84,14 @@ WindowsHost::WindowsHost(HWND mainWindow) { mouseDeltaX = 0; mouseDeltaY = 0; -#define PUSH_BACK(Cls) do { list.push_back(std::shared_ptr(new Cls())); } while (0) - + //add first XInput device to respond input.push_back(std::shared_ptr(new XinputDevice())); - input.push_back(std::shared_ptr(new DinputDevice())); + //find all connected DInput devices of class GamePad + int numDInputDevs = DinputDevice::getNumPads(); + for (int i = 0; i < numDInputDevs; i++) + { + input.push_back(std::shared_ptr(new DinputDevice(i))); + } keyboard = std::shared_ptr(new KeyboardDevice()); input.push_back(keyboard); From 7d25a1591c29df6190c0dc9abd721b2078607e27 Mon Sep 17 00:00:00 2001 From: Peter Tissen Date: Sat, 31 May 2014 20:01:06 +0200 Subject: [PATCH 2/4] fix formatting --- Windows/DinputDevice.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Windows/DinputDevice.h b/Windows/DinputDevice.h index 74a3eb3643..e27bd66840 100644 --- a/Windows/DinputDevice.h +++ b/Windows/DinputDevice.h @@ -36,24 +36,24 @@ public: private: void ApplyButtons(DIJOYSTATE2 &state, InputState &input_state); //unfortunate and unclean way to keep only one DirectInput instance around - static LPDIRECTINPUT8 getPDI(); + static LPDIRECTINPUT8 getPDI(); //unfortunate and unclean way to keep track of the number of devices and the //GUIDs of the plugged in devices. This function will only search for devices //if none have been found yet and will only list plugged in devices //also, it excludes the devices that are compatible with XInput - static void getDevices(); + static void getDevices(); //callback for the WinAPI to call - static BOOL DevicesCallback( - LPCDIDEVICEINSTANCE lpddi, - LPVOID pvRef - ); - static unsigned int pInstances; + static BOOL DevicesCallback( + LPCDIDEVICEINSTANCE lpddi, + LPVOID pvRef + ); + static unsigned int pInstances; static std::vector devices; - static LPDIRECTINPUT8 pDI; - int pDevNum; + static LPDIRECTINPUT8 pDI; + int pDevNum; LPDIRECTINPUTDEVICE8 pJoystick; - DIJOYSTATE2 pPrevState; - bool analog; + DIJOYSTATE2 pPrevState; + bool analog; BYTE lastButtons_[128]; WORD lastPOV_[4]; short last_lX_; From 46eb74e98a7e5836348bac8c601849d693d22c6c Mon Sep 17 00:00:00 2001 From: Peter Tissen Date: Sat, 31 May 2014 20:04:26 +0200 Subject: [PATCH 3/4] undo visual studio auto-indenting --- Windows/DinputDevice.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Windows/DinputDevice.cpp b/Windows/DinputDevice.cpp index 408f665e9f..e2df8dc610 100644 --- a/Windows/DinputDevice.cpp +++ b/Windows/DinputDevice.cpp @@ -155,25 +155,25 @@ DinputDevice::DinputDevice(int devnum) { return; } - DIPROPRANGE diprg; - diprg.diph.dwSize = sizeof(DIPROPRANGE); + DIPROPRANGE diprg; + diprg.diph.dwSize = sizeof(DIPROPRANGE); diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); - diprg.diph.dwHow = DIPH_DEVICE; - diprg.diph.dwObj = 0; - diprg.lMin = -10000; - diprg.lMax = 10000; + diprg.diph.dwHow = DIPH_DEVICE; + diprg.diph.dwObj = 0; + diprg.lMin = -10000; + diprg.lMax = 10000; analog = FAILED(pJoystick->SetProperty(DIPROP_RANGE, &diprg.diph)) ? false : true; // Other devices suffer if the deadzone is not set. // TODO: The dead zone will be made configurable in the Control dialog. DIPROPDWORD dipw; - dipw.diph.dwSize = sizeof(DIPROPDWORD); + dipw.diph.dwSize = sizeof(DIPROPDWORD); dipw.diph.dwHeaderSize = sizeof(DIPROPHEADER); - dipw.diph.dwHow = DIPH_DEVICE; - dipw.diph.dwObj = 0; + dipw.diph.dwHow = DIPH_DEVICE; + dipw.diph.dwObj = 0; // dwData 1000 is deadzone(0% - 10%) - dipw.dwData = 1000; + dipw.dwData = 1000; analog |= FAILED(pJoystick->SetProperty(DIPROP_DEADZONE, &dipw.diph)) ? false : true; } From 5e34dbf3fcdf39777979fc8022452144e020c27c Mon Sep 17 00:00:00 2001 From: Peter Tissen Date: Sun, 1 Jun 2014 12:22:38 +0200 Subject: [PATCH 4/4] limit Dinput devices to the max supported in input_state and update the native submodule to get the MAX_NUM_PADS constant --- Windows/DinputDevice.cpp | 7 ++++++- native | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Windows/DinputDevice.cpp b/Windows/DinputDevice.cpp index e2df8dc610..f25559bc6c 100644 --- a/Windows/DinputDevice.cpp +++ b/Windows/DinputDevice.cpp @@ -143,8 +143,13 @@ DinputDevice::DinputDevice(int devnum) { return; } + if (devnum >= MAX_NUM_PADS) + { + return; + } + getDevices(); - if ( (devnum > devices.size()) || FAILED(getPDI()->CreateDevice(devices.at(devnum).guidInstance, &pJoystick, NULL))) + if ( (devnum >= devices.size()) || FAILED(getPDI()->CreateDevice(devices.at(devnum).guidInstance, &pJoystick, NULL))) { return; } diff --git a/native b/native index f4d66f5c50..d3fb0459e6 160000 --- a/native +++ b/native @@ -1 +1 @@ -Subproject commit f4d66f5c5057300f2ca83e46657b17de8dd472d2 +Subproject commit d3fb0459e6eb526a986d1e0d3d4f9e18a650621a