From 336357643c86bd729c5f82f10922ceade1df9966 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Mon, 15 Jun 2026 06:49:53 +0000 Subject: [PATCH] feat(host): KWin virtual output primary + settle portal env on switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two parked follow-ups from the session-aware host work: #3 — KWin/Mutter virtual output not set primary. The auto-detected desktop path *is* "stream this desktop", but the per-session virtual output wasn't promoted to primary, so KDE/GNOME panels + windows stayed on an unstreamed real output and the streamed screen showed only wallpaper. apply_session_env now defaults PUNKTFUNK_KWIN_VIRTUAL_PRIMARY / PUNKTFUNK_MUTTER_VIRTUAL_PRIMARY on for the auto path (explicit config still wins), so the streamed output becomes the sole desktop. #2 — input flaky after a mid-stream Gaming->Desktop switch. The xdg portal (D-Bus-activated) and the systemd --user env still pointed at the old session, so the host's RemoteDesktop portal opened against a half-stale env: it accepted events but they didn't reach the compositor until a reconnect. New vdisplay::settle_desktop_portal() pushes the live session env into the systemd/D-Bus activation environment and (for KWin) restarts the portal so it re-reads it, mirroring a fresh desktop login (and the existing wlroots portal restart). Called from the mid-stream switch rebuild slot before the injector reopens. GNOME uses Mutter's direct EIS, so it only gets the env push. Compiles, clippy/fmt clean, 78 host tests pass. Live validation on the Bazzite box next. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/m3.rs | 9 ++++ crates/punktfunk-host/src/vdisplay.rs | 62 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/crates/punktfunk-host/src/m3.rs b/crates/punktfunk-host/src/m3.rs index 029a2c7..48021b8 100644 --- a/crates/punktfunk-host/src/m3.rs +++ b/crates/punktfunk-host/src/m3.rs @@ -2014,6 +2014,15 @@ fn virtual_stream( env: sw.env, }); crate::vdisplay::apply_input_env(sw.compositor); + // Switching INTO a desktop mid-stream: the xdg portal / systemd-user env may still + // point at the old session, so input would silently not land until a reconnect. + // Settle it (env push + KWin portal restart) before the injector reopens against it. + if matches!( + sw.compositor, + crate::vdisplay::Compositor::Kwin | crate::vdisplay::Compositor::Mutter + ) { + crate::vdisplay::settle_desktop_portal(sw.compositor); + } // Build the new backend's pipeline BEFORE dropping the old one (retry absorbs the // brief compositor-coexistence race during a switch); on failure keep the old. let rebuilt = diff --git a/crates/punktfunk-host/src/vdisplay.rs b/crates/punktfunk-host/src/vdisplay.rs index 9a1fb32..e53a06c 100644 --- a/crates/punktfunk-host/src/vdisplay.rs +++ b/crates/punktfunk-host/src/vdisplay.rs @@ -367,10 +367,72 @@ pub fn apply_session_env(active: &ActiveSession) { if active.kind == ActiveKind::DesktopGnome { std::env::set_var("PUNKTFUNK_FORCE_SHM", "1"); } + // Stream the desktop as the SOLE output: promote the per-session virtual output to PRIMARY so + // the panels + windows land on the streamed surface, not an unstreamed real output (the + // auto-detected desktop path *is* "stream this desktop"). Default-on for the auto path; an + // explicit `PUNKTFUNK_{KWIN,MUTTER}_VIRTUAL_PRIMARY` still wins. + match active.kind { + ActiveKind::DesktopKde if std::env::var_os("PUNKTFUNK_KWIN_VIRTUAL_PRIMARY").is_none() => { + std::env::set_var("PUNKTFUNK_KWIN_VIRTUAL_PRIMARY", "1"); + } + ActiveKind::DesktopGnome + if std::env::var_os("PUNKTFUNK_MUTTER_VIRTUAL_PRIMARY").is_none() => + { + std::env::set_var("PUNKTFUNK_MUTTER_VIRTUAL_PRIMARY", "1"); + } + _ => {} + } } #[cfg(not(target_os = "linux"))] pub fn apply_session_env(_active: &ActiveSession) {} +/// On a **mid-stream** switch to a desktop, the xdg-desktop-portal (D-Bus-activated) and the systemd +/// `--user` environment can still point at the OLD session, so the host's RemoteDesktop portal opens +/// against a half-stale env — it accepts events but they don't reach the compositor until a +/// reconnect. Push the live session env into the systemd/D-Bus activation environment and (for KWin, +/// whose input rides the xdg RemoteDesktop portal) restart the portal so it re-reads it — the same +/// settling a fresh desktop login does. Best-effort; mirrors the wlroots portal restart. GNOME uses +/// Mutter's *direct* EIS (no xdg portal), so it only needs the env push. +#[cfg(target_os = "linux")] +pub fn settle_desktop_portal(chosen: Compositor) { + const VARS: &[&str] = &[ + "WAYLAND_DISPLAY", + "XDG_CURRENT_DESKTOP", + "DBUS_SESSION_BUS_ADDRESS", + "XDG_RUNTIME_DIR", + ]; + // Push our (correct) env into the systemd --user manager + the D-Bus activation environment so a + // re-activated portal/backend inherits the live session. + let _ = std::process::Command::new("systemctl") + .args(["--user", "import-environment"]) + .args(VARS) + .status(); + let _ = std::process::Command::new("dbus-update-activation-environment") + .arg("--systemd") + .args(VARS) + .status(); + // KWin input goes through the xdg RemoteDesktop portal; the frontend routes RemoteDesktop to a + // backend by its OWN startup XDG_CURRENT_DESKTOP, so restart it (+ the KDE backend) to re-read + // the now-live session, then let it settle before the injector reopens against it. + if chosen == Compositor::Kwin { + let _ = std::process::Command::new("systemctl") + .args([ + "--user", + "try-restart", + "xdg-desktop-portal-kde.service", + "xdg-desktop-portal.service", + ]) + .status(); + std::thread::sleep(std::time::Duration::from_millis(600)); + } + tracing::info!( + compositor = chosen.id(), + "settled desktop portal env for the switched-to session" + ); +} +#[cfg(not(target_os = "linux"))] +pub fn settle_desktop_portal(_chosen: Compositor) {} + /// Route input to match the chosen video backend (they must not diverge), via the highest-priority /// `PUNKTFUNK_INPUT_BACKEND` knob the injector honors. For gamescope, the **default is a managed /// session at the client's mode** (tears the TV's autologin down on connect; restored on a debounced