From f869b434ba40ce9dc26a943b5f989616e9caed7d Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 14 Jun 2026 23:14:36 +0000 Subject: [PATCH] fix(host): input follows session per-connect + restore-guard on desktop switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes from live Bazzite testing of the managed-Gaming + mid-stream work: 1. Input now FOLLOWS the active session. The host-lifetime injector was pinned to the first backend it opened and only reopened on an inject FAILURE — but with Feature A keeping the managed gamescope warm, its EIS socket stays alive, so a switch to the KDE desktop + reconnect kept injecting into the idle gamescope (input silently dead on KDE). injector_service_thread now compares the resolved input backend (default_backend() ← PUNKTFUNK_INPUT_BACKEND, set per connect by apply_input_env, and on a mid-stream switch) each event and reopens when it changes. Fixes input on a Gaming->Desktop reconnect AND Feature B's mid-stream input re-route, with no plumbing. 2. Debounced TV-restore no longer yanks you back to gaming. do_restore_tv_session now checks detect_active_session(): if a desktop session is active (the user switched), it tears down the idle managed gamescope but does NOT restart the gaming autologin. Observed live: the restore fired and restarted gamescope-session-plus@ogui-steam while the client was already on the KDE desktop. Also: document PUNKTFUNK_SESSION_WATCH (Feature B opt-in) in the Bazzite host.env and correct the managed-default description. Compiles, clippy/fmt clean, 78 tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/m3.rs | 22 ++++++++++++++++--- .../punktfunk-host/src/vdisplay/gamescope.rs | 13 +++++++++++ packaging/bazzite/host.env | 7 ++++-- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/crates/punktfunk-host/src/m3.rs b/crates/punktfunk-host/src/m3.rs index 6875c6f..bb128bc 100644 --- a/crates/punktfunk-host/src/m3.rs +++ b/crates/punktfunk-host/src/m3.rs @@ -988,21 +988,36 @@ const INJECTOR_REOPEN_BACKOFF: std::time::Duration = std::time::Duration::from_s /// sender have dropped (host shutdown), which drops the injector and closes its portal session. fn injector_service_thread(rx: std::sync::mpsc::Receiver) { let mut injector: Option> = None; + let mut open_backend: Option = None; let mut last_failed: Option = None; for ev in rx { + // The resolved input backend (PUNKTFUNK_INPUT_BACKEND, set per connect by apply_input_env, + // also on a mid-stream session switch) may have changed since we opened. Reopen against it + // so input FOLLOWS the active session instead of injecting into a stale, still-warm backend + // (e.g. the managed gamescope's EIS socket after the user switched to the KDE desktop). + let want = crate::inject::default_backend(); + if injector.is_some() && open_backend != Some(want) { + tracing::info!( + ?open_backend, + ?want, + "input: backend changed — reopening injector for the active session" + ); + injector = None; + last_failed = None; // re-resolve immediately + } if injector.is_none() { // Open on the first event; after a failure wait out the backoff before retrying (a // few events drop during setup — acceptable, input is lossy). let ready = last_failed.is_none_or(|t| t.elapsed() >= INJECTOR_REOPEN_BACKOFF); if ready { - let backend = crate::inject::default_backend(); - match crate::inject::open(backend) { + match crate::inject::open(want) { Ok(i) => { tracing::info!( - ?backend, + backend = ?want, "punktfunk/1 input injector ready (host-lifetime)" ); injector = Some(i); + open_backend = Some(want); last_failed = None; } Err(e) => { @@ -1018,6 +1033,7 @@ fn injector_service_thread(rx: std::sync::mpsc::Receiver) { // a later event (covers a gamescope EIS socket that respawns with its session). tracing::warn!(error = %format!("{e:#}"), "inject failed — reopening injector"); injector = None; + open_backend = None; last_failed = Some(std::time::Instant::now()); } } diff --git a/crates/punktfunk-host/src/vdisplay/gamescope.rs b/crates/punktfunk-host/src/vdisplay/gamescope.rs index e61b6b2..2f877c9 100644 --- a/crates/punktfunk-host/src/vdisplay/gamescope.rs +++ b/crates/punktfunk-host/src/vdisplay/gamescope.rs @@ -266,6 +266,19 @@ fn do_restore_tv_session() { } stop_session(SESSION_UNIT); // our gamescope/Steam session, so Steam is free for the autologin *MANAGED_SESSION.lock().unwrap_or_else(|e| e.into_inner()) = None; + // Only bring the gaming autologin BACK if the box is still meant to be in gaming mode. If the + // user switched to a desktop session (KDE/GNOME/wlroots) in the meantime, don't yank them back + // to gaming — leave the desktop alone. (We still stopped our idle managed session above.) + use super::ActiveKind; + if matches!( + super::detect_active_session().kind, + ActiveKind::DesktopKde | ActiveKind::DesktopGnome | ActiveKind::DesktopWlroots + ) { + tracing::info!( + "gamescope: a desktop session is active — not restoring the TV gaming session" + ); + return; + } for unit in units { let _ = Command::new("systemctl") .args(["--user", "start", &unit]) diff --git a/packaging/bazzite/host.env b/packaging/bazzite/host.env index 9f6b70c..2fbbbd6 100644 --- a/packaging/bazzite/host.env +++ b/packaging/bazzite/host.env @@ -1,8 +1,8 @@ # punktfunk host config for Bazzite (~/.config/punktfunk/host.env). # # The compositor + input backend are AUTO-DETECTED per connect from the ACTIVE session: the host -# follows the box as you flip between Steam Gaming Mode (gamescope — attached to the running -# session, no churn) and a KDE/GNOME Desktop (KWin/Mutter virtual output at the client's mode). +# follows the box as you flip between Steam Gaming Mode (gamescope — a managed session at the +# CLIENT's resolution) and a KDE/GNOME Desktop (KWin/Mutter virtual output at the client's mode). # So nothing here forces a backend — only the trustworthy anchors stay. XDG_RUNTIME_DIR=/run/user/1000 @@ -26,3 +26,6 @@ PUNKTFUNK_ZEROCOPY=1 # stays live on the panel, no Steam restart), set: # PUNKTFUNK_GAMESCOPE_ATTACH=1 # PUNKTFUNK_GAMESCOPE_APP=steam -gamepadui # only for an ad-hoc bare-spawn fallback +# +# Follow a Gaming<->Desktop switch MID-STREAM (rebuild the backend in place, no reconnect): +# PUNKTFUNK_SESSION_WATCH=1