ddaef1f805
the user may run amane from a terminal; start used to crash into it with 'amane is already running'. now it says which pid runs and stops
148 lines
7.0 KiB
Bash
Executable File
148 lines
7.0 KiB
Bash
Executable File
#!/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 over 20 ms, 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 <window> <w>x<h> gap <ms> view <ms> draw <ms> gpu <ms>
|
|
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;i<n;i++) if (gap[i]>20) slow++;
|
|
printf \"%-5s frames %2d over 20 ms %2d gpu wait %5.2f ms gaps:\", act, n, slow, gpu/n;
|
|
for (i=1;i<n && i<26;i++) printf \" %.0f\", gap[i]; print \"\"}'
|
|
done; done"
|
|
;;
|
|
ipc)
|
|
remote "amane ipc call $*"
|
|
;;
|
|
shot)
|
|
namespace=${1:?namespace}
|
|
file=${2:?local file}
|
|
pad=${3:-0}
|
|
# hyprctl layers -j: {monitor: {levels: {n: [{namespace, x, y, w, h}]}}}, x and y in global layout pixels
|
|
remote "geometry=\$(hyprctl layers -j | python3 -c '
|
|
import json, sys
|
|
for monitor in json.load(sys.stdin).values():
|
|
for level in monitor[\"levels\"].values():
|
|
for layer in level:
|
|
if layer[\"namespace\"] == \"$namespace\":
|
|
p = $pad
|
|
print(f\"{layer[\"x\"] - p},{max(0, layer[\"y\"] - p)} {layer[\"w\"] + 2 * p}x{layer[\"h\"] + 2 * p}\")
|
|
sys.exit()
|
|
sys.exit(\"no layer named $namespace\")
|
|
') && grim -g \"\$geometry\" shot.png && echo \"\$geometry\" >&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
|