fix(gamestream): the resolved launch command is what the backend nests
plugin-kit-publish / publish (push) Failing after 34s
ci / web (push) Successful in 58s
ci / docs-site (push) Successful in 1m2s
apple / swift (push) Successful in 5m39s
ci / bench (push) Successful in 6m18s
deb / build-publish (push) Successful in 8m56s
decky / build-publish (push) Successful in 34s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 42s
android / android (push) Successful in 12m2s
deb / build-publish-client-arm64 (push) Successful in 7m35s
ci / rust-arm64 (push) Successful in 17m58s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m26s
windows-host / package (push) Successful in 19m41s
deb / build-publish-host (push) Successful in 18m45s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8m49s
arch / build-publish (push) Successful in 22m17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9m4s
ci / rust (push) Failing after 23m6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12m7s
docker / deploy-docs (push) Successful in 28s
docker / build-push-arm64cross (push) Successful in 4m20s
apple / screenshots (push) Successful in 24m30s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m41s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 21m18s

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.
This commit is contained in:
2026-07-26 18:28:13 +02:00
parent ee722c2160
commit f0e71e928a
2 changed files with 41 additions and 7 deletions
@@ -595,14 +595,12 @@ fn open_gs_virtual_source(
}; };
let mut vd = crate::vdisplay::open(compositor).context("open virtual display")?; 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 // 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 // process-global env var, so concurrent sessions can't stomp each other's launch target. It is
// Linux resolve a library-id selection to its command too, so gamescope's bare spawn nests a // the RESOLVED command, so gamescope's bare spawn nests a library title exactly like an
// library title exactly like an apps.json command (it previously nested only `cmd`, silently // apps.json command (it previously nested only `cmd`, silently dropping library picks). Off
// dropping library picks). // Linux this is a no-op backend-side, and a library title resolves to no command at all — the
#[cfg(target_os = "linux")] // interactive-session spawner launches it by id instead.
vd.set_launch_command(launch.and_then(|t| t.command.clone())); 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 // 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 // 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 // native session was mid-build (and vice versa), and its sealed-channel delivery would replace
@@ -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] #[test]
fn a_spec_with_no_path_signal_matches_nothing() { 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 // No reaper and no environment reading on Windows: an appid-only or env-only spec has nothing