fix(host): input follows session per-connect + restore-guard on desktop switch
apple / swift (push) Successful in 1m15s
ci / rust (push) Successful in 2m12s
ci / web (push) Successful in 28s
ci / docs-site (push) Successful in 30s
ci / bench (push) Successful in 1m35s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
deb / build-publish (push) Successful in 2m27s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m56s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m29s
apple / swift (push) Successful in 1m15s
ci / rust (push) Successful in 2m12s
ci / web (push) Successful in 28s
ci / docs-site (push) Successful in 30s
ci / bench (push) Successful in 1m35s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
deb / build-publish (push) Successful in 2m27s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m56s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m29s
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
/// sender have dropped (host shutdown), which drops the injector and closes its portal session.
|
||||||
fn injector_service_thread(rx: std::sync::mpsc::Receiver<InputEvent>) {
|
fn injector_service_thread(rx: std::sync::mpsc::Receiver<InputEvent>) {
|
||||||
let mut injector: Option<Box<dyn crate::inject::InputInjector>> = None;
|
let mut injector: Option<Box<dyn crate::inject::InputInjector>> = None;
|
||||||
|
let mut open_backend: Option<crate::inject::Backend> = None;
|
||||||
let mut last_failed: Option<std::time::Instant> = None;
|
let mut last_failed: Option<std::time::Instant> = None;
|
||||||
for ev in rx {
|
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() {
|
if injector.is_none() {
|
||||||
// Open on the first event; after a failure wait out the backoff before retrying (a
|
// Open on the first event; after a failure wait out the backoff before retrying (a
|
||||||
// few events drop during setup — acceptable, input is lossy).
|
// few events drop during setup — acceptable, input is lossy).
|
||||||
let ready = last_failed.is_none_or(|t| t.elapsed() >= INJECTOR_REOPEN_BACKOFF);
|
let ready = last_failed.is_none_or(|t| t.elapsed() >= INJECTOR_REOPEN_BACKOFF);
|
||||||
if ready {
|
if ready {
|
||||||
let backend = crate::inject::default_backend();
|
match crate::inject::open(want) {
|
||||||
match crate::inject::open(backend) {
|
|
||||||
Ok(i) => {
|
Ok(i) => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
?backend,
|
backend = ?want,
|
||||||
"punktfunk/1 input injector ready (host-lifetime)"
|
"punktfunk/1 input injector ready (host-lifetime)"
|
||||||
);
|
);
|
||||||
injector = Some(i);
|
injector = Some(i);
|
||||||
|
open_backend = Some(want);
|
||||||
last_failed = None;
|
last_failed = None;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -1018,6 +1033,7 @@ fn injector_service_thread(rx: std::sync::mpsc::Receiver<InputEvent>) {
|
|||||||
// a later event (covers a gamescope EIS socket that respawns with its session).
|
// a later event (covers a gamescope EIS socket that respawns with its session).
|
||||||
tracing::warn!(error = %format!("{e:#}"), "inject failed — reopening injector");
|
tracing::warn!(error = %format!("{e:#}"), "inject failed — reopening injector");
|
||||||
injector = None;
|
injector = None;
|
||||||
|
open_backend = None;
|
||||||
last_failed = Some(std::time::Instant::now());
|
last_failed = Some(std::time::Instant::now());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,6 +266,19 @@ fn do_restore_tv_session() {
|
|||||||
}
|
}
|
||||||
stop_session(SESSION_UNIT); // our gamescope/Steam session, so Steam is free for the autologin
|
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;
|
*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 {
|
for unit in units {
|
||||||
let _ = Command::new("systemctl")
|
let _ = Command::new("systemctl")
|
||||||
.args(["--user", "start", &unit])
|
.args(["--user", "start", &unit])
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# punktfunk host config for Bazzite (~/.config/punktfunk/host.env).
|
# punktfunk host config for Bazzite (~/.config/punktfunk/host.env).
|
||||||
#
|
#
|
||||||
# The compositor + input backend are AUTO-DETECTED per connect from the ACTIVE session: the host
|
# 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
|
# follows the box as you flip between Steam Gaming Mode (gamescope — a managed session at the
|
||||||
# session, no churn) and a KDE/GNOME Desktop (KWin/Mutter virtual output at the client's mode).
|
# 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.
|
# So nothing here forces a backend — only the trustworthy anchors stay.
|
||||||
|
|
||||||
XDG_RUNTIME_DIR=/run/user/1000
|
XDG_RUNTIME_DIR=/run/user/1000
|
||||||
@@ -26,3 +26,6 @@ PUNKTFUNK_ZEROCOPY=1
|
|||||||
# stays live on the panel, no Steam restart), set:
|
# stays live on the panel, no Steam restart), set:
|
||||||
# PUNKTFUNK_GAMESCOPE_ATTACH=1
|
# PUNKTFUNK_GAMESCOPE_ATTACH=1
|
||||||
# PUNKTFUNK_GAMESCOPE_APP=steam -gamepadui # only for an ad-hoc bare-spawn fallback
|
# 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
|
||||||
|
|||||||
Reference in New Issue
Block a user