From 7d2491ea10bebe158ceb6cb028e75801cbbd789f Mon Sep 17 00:00:00 2001 From: jasonwitty Date: Wed, 9 Sep 2026 13:30:11 -0700 Subject: [PATCH] Survive a missing touch panel at boot; retry the open briefly Two changes to the unattended path, both prompted by deploying to the rack. The autostart can win the race against USB enumeration at boot, so the panel may simply not exist yet when socktop-swipe starts. Opening it now retries for ten seconds -- long enough for enumeration, short enough that a genuinely wrong device path still reports promptly. If the panel cannot be opened at all, `run` no longer treats that as fatal. By that point the dashboard is already on the wall, and exiting would replace a display you cannot swipe with no display at all. It now says loudly that gestures are inactive, points at the movement subcommands, and carries on serving the control socket. `daemon` and `doctor` still fail fast, since there is no display at stake in either. Co-Authored-By: Claude Opus 5 --- src/main.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index e62fe55..d54f51e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -253,7 +253,16 @@ fn run(cfg: &Config, no_touch: bool) -> Result<()> { }); if !no_touch { - spawn_panel(cfg, tx)?; + // Deliberately not fatal. The dashboard is already on the wall by this + // point; exiting because the panel is missing would replace a display + // you cannot swipe with no display at all. Say so loudly and carry on + // serving the control socket, which is still a way to drive it. + if let Err(e) = spawn_panel(cfg, tx) { + eprintln!("socktop-swipe: touch gestures are NOT active: {e:#}"); + eprintln!( + "socktop-swipe: the dashboard is up; drive it with `socktop-swipe forward` etc." + ); + } } drive(&tmux, &mut grid, rx) } @@ -281,11 +290,30 @@ fn daemon(cfg: &Config, no_touch: bool) -> Result<()> { drive(&tmux, &mut grid, rx) } +/// Open the touch panel, retrying briefly. +/// +/// At boot the autostart can win the race against USB enumeration, so the +/// device is simply not there yet. Ten seconds covers that without making a +/// genuinely wrong device path take ten seconds to report. +fn open_panel(cfg: &Config) -> Result { + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + loop { + match Touchpanel::open(&cfg.touch) { + Ok(p) => return Ok(p), + Err(e) if std::time::Instant::now() < deadline => { + std::thread::sleep(std::time::Duration::from_millis(500)); + let _ = e; + } + Err(e) => return Err(e), + } + } +} + /// Read the panel on its own thread. Opening it here rather than in the thread /// keeps a permission or grab failure on the main path, where it can be /// reported properly instead of vanishing into a detached thread. fn spawn_panel(cfg: &Config, tx: mpsc::Sender) -> Result<()> { - let mut panel = Touchpanel::open(&cfg.touch)?; + let mut panel = open_panel(cfg)?; let gestures = cfg.gestures.clone(); std::thread::spawn(move || { let result = panel.run(|ev| {