e847cbde73
$XDG_RUNTIME_DIR/hypr keeps folders of dead instances, and the newest one isn't always alive; take the newest with a .socket.sock
145 lines
6.7 KiB
Bash
Executable File
145 lines
6.7 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
|
|
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
|