Launch splash under headless QEMU
Observed 2026-09-26. This test ran the cross-built Sway from source
1ff6a837 (/nix/store/6xrc0x9kb1gpy6v8g2v3512a0373vwcm-sway-unwrapped-riscv64-unknown-linux-gnu-1.12,
via nix-store -qR of .#card-shell), the RISC-V Rust client with this
change’s splash source
(/nix/store/71v0ssqfkp89yaw8ssxs2d08ysg97rbv-k230-shell-rust-riscv64-unknown-linux-gnu-0.1.0),
and the existing card-composition-probe-client fixture
(/nix/store/dw3p6aq52mz55mqrm97hlc0056sj9x1g-card-composition-probe-client-riscv64-unknown-linux-gnu-0.1)
under qemu-riscv64-static, with a 568x1232 headless Pixman output. Native
compositor test-touch supplied synthetic Wayland touch events. The desktop
catalog held one private fixture entry (“Splash Fixture”) whose Exec is a
shell wrapper: sleep 1 then exec qemu-riscv64-static card-composition-probe-client --app-id k230.card.two. exec, not a fork,
keeps the pid GIO’s AppLaunchContext reports identical to the pid that
ends up owning the mapped Wayland window, so this exercises the exact-pid
match path this feature’s window-matching depends on.
Reproduce with the committed runner and exact executables:
python3 tests/test_launch_splash_qemu.py \
--sway /nix/store/6xrc0x9kb1gpy6v8g2v3512a0373vwcm-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway \
--swaymsg /nix/store/6xrc0x9kb1gpy6v8g2v3512a0373vwcm-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/swaymsg \
--rust /nix/store/71v0ssqfkp89yaw8ssxs2d08ysg97rbv-k230-shell-rust-riscv64-unknown-linux-gnu-0.1.0/bin/k230-shell-rust \
--client /nix/store/dw3p6aq52mz55mqrm97hlc0056sj9x1g-card-composition-probe-client-riscv64-unknown-linux-gnu-0.1/bin/card-composition-probe-client \
--output /tmp/k230-launch-splash-qemu
PASS (reproduced twice). deck.png shows the initial card
overview with fixture card k230.card.one; drawer-open.png
shows the drawer opened over it with the single “Splash Fixture” entry (an
initial-letter “S” fallback plate — no icon theme is staged in this
private harness, so icon.rs’s resolution correctly no-ops rather than
drawing nothing incorrectly). splash.png, captured while the
fixture’s own sleep 1 guarantees its window has not mapped yet, shows the
full-screen splash — the theme background colour and the app’s name —
having already replaced the drawer. The Rust log recorded touch-down,
app-launch-requested, and critically zero unmap events at this
point: the overlay was never torn down, so the drawer/deck could not have
flashed through even for one frame. handoff.png, captured
after the fixture’s window actually mapped, shows the probe client’s own
striped card content with the splash gone; the Rust log then showed exactly
one unmap (the hand-off itself), app-launch-mapped, and the sway IPC
tree’s focused app_id was k230.card.two — the launched window, not the
splash or the original deck. result.json retains the
machine-readable outcome and executable paths.
This is host/QEMU wiring and timing evidence under a headless Pixman
backend with synthetic touch and a private single-fixture catalog. It does
not establish real-glass splash readability, real-finger touch-dismiss
feel on the TimedOut/Failed states, a real Terminal=true/foot
app_id-mismatch hand-off, or that the previously active app is
imperceptible on the physical panel — those remain the open board tasks
in openspec/changes/launching-an-app-shows-a-splash/tasks.md. The bounded
/proc-ancestry matching this exact-pid case does not exercise is covered
instead by splash::pid_or_ancestor’s own host unit tests in
nix/rust-shell-client/src/splash.rs.