From f0e71e928afb2bb1dd22360d5a35024d845434ef Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 26 Jul 2026 18:19:27 +0200 Subject: [PATCH] fix(gamestream): the resolved launch command is what the backend nests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Windows/macOS build compiled the resolved-launch parameter out entirely and warned about it, because the two uses left were both Linux-gated. Both platforms want the same value: off Linux `set_launch_command` is a backend no-op and a library title resolves to no command at all (the interactive-session spawner launches it by id), so the cfg split was carrying an older distinction that no longer exists. Also covers the Windows half of `process_name` with a test — the pure name match plus a live scan for this test process, since `find`'s early return would otherwise skip the signal entirely and the test would pass vacuously. Gates: .133 check + clippy --all-targets clean with no warnings, 5/5 procscan (incl. live); .21 299 tests + fmt. --- .../punktfunk-host/src/gamestream/stream.rs | 12 +++---- crates/punktfunk-host/src/procscan/windows.rs | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/crates/punktfunk-host/src/gamestream/stream.rs b/crates/punktfunk-host/src/gamestream/stream.rs index a4fa1512..1750eab1 100644 --- a/crates/punktfunk-host/src/gamestream/stream.rs +++ b/crates/punktfunk-host/src/gamestream/stream.rs @@ -595,14 +595,12 @@ fn open_gs_virtual_source( }; let mut vd = crate::vdisplay::open(compositor).context("open virtual display")?; // Carry the resolved launch command on the backend instance (per-session) rather than a - // process-global env var, so concurrent sessions can't stomp each other's launch target. On - // Linux resolve a library-id selection to its command too, so gamescope's bare spawn nests a - // library title exactly like an apps.json command (it previously nested only `cmd`, silently - // dropping library picks). - #[cfg(target_os = "linux")] + // process-global env var, so concurrent sessions can't stomp each other's launch target. It is + // the RESOLVED command, so gamescope's bare spawn nests a library title exactly like an + // apps.json command (it previously nested only `cmd`, silently dropping library picks). Off + // Linux this is a no-op backend-side, and a library title resolves to no command at all — the + // interactive-session spawner launches it by id instead. vd.set_launch_command(launch.and_then(|t| t.command.clone())); - #[cfg(not(target_os = "linux"))] - vd.set_launch_command(app.and_then(|a| a.cmd.clone())); // Serialize with the punktfunk/1 plane's IDD-push setup dance (Goal-1 §2.5). A GameStream // connect used to skip it entirely, so it could ADD/reconfigure the shared monitor while a // native session was mid-build (and vice versa), and its sealed-channel delivery would replace diff --git a/crates/punktfunk-host/src/procscan/windows.rs b/crates/punktfunk-host/src/procscan/windows.rs index 3b3fddac..532e73fc 100644 --- a/crates/punktfunk-host/src/procscan/windows.rs +++ b/crates/punktfunk-host/src/procscan/windows.rs @@ -292,6 +292,42 @@ mod tests { )); } + /// The operator-supplied fallback ([`DetectSpec::process_name`]): the image's own file name and + /// nothing else — never a path that merely contains the name. + #[test] + fn process_name_matches_the_image_name_only() { + assert!(same_name( + Path::new(r"C:\Games\Hades\Hades.exe"), + "hades.exe" + )); + assert!(same_name( + Path::new(r"C:\Games\Hades\Hades.exe"), + " Hades.exe " + )); + // A longer name that starts the same way is a different program. + assert!(!same_name( + Path::new(r"C:\Games\Hades\HadesLauncher.exe"), + "Hades.exe" + )); + // A directory called the same thing doesn't qualify its contents. + assert!(!same_name( + Path::new(r"C:\Games\Hades.exe\other.exe"), + "Hades.exe" + )); + + // …and it reaches the live scan, which the `find` early-return would otherwise skip. + let me = std::env::current_exe().expect("current exe"); + let name = me.file_name().and_then(|n| n.to_str()).expect("exe name"); + let spec = DetectSpec { + process_name: Some(name.to_ascii_uppercase()), + ..Default::default() + }; + assert!(Scanner::system() + .find(&spec, None) + .iter() + .any(|p| p.pid == std::process::id())); + } + #[test] fn a_spec_with_no_path_signal_matches_nothing() { // No reaper and no environment reading on Windows: an appid-only or env-only spec has nothing