Repository navigation
fix: Steam Controller survives suspend/resume (hidraw/evdev hotplug hardening) - #14
Merged
Merged
Conversation
raise of an unnarrowed Future.exception() and a list/set variable redefinition — both pre-existing on main, tripped by the newer mypy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Steam Controller went dead after every sleep/wake cycle because the hotplug skeleton couldn't see USB re-enumeration: - HidrawMonitor watched /dev/hidraw for inotify events — that path is not a directory (nodes are /dev/hidrawN), so inotify setup failed silently and hotplug degraded to the 45s rescan. Watch /dev instead, filtering events to hidraw* names (new WATCH_PREFIX; InputMonitor filters event*). - An EOF read from a re-enumerated device never evicted the dead fd (only OSError did); it now does. - A node re-created under the SAME name was never reopened (_scan dedups by path). _scan now revalidates open fds against the node's inode/rdev and reopens on mismatch — which also hands hidraw nodes a fresh detector (fresh warmup + runaway guard). - The periodic rescan now always runs, even when inotify is available, as a backstop for events lost across suspend. - Watch mask gains IN_ATTRIB: udev applies the uaccess ACL after IN_CREATE, so a create-time EACCES open gets retried on the chmod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A suspend can leave the hidraw nodes fully intact (no re-enumeration, as observed on the reference box) — in that case none of the hotplug fixes engage, but the detectors' volatility masks are stale and the controller's post-resume re-sync burst can permanently latch the runaway guard, killing detection until the agent restarts. Detect the resume directly: (CLOCK_BOOTTIME - CLOCK_MONOTONIC) grows by exactly the time spent suspended, so a jump in that delta is a reliable, logind-free resume signal. On a jump, every watched node gets a fresh SteamControllerActivity (fresh warmup + guard). Controller sleep/wake does not move the delta, so the first press after a controller nap is still detected immediately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On the reference HTPC, the Steam Controller stopped unblanking the TV after every suspend/resume cycle until the agent was restarted.
Root causes
Three compounding bugs in the
_HotplugMonitorskeleton:HidrawMonitor.WATCH_DIR = b"/dev/hidraw"is not a directory — hidraw nodes live directly in/dev.inotify_add_watchfailed silently, so hidraw hotplug ran only on the 45 s fallback rescan.b""(notOSError), so it was fed to_handleforever._scan— a node deleted and re-created as the same/dev/hidrawNis present in both glob and_fds, so it was never reopened; the stale fd and its stale detector persisted indefinitely.Fix
/devwith name filtering (WATCH_PREFIX = b"hidraw"/b"event"), parsing the inotify event stream so unrelated/devchurn doesn't trigger rescans;IN_Q_OVERFLOWforces one. Mask gainsIN_ATTRIBso udev's post-create ACL application retries an EACCES open._scanrevalidates open fds against the node's(st_ino, st_rdev)and reopens on mismatch — a replaced hidraw node gets a freshSteamControllerActivity(fresh warmup + runaway guard).The evdev
InputMonitorgains the same hardening.Also includes a small chore commit fixing two pre-existing errors surfaced by mypy 2.x (unnarrowed
Future.exception()raise; list/set variable redefinition).Tests
New coverage: inotify name filtering (match / non-match / queue overflow), EOF eviction (fd + detector), same-name inode-swap reopen with fresh detector, unchanged-node fd stability, stat-failure staleness,
WATCH_DIR/WATCH_PREFIXregression assert, Linux-only real inotify watch on/dev. 524 passed, ruff + mypy clean.🤖 Generated with Claude Code