Files
playpen/shot.sh
T
Greyson Parrelli 7af5cf72e6 Use the user's login shell, and fix login-shell argv
Two separate problems made the terminal run the wrong shell:

Pty.create took a single argv slice and used argv[0] as both the exec path
and the argument vector's first entry. Session passed {shell, "-shell"},
so the shell received "-zsh" as an ordinary argument rather than as its
argv[0]. Bash quietly tolerated it; zsh rejects it and exits immediately,
which closed the tab and took the window down with it. The exec path and
argv are now separate parameters.

Shell resolution preferred $SHELL, which describes whichever shell launched
us rather than the one the user configured. Inside 'nix develop' that is
Nix's own minimal bash, so the terminal never opened the user's zsh. We now
read the login shell from the passwd database and keep $SHELL only as a
fallback for systems without a usable passwd entry.

shot.sh no longer forces a shell, so screenshots reflect real behavior.
2026-08-11 09:11:53 -04:00

114 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Run vtabs inside a throwaway headless Sway and screenshot it.
#
# This keeps UI checks out of the developer's real Wayland session: nothing
# pops up on screen, and it works even while the session is locked.
#
# Usage: ./shot.sh OUT.png ["keys to type"] [settle-seconds]
set -u
cd "$(dirname "$0")"
OUT="${1:-/tmp/vtabs-shot.png}"
KEYS="${2:-}"
SETTLE="${3:-2}"
RUNDIR="$(mktemp -d)"
trap 'rm -rf "$RUNDIR"' EXIT
cat > "$RUNDIR/sway.conf" <<CONF
output HEADLESS-1 resolution 1200x800
default_border none
exec "'$RUNDIR/launch.sh' >'$RUNDIR/app.log' 2>&1"
CONF
# The nix dev shell exports bash functions into the environment. Any /bin/sh
# the terminal's child spawns tries to import them and spews parse errors that
# have nothing to do with vtabs, so drop them just before exec.
cat > "$RUNDIR/launch.sh" <<'LAUNCH'
#!/usr/bin/env bash
while read -r fn; do unset -f "$fn" 2>/dev/null; done < <(declare -Fx | awk '{print $3}')
exec "$VTABS_BIN"
LAUNCH
chmod +x "$RUNDIR/launch.sh"
export VTABS_BIN="$PWD/zig-out/bin/vtabs"
export WLR_BACKENDS=headless
export WLR_LIBINPUT_NO_DEVICES=1
# No GPU is available to a headless compositor here, so force software
# rendering on both the compositor and GTK sides.
export WLR_RENDERER=pixman
export LIBGL_ALWAYS_SOFTWARE=1
export GSK_RENDERER=cairo
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
export SWAYSOCK="$RUNDIR/sway.sock"
# Don't let the app fall back to the real session.
unset DISPLAY
unset WAYLAND_DISPLAY
for sock in "$XDG_RUNTIME_DIR"/wayland-*; do
[ -S "$sock" ] && basename "$sock"
done > "$RUNDIR/pre-sockets" 2>/dev/null || true
sway -c "$RUNDIR/sway.conf" >"$RUNDIR/sway.log" 2>&1 &
SWAY_PID=$!
# Wait for the compositor socket before talking to it.
for _ in $(seq 1 50); do
[ -S "$SWAYSOCK" ] && break
sleep 0.2
done
# Sway picks its own socket name, so discover it by diffing the sockets in
# XDG_RUNTIME_DIR against the ones that existed before we started.
DISPLAY_NAME=""
for _ in $(seq 1 50); do
for sock in "$XDG_RUNTIME_DIR"/wayland-*; do
case "$sock" in *.lock) continue;; esac
[ -S "$sock" ] || continue
name="$(basename "$sock")"
grep -qx "$name" "$RUNDIR/pre-sockets" && continue
DISPLAY_NAME="$name"
break
done
[ -n "$DISPLAY_NAME" ] && break
sleep 0.2
done
if [ -z "$DISPLAY_NAME" ]; then
echo "could not determine wayland display" >&2
cat "$RUNDIR/sway.log" >&2
exit 1
fi
export WAYLAND_DISPLAY="$DISPLAY_NAME"
sleep "$SETTLE"
if [ -n "$KEYS" ]; then
# wtype drops the first keystroke of every invocation while the compositor
# takes up its freshly uploaded keymap, so burn one on a harmless key.
wtype -k Shift_L 2>/dev/null || true
sleep 0.3
wtype "$KEYS" 2>/dev/null || true
sleep 1.5
fi
if [ -n "${VTABS_SHOT_DEBUG:-}" ]; then
echo "--- swaymsg get_tree (apps) ---" >&2
swaymsg -t get_tree 2>&1 | grep -E '"(app_id|name|pid)"' | head -30 >&2
echo "--- sway log ---" >&2
cat "$RUNDIR/sway.log" >&2
echo "--- app log ---" >&2
cat "$RUNDIR/app.log" >&2 2>/dev/null || echo "(no app log)" >&2
fi
if ! grim -o HEADLESS-1 "$OUT"; then
echo "grim -o failed, trying full capture" >&2
grim "$OUT" || echo "grim failed entirely" >&2
fi
swaymsg exit >/dev/null 2>&1 || kill "$SWAY_PID" 2>/dev/null
wait "$SWAY_PID" 2>/dev/null
echo "wrote $OUT"
# Surface app-side errors, which sway captures on its stdout.
grep -iE "error|warn|panic|segfault" "$RUNDIR/sway.log" | grep -v "portal" | head -20 || true