#!/usr/bin/env bash # Visual test loop for an amane shell on a remote Wayland desktop, driven over ssh. # # usage: amane-remote.sh HOST COMMAND [ARGS...] # deploy SRC_DIR back up the remote ~/.config/amane/src once, then replace it with SRC_DIR # build `amane compile` on the remote (backs up a saved amane-shell once) # start | stop run / stop the compiled shell, with AMANE_FRAMES=1 logging # log [N] last N lines of the shell's log (default 30) # fps [SECONDS] frames drawn over SECONDS (default 3), with average gap and gpu wait # anim HANDLER 3x `amane ipc call HANDLER open|close`: frames, gaps of 20-500 ms (stutter; longer # gaps are other windows resting), every gap # ipc ARGS... amane ipc call ARGS... # shot NAMESPACE FILE [PAD] screenshot the layer window with that namespace (Hyprland) into local FILE # players start N | stop run N fake MPRIS players (fake_mpris.py, next to this script) # sinks start | stop two inaudible null sinks (island_test_a/_b); fake players play into _a # sh 'COMMAND' any command inside the remote session environment set -euo pipefail host=${1:?host} command=${2:?command} shift 2 here=$(dirname "$(readlink -f "$0")") work='$HOME/.cache/amane-remote-test' # what a terminal inside the graphical session would have session='uid=$(id -u) export XDG_RUNTIME_DIR=/run/user/$uid export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus export WAYLAND_DISPLAY=$(ls "$XDG_RUNTIME_DIR" | grep -m1 -x "wayland-[0-9]*") export HYPRLAND_INSTANCE_SIGNATURE=$(cd "$XDG_RUNTIME_DIR/hypr" 2>/dev/null && for d in $(ls -t); do [ -S "$d/.socket.sock" ] && echo "$d" && break; done) export PATH=$HOME/.cargo/bin:$PATH mkdir -p '"$work"' && cd '"$work"' ' remote() { ssh -o BatchMode=yes "$host" "$session$1" } case $command in deploy) source_dir=${1:?source folder} remote 'test -d ~/.config/amane/src.before-test || cp -a ~/.config/amane/src ~/.config/amane/src.before-test 2>/dev/null || true mkdir -p ~/.config/amane/src && find ~/.config/amane/src -maxdepth 1 -name "*.rs" -delete' # -m: the two clocks may disagree, and future timestamps only cause warnings tar -C "$source_dir" -cf - . | ssh -o BatchMode=yes "$host" 'tar -C ~/.config/amane/src -xmf -' scp -q "$here/fake_mpris.py" "$host:.cache/amane-remote-test/" remote 'ls ~/.config/amane/src' ;; build) remote 'test -f ~/.cache/amane/amane-shell.before-test || cp -a ~/.cache/amane/amane-shell ~/.cache/amane/amane-shell.before-test 2>/dev/null || true amane compile 2>&1 | tail -40' ;; start) # newer CLIs save ~/.cache/amane/amane-shell; older ones only leave cargo's output # the old shell can take a few seconds to let go of amane's socket; wait for it, or the new one panics remote 'test -f shell.pid && kill $(cat shell.pid) 2>/dev/null for i in $(seq 50); do pgrep -F shell.pid > /dev/null 2>&1 || break; sleep 0.1; done # a shell this script did not start belongs to the user: leave it, and say so other=$(pgrep -x amane-shell) if [ -n "$other" ]; then echo "not started: another amane-shell runs (pid $other), not started by this script"; exit 1; fi binary=~/.cache/amane/amane-shell test -x "$binary" || binary=~/.cache/amane/project/target/debug/amane-shell AMANE_FRAMES=1 setsid -f sh -c "echo \$\$ > shell.pid; exec $binary" > shell.log 2>&1 sleep 2; pgrep -F shell.pid > /dev/null && echo "running, pid $(cat shell.pid)" || { echo "exited:"; tail -20 shell.log; }' ;; stop) remote 'test -f shell.pid && kill $(cat shell.pid) 2>/dev/null; rm -f shell.pid; echo stopped' ;; log) remote "tail -n ${1:-30} shell.log" ;; fps) # AMANE_FRAMES lines: frame x gap view draw gpu remote "a=\$(wc -l < shell.log); sleep ${1:-3}; tail -n +\$((a + 1)) shell.log | grep '^frame' | awk '{gap+=\$5; view+=\$7; gpu+=\$11; n++} END {if (n) printf \"%d frames in ${1:-3} s, gap %.1f ms, view %.2f ms, gpu wait %.2f ms\n\", n, gap/n, view/n, gpu/n; else print \"no frames\"}'" ;; anim) # a smooth animation shows only ~17 ms gaps; 33 ms runs mean it fell to 30 fps handler=${1:?ipc handler with open and close} remote "for round in 1 2 3; do for action in open close; do a=\$(wc -l < shell.log); amane ipc call $handler \$action > /dev/null; sleep 1 tail -n +\$((a + 1)) shell.log | grep '^frame' | awk -v act=\$action ' {gap[n++]=\$5; gpu+=\$11} END {slow=0; for (i=1;i20 && gap[i]<500) slow++; printf \"%-5s frames %2d over 20 ms %2d gpu wait %5.2f ms gaps:\", act, n, slow, gpu/n; for (i=1;i&2" && ssh -o BatchMode=yes "$host" 'cat ~/.cache/amane-remote-test/shot.png' > "$file" echo "saved $file" ;; players) case ${1:?start N or stop} in start) count=${2:-3} remote "sink=\$(pactl list sinks short | grep -q island_test_a && echo island_test_a) for index in \$(seq 0 $((count - 1))); do FAKE_MPRIS_SINK=\$sink setsid -f python3 fake_mpris.py \$index >> players.log 2>&1 done sleep 1.5; playerctl -l" ;; stop) # [p]: the remote shell's own command line holds this text, and must not match it remote 'pkill -TERM -f "[p]ython3 fake_mpris.py" || true; sleep 0.5; playerctl -l 2>&1' ;; esac ;; sinks) case ${1:?start or stop} in start) remote 'for name in island_test_a island_test_b; do pactl list sinks short | grep -q "$name" || pactl load-module module-null-sink sink_name=$name sink_properties=device.description=$name > /dev/null done pactl list sinks short | grep island_test' ;; stop) remote 'pactl list modules short | awk "/module-null-sink/ && /island_test/ {print \$1}" | xargs -r -n1 pactl unload-module; pactl list sinks short | grep -c island_test' ;; esac ;; sh) remote "${1:?command}" ;; *) sed -n '2,15p' "$0" exit 1 ;; esac