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| {