godot: Controller reconnection does not emit `joy_connection_changed` signal on Steam Deck
Godot version
3.5.1.stable
System information
Steam Deck
Issue description
Steam Deck owners who have played Cassette Beasts report that the only way they can get external controllers to reconnect after a disconnection is to restart the whole game.
Cassette Beasts connects to the Input.joy_connection_changed
signal and logs when controllers are connected or disconnected. It also logs the controllers already available when the game starts. From the logs players have sent me I can see that:
- controllers connected while the game starts up are detected (via
Input.get_connected_joypads
) - controller disconnection events are detected
- no controller connection events are detected
It seems like the engine does not seem to detect controller connection events. However I don’t know whether this is a Steam issue or an engine issue.
I did some additional testing around this (with bluetooth controllers, but not any wired controllers):
- Reconnection is not detected in Gaming Mode
- Reconnection is not detected in Desktop Mode, when launched from the Steam client
- Reconnection is correctly detected in Desktop Mode, when launched from the install directory, or the Godot editor.
- The issue is unaffected by whether I use the stock linux 64-bit export template or my modified engine binaries
- The issue is unaffected by the model of controller. It happens with xbox and PS5 controllers at the very least
- Although the engine doesn’t detect the reconnected controller, there are no problems using it to navigate Steam
- Adding a Godot project to Steam as a non-Steam game for testing is not enough to trigger the bug. It seems like the game actually has to be published through Steam (or overwrite the pck and binary of a project that has been published through Steam)
- Whether Steam Input is enabled or not for the controller and game makes no difference
Steps to reproduce
- Install a Godot game with controller support on a Steam Deck.
- Connect a bluetooth controller to the Steam Deck
- Launch the game
- Check whether the controller has been detected by the game
- Shut down the controller, and then power it on again
- Check whether the controller has been detected by the game
Minimal reproduction project
Attach this script to a scene, export the project, publish it through Steam (or overwrite the pck and executable in the install directory of a Godot project already published through Steam), then launch it through Steam:
func _ready() -> void:
for joypad in Input.get_connected_joypads():
print("Detected joypad device: ", joypad)
Input.connect("joy_connection_changed", self, "_on_joy_connection_changed")
func _on_joy_connection_changed(device: int, connected: bool) -> void:
if connected:
print("Detected joypad connection: ", device)
else:
print("Detected joypad disconnection: ", device)
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 18 (11 by maintainers)
Commits related to this issue
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to akien-mga/godot by akien-mga a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to akien-mga/godot by akien-mga a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to akien-mga/godot by akien-mga a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to akien-mga/godot by akien-mga a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to akien-mga/godot by akien-mga a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to akien-mga/godot by akien-mga a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to akien-mga/godot by akien-mga a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to hakro/godot by akien-mga a year ago
- Squashed commit of the following: commit 467eecb12bce4955fdb422aa2a171e45b94ae234 Author: Aaron Franke <arnfranke@yahoo.com> Date: Fri May 26 17:22:29 2023 -0500 Add a get_node_index method to... — committed to jeronimo-schreyer/godot by jeronimo-schreyer a year ago
- Squashed commit of the following: commit 467eecb12bce4955fdb422aa2a171e45b94ae234 Author: Aaron Franke <arnfranke@yahoo.com> Date: Fri May 26 17:22:29 2023 -0500 Add a get_node_index method to... — committed to jeronimo-schreyer/godot by jeronimo-schreyer a year ago
- Squashed commit of the following: commit 467eecb12bce4955fdb422aa2a171e45b94ae234 Author: Aaron Franke <arnfranke@yahoo.com> Date: Fri May 26 17:22:29 2023 -0500 Add a get_node_index method to... — committed to jeronimo-schreyer/godot by jeronimo-schreyer a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to haunted-loaf/godot by akien-mga a year ago
- Linux: Don't use udev for joypad hotloading when running in a sandbox udev doesn't work in sandboxes, notably the new Steam container runtime as found notably on the Steam Deck, and in Flatpak/Snap p... — committed to akien-mga/godot by akien-mga a year ago
Yes, SDL’s approach is the least-bad way to deal with this. The Steam container runtime cannot make this work more transparently than it already does, because we’re constrained by how udev and evdev were designed. If Godot doesn’t use SDL, then it’ll be necessary to reinvent essentially the same logic that SDL uses, including having a heuristic to decide which input devices are gamepads suitable for your game and which ones are accelerometers or other weird things which should be ignored unless supported, based on their evdev capabilities and/or the information in
/sys
.Another possible route would be to treat SDL as being a “platform library” on Linux, analogous to how you presumably use DirectX interfaces on Windows, and build higher-level interfaces over the top of it where needed. Some other game engines already work that way, and so does Proton.
I’ve thought about providing a shim/modified libudev that fakes the information you would have seen on the host system by using IPC to a service running the real libudev outside the container; but libudev’s API is really at too low a level for that to be particularly feasible, so this is unlikely to happen.
The reason this is different on Steam Deck is that the Steam Deck’s operating system is a fast-moving rolling release based on Arch Linux, so it’s too much of a moving target to be reasonable for games to run on it directly or via the legacy LD_LIBRARY_PATH Steam Runtime - that would come with a high risk of games working in 2023, but failing to work in 2025 or 2030 when the operating system has changed too much for them to keep up with. Instead, by default native Linux games on Deck run in the Steam container runtime (specifically the scout-on-soldier version, which provides ABI compatibility with the legacy LD_LIBRARY_PATH runtime). That’s designed to be more a stable thing to support for years to come.
It is also possible to opt-in to using the newer, legacy-free Steam Runtime 3 “sniper” container runtime by editing a game’s metadata, but at the moment there’s no interface for that in Valve’s partner web-UI, so that change can only be done with help from a Valve developer. When sniper becomes available on a self-service basis, I suspect that the developers of games based on open source engines like Godot will want to start targeting that preferentially, with binaries built in a sniper container.
On desktop Linux, you can force a game to use the same runtime as on the Deck via Properties -> Compatibility -> Force the use of -> Steam Linux Runtime (behind the scenes, that’s the scout-on-soldier container runtime). You should find that you get the same results as on Deck: if you’re relying on udev for device enumeration, then controllers that are already there at startup (in particular the built-in controls) will often work, but hotplugging USB controllers or connecting Bluetooth controllers during gameplay usually won’t, because the notification that says a new controller was connected doesn’t arrive in your game code.
Running a game as a Flatpak app, or inside a Flatpak app like the unofficial Flatpak version of Steam (even without using the Steam container runtime), would have exactly the same issues as in the Steam container runtime for essentially the same reasons. That’s why SDL detects both.
The main thing that you get from udev, beyond what you can get from
/dev
and/sys
, is that udev rules can mark devices with metadata likeID_INPUT_JOYSTICK
andID_INPUT_ACCELEROMETER
according to a heuristic and/or a database of known devices. When accessing devices directly, you don’t get access to that information, so either game, engine or library code needs to implement a heuristic to figure out what each device is, so that you won’t accidentally treat an accelerometer/gyro as a joystick and move the player character around when the device is tilted. I would personally recommend SDL’s approach: use libudev when running directly on the host system, or access/dev
and/sys
directly when a container is detected.I have some merge requests open in SDL to improve its heuristic. If Godot isn’t using SDL, then it might be necessary to track changes like those and apply the equivalent in Godot.
(PS: I haven’t got much further with Cassette Beasts than the point the demo ended at, but I’m enjoying it and will be recommending it to friends 😃
I’m not a Valve or Steam Input developer, but I believe this is how Valve want games/engines/middleware to deal with Steam Input legacy mode bindings.
(What they would ideally like games to do is the action-based API, where the game uses the Steamworks API to tell the Steam Input layer what “actions” the game has, in terms of game-specific abstract verbs like “jump” and “talk to NPC” rather than game controller buttons like A and X, and then receive events from Steam Input in terms of those abstract game-specific actions, with the player able to remap those abstract actions to a button of their choice. But I suspect that’s probably something that can only usefully be implemented per-game, and difficult to do in a generic game engine that can’t know what gameplay mechanics the game will have.)
I could only find a pretty lengthy thread on Steam forums about this when I was looking it up last, as I was confused to why it was working through my desktop steam client w/ steam input but uniquely not on the Steam Deck. However, I can’t find it right now – I’ll comb through my laptop history to see if I can find it again.
What I do know is that the existing MR fixes this bug when launching a game through steam that has steam input enabled and also gives preferential treatment to the users desired keybinds (as in, it prefers the steam input rebindings first and foremost, and ignores the original hardware.) There might be ways to work around this issue if we create a work around for
STEAM_DECK=1
, but I haven’t found a clean solution yet. I’ll keep you updated.edit: I didn’t find the exact thread I saw but I found this thread which seems to be a similar unresolved issue occurring on multiple (non-godot) games. I think Valve will probably fix it once they add proper controller “order” reassignment, as I think their current external controller support is a bit of a band-aid to the fact that the deck controller is always considered the “first player” by order at which the controllers are connected. The problem is that Valve can be kind of hard to know what they plan with regards to their roadmap, so it might be worth me making a thread on the bugs section to highlight this particular issue (or perhaps bring it up with people of interest via direct email.)
#76961 should fix this for 4.x (4.0.3 and 4.1), and #76962 for 3.x (3.5.3 and 3.6). I tested the 3.x version successfully with Cassette Beasts, wrote some details in #76962.
I still plan to look into this for another PR.
Thanks for your detailed input, that’s very helpful! We’ll definitely have to go with a SDL like approach (or give up and integrate SDL, but we try to keep our thirdparty dependencies as lean as possible).
The current non-udev fallback code indeed polls
/dev/input
every second currently: https://github.com/godotengine/godot/blob/master/platform/linuxbsd/joypad_linux.cpp#L214-L235And then we have some barebones checks to validate what may or may not be a joystick: https://github.com/godotengine/godot/blob/master/platform/linuxbsd/joypad_linux.cpp#L323-L386
It’s relatively old code which has been improved a bit over the years but still has some issues (#59412). Things mostly work, until they don’t as seen here 😃
As a side note, to ensure maximum portability in Godot, we typically link all our thirdparty dependencies statically, aside from the ones which are too close to the OS, which we instead dlopen (and have fallback code when missing), such as X11, pulseaudio, alsa, dbus, udev.
So Godot’s requirements on the host system are fairly low, and in my experience Godot games don’t require the Steam Linux Runtime at all:
With Godot 4 we even load X11, GL, or Vulkan dynamically (and soon Wayland), so the dependencies are very lean:
So those binaries should be fairly portable, whether running in Steam or standalone (and users typically don’t build them against the Steam Runtime, but just use our prebuilt portable binaries)… At least until glibc’s next soname change 😛
Yeah that’s where for Godot specifically, not using libudev from the Steam Runtime but dlopen’ing the one from the host OS which would match udevd should hopefully work better.