Organize Iris support files
This commit is contained in:
1 parent
40e7259d99
commit
ea2112c552
11 files changed
+94
-56
No files matched your search
Executable
+198
@@ -0,0 +1,198 @@
|
||||
#!/bin/sh
|
||||
# Run an iris example on this machine, which has no display.
|
||||
#
|
||||
# ./scripts/run-headless.sh tabs [-- cargo args]
|
||||
# ./scripts/run-headless.sh tabs --shot /tmp/tabs.png --seconds 4
|
||||
# ./scripts/run-headless.sh phone --phone --dir ../app --shot /tmp/p.png
|
||||
# ./scripts/run-headless.sh phone --phone --dir ../app \
|
||||
# --replay ../app/touch/flick-120hz.touch --shot /tmp/p.png
|
||||
#
|
||||
# `--dir DIR` names the workspace to build in, defaulting to `iris/`. The
|
||||
# app's examples -- the phone-sized transcript
|
||||
# screen and everything else that is about *this product* -- live in
|
||||
# `app/`, which is a workspace of its own; `replay-touch` is still
|
||||
# built from iris, since it is part of the rig rather than of either app.
|
||||
#
|
||||
# `--phone` is layer 2 of docs/RUST.md's "Three test layers": the output
|
||||
# and the window take Iris's phone's own size and density (1080x2424 at
|
||||
# `content_scale` 2.55, from docs/bench/iris-phone-v2-2026-09-06.md,
|
||||
# carried in `ai_app::ui::fixture::PHONE_*`), and `IRIS_SCALE` hands that
|
||||
# density to iris the way `DisplayMetrics.density` does on Android
|
||||
# (`iris::desktop::content_scale`). So a screenshot from here and one
|
||||
# from the phone are the same layout at the same density, and what
|
||||
# differs is only the renderer. Without it the output stays desktop-
|
||||
# shaped, which is what every other example wants.
|
||||
#
|
||||
# `--replay FILE` drives one of the `.touch` recordings the headless
|
||||
# tests use (`app/touch/`) into the window through
|
||||
# `rig-input`'s `replay-touch` -- one recording, both layers. With
|
||||
# `--shot` it also writes `<shot>-before.png` from just before the
|
||||
# gesture, since "the list moved" is a claim about two pictures.
|
||||
#
|
||||
# `--bin` runs a real crate binary instead of an example (E4's
|
||||
# `ai-app-desktop`, which is a window a person runs, not a demo) --
|
||||
# `cargo build --bin NAME` instead of `--example NAME`, and
|
||||
# `target/debug/NAME` instead of `target/debug/examples/NAME`. Its own
|
||||
# argv (the CLI flags a real binary takes, as opposed to `cargo build`'s
|
||||
# own flags after `--`) comes through `$RUN_HEADLESS_ARGS`, word-split on
|
||||
# purpose -- an example never needed one, so there was nowhere to plumb it
|
||||
# through positionally without disturbing the existing `-- cargo args`
|
||||
# convention above.
|
||||
#
|
||||
# The VM has a real GPU and no display (the `this-machine-graphics` skill
|
||||
# says what it is and how it fails), so what is missing here is only a
|
||||
# compositor to give winit a surface. So: a headless sway, the same trick
|
||||
# `emu` uses for the Android emulator, and `grim` to see the result.
|
||||
#
|
||||
# It is deliberately *not* `emu`'s compositor. sway tiles, so adding a window
|
||||
# to the one an emulator is sitting in resizes that emulator's window, and a
|
||||
# peer session's `emu up` could join at any moment. This one has its own
|
||||
# socket and its own runtime directory and goes away with the machine.
|
||||
set -eu
|
||||
|
||||
scripts=$(cd "$(dirname "$0")" && pwd)
|
||||
root=$(cd "$scripts/.." && pwd)
|
||||
workdir="$root"
|
||||
cd "$root"
|
||||
run="${XDG_RUNTIME_DIR:-/tmp}/iris-headless"
|
||||
seconds=3
|
||||
shot=""
|
||||
replay=""
|
||||
example=""
|
||||
kind=example
|
||||
phone=no
|
||||
|
||||
# The phone Iris runs the bench on. Not typed from memory: these are
|
||||
# `ai_app::ui::fixture::PHONE_WIDTH`/`PHONE_HEIGHT`/`PHONE_SCALE`, which
|
||||
# in turn come from her own reports -- keep the three in step.
|
||||
PHONE_MODE=1080x2424@120Hz
|
||||
PHONE_SCALE=2.55
|
||||
DESKTOP_MODE=1920x1200@60Hz
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--shot) shot=$2; shift 2 ;;
|
||||
--seconds) seconds=$2; shift 2 ;;
|
||||
--bin) kind=bin; shift ;;
|
||||
--phone) phone=yes; shift ;;
|
||||
--replay) replay=$2; shift 2 ;;
|
||||
--dir) workdir=$(cd "$2" && pwd); shift 2 ;;
|
||||
--) shift; break ;;
|
||||
*) example=$1; shift ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$example" ] || { echo "usage: $0 NAME [--bin] [--phone] [--dir DIR] [--replay TOUCH] [--shot PNG] [--seconds N] [-- cargo args]" >&2; exit 2; }
|
||||
[ -z "$replay" ] || [ -f "$replay" ] || { echo "run-headless: no touch script at $replay" >&2; exit 2; }
|
||||
|
||||
mkdir -p "$run"
|
||||
export SWAYSOCK="$run/sway.sock"
|
||||
|
||||
# Named rather than left to sway's pid-based default, so a second run reuses
|
||||
# this compositor instead of starting another beside it.
|
||||
if ! swaymsg -t get_version >/dev/null 2>&1; then
|
||||
rm -f "$SWAYSOCK"
|
||||
WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 LIBSEAT_BACKEND=noop \
|
||||
setsid sway -c "$scripts/headless.conf" >"$run/sway.log" 2>&1 &
|
||||
i=0
|
||||
while [ $i -lt 20 ]; do
|
||||
swaymsg -t get_version >/dev/null 2>&1 && break
|
||||
i=$((i + 1)); sleep 0.5
|
||||
done
|
||||
swaymsg -t get_version >/dev/null 2>&1 || {
|
||||
echo "run-headless: compositor did not start; see $run/sway.log" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
rm -f "$run/display"
|
||||
swaymsg exec -- "sh -c 'printf %s \"\$WAYLAND_DISPLAY\" > $run/display'" >/dev/null
|
||||
i=0
|
||||
while [ $i -lt 20 ]; do
|
||||
[ -s "$run/display" ] && break
|
||||
i=$((i + 1)); sleep 0.5
|
||||
done
|
||||
[ -s "$run/display" ] || { echo "run-headless: could not read WAYLAND_DISPLAY" >&2; exit 1; }
|
||||
WAYLAND_DISPLAY=$(cat "$run/display")
|
||||
export WAYLAND_DISPLAY
|
||||
|
||||
echo "run-headless: $WAYLAND_DISPLAY (sway $(swaymsg -t get_version --raw | sed -n 's/.*"human_readable":"\([^"]*\)".*/\1/p'))" >&2
|
||||
|
||||
# Set every run rather than only when it changes: this compositor is
|
||||
# reused across runs (see the socket comment above), so a desktop-shaped
|
||||
# run after a phone-shaped one would otherwise inherit the phone's output
|
||||
# and silently screenshot the wrong size.
|
||||
if [ "$phone" = yes ]; then
|
||||
mode=$PHONE_MODE
|
||||
export IRIS_SCALE="$PHONE_SCALE"
|
||||
echo "run-headless: phone-shaped output $PHONE_MODE at IRIS_SCALE=$PHONE_SCALE" >&2
|
||||
else
|
||||
mode=$DESKTOP_MODE
|
||||
fi
|
||||
swaymsg output HEADLESS-1 mode "$mode" >/dev/null
|
||||
# The extent `replay-touch` positions against, so a script's coordinates
|
||||
# are the output's own pixels.
|
||||
out_w=${mode%x*}
|
||||
out_h=${mode#*x}; out_h=${out_h%@*}
|
||||
|
||||
# Built before the app starts, so a compile error is not reported as a
|
||||
# window that failed to move.
|
||||
[ -z "$replay" ] || (cd "$root" && cargo build --bin replay-touch -p rig-input) >&2
|
||||
|
||||
cd "$workdir"
|
||||
if [ "$kind" = bin ]; then
|
||||
cargo build --bin "$example" "$@" >&2
|
||||
bin="$workdir/target/debug/$example"
|
||||
else
|
||||
cargo build --example "$example" "$@" >&2
|
||||
bin="$workdir/target/debug/examples/$example"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086 -- deliberately word-split: this is the
|
||||
# binary's own argv, not a single path.
|
||||
"$bin" ${RUN_HEADLESS_ARGS:-} >"$run/$example.log" 2>&1 &
|
||||
pid=$!
|
||||
trap 'kill "$pid" 2>/dev/null || true' EXIT INT TERM
|
||||
|
||||
# Wait for the window to be mapped rather than for a number of seconds. A
|
||||
# fixed sleep took an all-black screenshot the first time this ran, when sway
|
||||
# had started in the same invocation and had not composited its output yet --
|
||||
# which is indistinguishable from an app that draws nothing.
|
||||
i=0
|
||||
while [ $i -lt 40 ]; do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
swaymsg -t get_tree --raw 2>/dev/null | grep -q "\"pid\":$pid," && break
|
||||
i=$((i + 1)); sleep 0.25
|
||||
done
|
||||
|
||||
i=0
|
||||
while [ $i -lt "$((seconds * 2))" ]; do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
i=$((i + 1)); sleep 0.5
|
||||
done
|
||||
|
||||
if [ -n "$replay" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
if [ -n "$shot" ]; then
|
||||
grim "${shot%.png}-before.png"
|
||||
echo "run-headless: wrote ${shot%.png}-before.png (before the gesture)" >&2
|
||||
fi
|
||||
"$root/target/debug/replay-touch" "$out_w" "$out_h" "$replay"
|
||||
# A fling outlives the finger: the gesture's own last sample is not
|
||||
# when the list stops. Long enough for Android's spline to settle
|
||||
# (`FlingCalculator::duration` tops out around a second and a half).
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
[ -n "$shot" ] && grim "$shot" && echo "run-headless: wrote $shot" >&2
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
status=0
|
||||
else
|
||||
wait "$pid" 2>/dev/null || status=$?
|
||||
echo "run-headless: $example exited early (status ${status:-0})" >&2
|
||||
status=${status:-1}
|
||||
fi
|
||||
|
||||
echo "--- $example output ---" >&2
|
||||
cat "$run/$example.log" >&2
|
||||
exit "$status"
|
||||
Reference in new issue
Block a user