fix(input): read a steam controller's gyro when steam starts the game #2495
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2495
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/steam-controller-gyro"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #1897.
Started from Steam, SDL gets Steam's controller ignore list (
SDL_GAMECONTROLLER_IGNORE_DEVICES), which has Valve's own pads in it, along withSDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD=1. So SDL only opens Steam Input's stand-in pad. That pad has no gyroscope, so motion controls found nothing.host/steam_pads.h(new, no SDL in it): takes Valve's0x28deentries out of the list, except the Steam Deck's built-in controls (0x28de/0x1205), and keeps the rest in order. The Deck's stay because SDL's Steam Deck driver resets their trackpad and button settings as it opens them, and that isn't tested beside Steam Input in Game Mode yet. A list from a file (@path) or one too long for the buffer is left as it is. Also the rule for when a pad gives its motion only.host/hos_sdl.c/.h: three optional calls,SDL_Get(Gamepad|GameController)Vendor,SDL_Get(Gamepad|GameController)SteamHandleandSDL_SetHintWithPriority(SDL 2.30+ and SDL3 3.2+). The list is set with override priority, since Steam's environment variable beats a plainSDL_SetHint. An SDL without all three leaves the list as Steam set it, so a real pad and its stand-in never both play.host/pad_sdl.c: while a stand-in pad (one with a Steam handle) is open, the real Valve pad gives its motion only. Its buttons and sticks come through the stand-in as the player's Steam layout maps them, so they aren't counted twice, and its rumble goes through Steam too. Its presses still mark it the pad used last, somotion_pollreads its gyro. It isn't counted in the controller total or used for the prompts name. When no stand-in is open, the real pad plays as before. A real Valve pad that SDL opens with no motion sensors (an SDL with no driver for it) is logged once, so the next person with #1897 sees why.tests/test_steam_pads.py: the list filter (the Deck's entry kept) and the motion-only rule.SDL requirement: gyro only comes through if the SDL the game loads has a driver that reads the pad's motion sensors. CachyOS's
sdl33.4.18 and 3.4.20 and Steam's bundled SDL3 do for the 2026 puck. One without it gets this line and no gyro, and buttons work as before:Testing
Linux (CachyOS, Hyprland), 2026 Steam Controller (wireless puck). Not tested with the original Steam Controller.
Started from Steam: gyro works and each input registers once. The log:
(Those runs predate
41d904e8, so the first line readsValve's controllers (not the Steam Deck's) taken out...with the current code. Steam's stand-in pad sometimes comes through as#SettingsController_SteamController, Steam's untranslated name.)Started on its own with Steam closed: there's no list and no stand-in pad, so none of this runs, and the real pad plays as before, buttons and gyro:
Focus switches: three switches to another window and back on the nightly plus this change, with no re-arm (
its gyroscope stopped changing) in the log. That doesn't answer the IMU question yet: on Hyprland, Steam's controller log shows no layout change during those switches, so Steam likely never moved to a gyro-less layout. A run that forces one (the Steam overlay, or a Deck in Game Mode) is next.tests/test_steam_pads.py,test_hd_rumble.py,test_gyro_rearm.py,test_touchpad_menu.pyandtest_motion.pypass.Not tested on Windows. The issue was reported there too and the code path is the same.
Thanks for working on this. I hit the same thing with a 2026 Steam Controller (puck) on Garuda Linux and built a fix before I saw this PR, so here are a couple of things I ran into.
SDL version. This approach needs an SDL that has a driver for the 2026 controller. My system SDL3 is 3.4.18 (Arch package), and as far as I can tell it doesn't have one. There's no SDL_JOYSTICK_HIDAPI_STEAM_TRITON hint in the library, only the original Steam Controller and the Deck. With an SDL like that, taking 28de out of the ignore list wouldn't give SDL a pad with a gyro. Your log shows "Steam Controller" connected (SDL), with a gyroscope, so it looks like CachyOS ships a newer SDL. Do you know which version it is? It might be worth falling back gracefully, or mentioning the requirement, for distros and the AppImage's SDL that don't have the driver yet.
SDL's driver and Steam both managing the controller. I did the same thing in another recompilation port, where SDL opened the real Valve controller next to Steam Input. SDL's driver and Steam both set lizard mode and the IMU setting, and I got gyro dropouts. Separately, Steam turns the controller's IMU off whenever the active layout uses no gyro, and Alt+Tab can switch layouts. When that happens, the reports keep coming with the IMU values frozen rather than zeroed, so the gyro just stops. The test_gyro_rearm path would catch that, but only after 1.2 s.
What I did instead. I left the ignore list alone and read the controller's own HID reports through SDL's hidapi. It only reads; Steam keeps managing the controller. It feeds the gyro and accelerometer to motion_poll only when no SDL pad has motion. It watches the IMU's own timestamp, and if that stops moving for 250 ms it resends the IMU-mode feature report (01 87 03 30 18 00), only to the puck interface that's actually reporting, since sending to an empty slot blocks for about 150 ms. That means it doesn't need a Triton driver in SDL, and SDL never opens the controller. It's Linux only, about 300 lines in a new host/steam_triton.c, plus small hidapi wrappers in hos_sdl.c and a parser test. It works on my machine with v2.1.0 and SDL3 3.4.18, both on the puck and after Alt+Tab.
I don't want to open a competing PR. If it's useful, I'm happy to share the branch, or to open it as a fallback for when SDL has no Triton driver, whichever you and the maintainers prefer.
The design is careful. The list filter is pure and tested, Steam's list is only changed when the SDL has all three calls, the motion-only pad is left out of the count, rumble and the prompts name, and
motion_pollonly picks pads with sensors, so the stand-in never wins. CI is green.My concern is the Steam Deck, which is tier 1 in
docs/workflow.md. Its built-in controls are0x28de/0x1205, in the same ignore list, sohost/pad_sdl.c:110takes them out too. Under Steam, Game Mode included, SDL's HIDAPI Steam Deck driver then opens them alongside Steam Input. On open, that driver sendsID_CLEAR_DIGITAL_MAPPINGSand sets both trackpads toTRACKPAD_NONE, then repeats every ~200 reads. That may be harmless, and it would give Deck players gyro, but it was only tested with a Steam Controller on a desktop. Could you test on a Deck in Game Mode (trackpads, Steam/QAM buttons, gyro, inputs counted once)? If not before merge, keep0x28de/0x1205in the list for now.@Joey-Magnus Good catch. I don't want to change Deck behavior without testing it, so 0x28de/0x1205 stays in the list now and only the Steam Controller entries come out (
41d904e8). I have a Deck and will test Game Mode (trackpads, Steam/QAM, gyro, inputs counted once). If it holds up, a follow-up PR takes 0x1205 out.@rivyr Thanks, this is useful. On SDL: my Steam run used CachyOS's sdl3 3.4.18-1.1 and the no-Steam run 3.4.20-1.1. Both opened the puck on hidraw with gyro and accelerometer, and so did Steam's bundled SDL3 in a standalone probe. Not sure why yours didn't, so a requirement note makes sense. On dropouts: fair point. I didn't see any, but my sessions were short and I didn't Alt+Tab into a layout without gyro. Your read-only approach avoids the two-managers problem entirely, so I'd be glad to see it as a fallback, or as the main fix if the maintainers prefer it.
Reviewed at
41d904e8(the Deck-entry commit on top ofd64432ea). Code reads correct to me and I agree with Joey-Magnus's read of the design. His Steam Deck concern is addressed:0x28de/0x1205now stays in the list (steam_pads.h, with a test), so Game Mode is unchanged until you've tested it.What's still open:
motion_poll's 1.2 s re-arm covers it, but gyro will stutter for that long after a layout switch/Alt+Tab. Your sessions were short, so a longer run with an Alt+Tab into a gyro-less layout would show whether it's livable. rivyr offered a read-only hidapi fallback; whether that should be the main fix is NotRexed's call.Nits:
0x28deentries come out; since41d904e8the Deck's0x1205stays. Worth a sentence, and the log line in the Testing section differs from the one in the code now ("not the Steam Deck's").GetProcAddressfor the three calls,getenvfor an ASCII list) is a problem.CI: the Windows and AppImage builds pass; the Python source checks and the title check were still pending when I looked. Decision on merge stays with NotRexed given the two open questions above.
Re-reviewed at
11529114(one new commit on top of41d904e8,host/pad_sdl.conly).Fixed: my point about an SDL with no driver for the pad. The log line when a Valve pad opens with no motion sensors is there now (said once, on the motion-only change), and it reads right:
pads[i].motionis set from the gyro and accel checks at open, so the branch only fires for a pad that really has none. Thanks for that, and for the SDL versions in your reply to rivyr.Still open:
Nits:
0x28deentries come out, but the Deck's0x1205stays since41d904e8, and the Testing log doesn't show the new no-sensors line. One sentence on each would do. A note on the SDL requirement (a build with a driver for the 2026 pad) would help whoever hits #1897 next.CI: the Windows Vulkan build, AppImage, builder and Linux checks pass; the title check was still pending.
Python source checks (windows-2022, part 2/2)fails intest_pcache_header_read.py(fopendeprecated, nofseeko) andtest_psychov35.py(scanfdeprecated), both compile errors in test fixtures this PR doesn't touch, so not from this change. No conflicts.@hckrman101 Thanks for the re-review. The PR description is updated: the Deck's
0x1205stays, the no-sensors log line and the SDL requirement are in, and it says only the 2026 puck is tested, not the original Steam Controller.On the IMU question, I don't have an answer yet. I ran a session on the nightly plus this change and switched to another window and back three times, and the log shows no re-arm. But this machine runs Hyprland, and Steam's controller log shows no layout change during those switches, so Steam likely never moved to a gyro-less layout. I'm not counting it as a pass. Next I'll force a layout change with the Steam overlay on desktop and with Steam/QAM on a Deck in Game Mode, and post what the log shows.
That session turned up a separate bug: with the game behind another window, the buttons stop but the gyro keeps turning the camera, because
GetSixAxisSensorStatesdoesn't check the window. It's older than this PR, so it has its own: #2542.@Joey-Magnus Update on the Deck test: I couldn't get the game running on my Deck. The nightly hangs the GPU as soon as a save loads (
ring gfx_0.0.0/comptimeouts in the kernel log), which looks like the hangs collected in #1000 rather than anything in this PR. I'll try again another time, but probably not soon, so the Deck's0x1205should stay in the list for now, as it is.github-sync referenced this pull request2026-11-12 06:22:54 +00:00