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.
114 lines
3.4 KiB
Bash
Executable File
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
|