godot
                                
                                 godot copied to clipboard
                                
                                    godot copied to clipboard
                            
                            
                            
                        Prevent double input events on gamepad when running through steam input
Fixes #75480
The Problem
During GDC2023 and when generally testing on Steam Deck units, we found that a single gamepad would often register inputs twice under certain circumstances. This was caused by SteamInput creating a new virtual device, which Godot registers as a second gamepad. This resulted in two gamepad devices reporting the same button presses, often leading to buggy input response on games with no multi-device logic or otherwise could cause Steam controller rebindings to not work as intended (for example, swapping o and x on a PlayStation pad if that feature isn't supported by the game.)
SDL2 gets around this by taking in a list of devices that are to be ignored via system environment. When valve sees a controller that wants to be routed through SteamInput, they push a new VID/PID entry onto the environment variable SDL_GAMECONTROLLER_IGNORE_DEVICES matching the source gamepad so that the SDL based games will only read inputs from the virtual device remappings.
Proposed Solution
This PR fixes this issue by leveraging the same logic that SDL uses. As we are already using SDL gamepad related HID mappings, there shouldn't be much harm in leveraging another environment variable for usb device ignoring capabilities.
This feature is currently only being used on LinuxBSD platform, but can be applied to other platforms that need it due to the implementation in the Input class.
Considerations
- This environment variable parsing and should_ignore_deviceapi could be moved to another class. For example, they could be stored inJoypadLinux/JoypadOSX/JoypadXXXif we determine that we only really need this functionality for a few select platforms.
- It's very likely that this logic will also be needed on OSX as SDL2 does so on that platform as well. I don't have a computer to test this so I left it unimplemented. In theory, JoypadOSX would also simply call should_ignore_devicebefore registering controller connection to determine whether or not the device should be ignored.
- Windows supposedly doesn't need to take this into account, as Valve is doing their own thing on that platform with regards to virtual game inputs. Having said that, if it is determined that Windows does need this, it should also use a pretty similar solution to what's provided here.
Testing Procedure
Collapsed for ease-of-reading
Reproduction, Verification
- Download the attached testing project
- Get vanilla Godot 4.0 template_debug export target
- Verify that only 1 A Buttonpress occurs when running the testing project standalone (no steam)
- Export the testing project as a game via vanilla 4.0
- Load the game via Steam, make sure that Steam Input is enabled for your device (on Xbox/Nintendo controllers, this often needs to be done manually.)
- Observe when you press the A button (or X Button on PS layout, or the B Button on Nintendo controller layout) that there's two button presses registered on a single frame for the single gamepad.
Testing
- 
Compile this branch, both editor and template_debug export target. 
- 
Verify that only 1 button press occurs when running the testing project standalone (no steam) 
- 
Load the testing project in the editor and export. 
- 
Reload the game via Steam, making sure to use the same Steam Input settings as the reproduction. 
- 
Observe when you press the A button (or X button PS layout, B button Nintendo layout) there's now only one button press registered for the single gamepad. 
- 
[x] Controller input should work when running Godot without steam input 
- 
[x] Controller input should work when running Godot through steam input, with only one button press registered per button. 
- 
[x] Controllers should be rebindable via SteamInput when the user wants to take advantage of system-level customization features (for example, swapping buttons or axis.) 
I've gone ahead and made the suggested changes as they all seem to be a good idea. I haven't tested it yet but I'll find some time to test it on Monday.  I presume it will all work, but I think one of the changes suggested that I cast to a 16bit int and then shift 16 bits, which I think wouldn't work due to bit limitation so instead I'm casting the vid to 32bit int and shifting that left 16bits. I think that seems correct to me at least.
[...] I think one of the changes suggested that I cast to a 16bit int and then shift 16 bits, which I think wouldn't work due to bit limitation [...]
You're right. My suggested code was wrong.
Ok I've made all the necessary changes.
I've also relocated the conditional branch within the joypad_linux.cpp which was arguably in the wrong place. This seems to work more consistently than the previous iteration (which would have to double-check with godot whether or not the controller is properly "connected" or not, which is unnecessary.) Now devices should be ignored and never registered as a joystick if that's what steam wants.
@YuriSizov Thanks again, went ahead and fixed the last remaining problems.
Thanks!
If no one can confirm about Mac in the short term, we can just merge and iterate if news finally come on that later.
@RandomShaper, as I have been able to reproduce from my Linux desktop as well as a Steam Deck, I was (surprisingly) unable to reproduce from my Mac.
I only tested against branch 3.x. But given what I've seen of both versions, it's likely the same behavior for 4.x.
Cherry-picked for 4.1.2.