Windows: only one HID gamepad is ever usable, and the others are dropped by DirectInput too
Game or games this happens in
N/A
What area of the game / PPSSPP
Summary
On Windows, connecting two controllers that are both in g_psInfos (for example a Switch Pro and a DualSense) makes the second one completely invisible to PPSSPP. It is not handled by HidInputDevice, and it is also filtered out of the DirectInput enumeration, so no backend reports it at all.
Tested with PPSSPP 1.20.1 and confirmed against the 1.20.4 sources. Line references below are from v1.20.4.
Steps to reproduce Connect a Switch Pro controller over Bluetooth. Connect a DualSense over USB. Start PPSSPP and open Settings → Controls → Control Mapping. Only one of the two pads produces any input. The other is not listed and cannot be mapped.
Which one wins depends on the HID device interface enumeration order, so it is stable on a given machine but not predictable or user-controllable. Disconnecting the winning pad makes the other one work immediately.
Cause
Three things I noticed
- HidInputDevice only ever handles a single controller
Windows/InputDevice.cpp:79 creates one instance, pad_ is fixed at 0, and OpenFirstHIDController() returns on the first recognised VID/PID it finds while iterating SetupDiEnumDeviceInterfaces. The header comment already documents this as a known limitation ("Just picks the first one available, for now").
- DirectInput drops every supported HID VID/PID, whether or not the HID layer actually opened them
Windows/DinputDevice.cpp:113:
cpp ignoreDevices_ = DetectXInputVIDPIDs(); HidInputDevice::AddSupportedDevices(&ignoreDevices_);
AddSupportedDevices() inserts all of g_psInfos into the ignore set. Since only one of those devices can ever be opened by HidInputDevice, every additional PlayStation or Nintendo pad is rejected by HID (already busy) and filtered out of DirectInput as well. It ends up with no backend at all.
- bAllowHIDInput does not gate the DirectInput exclusion
The flag is only checked when emitting events (Windows/Hid/HidInputDevice.cpp:239). getDevices() never consults it. As a result, setting AllowHIDInput = False does not fall back to DirectInput for these pads — it makes them entirely mute, which is probably the opposite of what a user disabling the HID backend expects.
I also noticed 2 things while looking at it:
These are separate from the report above, but they are in the same area.
- Device ID collision between the HID and DirectInput backends.
HidInputDevice::DeviceID() (Windows/Hid/HidInputDevice.cpp:214-216) returns DEVICE_ID_PAD_0 + pad_, with pad_ always 0, so always 10. DinputDevice with pDevNum == 0 also emits on DEVICE_ID_PAD_0. A HID pad and the first DirectInput pad are therefore indistinguishable in controls.ini, even though their button keycodes differ — HID PlayStation pads report L1/R1 as NKCODE_BUTTON_7/NKCODE_BUTTON_8 and L3/R3 as NKCODE_BUTTON_THUMBL/THUMBR, while DirectInput only ever emits NKCODE_BUTTON_1..16.
- Key-up events sent to the wrong device ID on disconnect.
HidInputDevice::ReleaseAllKeys() uses DEVICE_ID_XINPUT_0 + pad_ (lines 190 and 207), while UpdateState() sends everything through DeviceID(pad_), which is DEVICE_ID_PAD_0 + pad_. The release events therefore go to device 20 instead of device 10, so any button held when the controller disconnects stays logically pressed on device 10. The two call sites in ReleaseAllKeys() should use DeviceID(pad_) like the rest of the class.
What should happen
Suggested fixes
The third point looks like a one-line change and would already give users a workaround for the first two:
ignoreDevices_ = DetectXInputVIDPIDs();
if (g_Config.bAllowHIDInput) {
HidInputDevice::AddSupportedDevices(&ignoreDevices_);
}A narrower variant, if the HID backend should stay enabled: only add to ignoreDevices_ the VID/PID of the device that HidInputDevice actually opened, rather than the whole supported list. That would let a second PlayStation pad fall through to DirectInput without any user action.
Supporting several HID pads at once would be the complete fix, but it is clearly a larger change.
Logs
No response
Platform
Windows
Mobile device model or graphics card (GPU)
RTX 3050
PPSSPP version affected
PPSSPP 1.20.4
Last working version
N/A
Graphics backend (3D API)
Vulkan
Checklist
- Test in the latest git build in case it's already fixed.
- Search for other reports of the same issue.
- Try resetting settings or older versions and include if the issue is related.
- Try without any cheats and without loading any save states.
- Include logs or screenshots of issue.
Source: hrydgard/ppsspp