feat: measure footage across segments and test X11 capture

Matroska output can stay buffered until a segment closes, so progress counts
bytes across all segments and the stall limit scales with segment length.
Unknown capture source types now fail instead of recording an empty
placeholder. The rig gains an Xwayland mode for Xcomposite window capture.
This commit is contained in:
2026-10-08 14:25:23 +02:00
parent 263e7a964e
commit 444758f9ec
4 changed files with 142 additions and 75 deletions
@@ -1,23 +1,38 @@
#!/usr/bin/env bash
# Run a command inside a disposable headless Wayland desktop for capture tests.
# Run a command inside a disposable headless desktop for capture tests.
#
# scripts/rig/headless-wayland.sh <command> [args...]
# scripts/rig/headless.sh [--x11] <command> [args...]
#
# Starts a private D-Bus session with PipeWire, WirePlumber, headless Sway and
# xdg-desktop-portal-wlr (auto-selecting the only output), plus a 60 FPS test
# pattern standing in for a game. Everything is torn down when the command exits.
# Starts a private D-Bus session with PipeWire, WirePlumber, a virtual audio
# sink, headless Sway on the GPU and xdg-desktop-portal-wlr (the portal
# auto-selects the only output). With --x11, the game and the command run as
# X11 clients of Sway's Xwayland instead, which has GPU rendering (DRI3) and an
# EWMH window manager like a real X11 desktop. A 60 FPS test pattern with a
# 440 Hz tone stands in for a game. Everything is torn down when the command
# exits.
#
# This rig exercises the PipeWire/portal path on a machine without a display.
# It does not replace validation on real hardware (Hyprland, X11, Windows).
# The command sees CLIPBIN_RIG_DIR (logs, plus game.pid and xdpw.pid for the
# test game and the portal backend) and, with --x11, CLIPBIN_RIG_GAME_WINDOW
# (the test window's X id). Set CLIPBIN_RIG_KEEP_LOGS=<dir> to keep the logs.
#
# This exercises the capture paths on a machine without a display. It does not
# replace validation on real hardware (Hyprland, an X11 desktop, Windows).
set -euo pipefail
mode=wayland
if [[ "${1:-}" == "--x11" ]]; then
mode=x11
shift
fi
if [[ $# -eq 0 ]]; then
echo "usage: $0 <command> [args...]" >&2
echo "usage: $0 [--x11] <command> [args...]" >&2
exit 2
fi
if [[ -z "${CLIPBIN_RIG_INNER:-}" ]]; then
exec env CLIPBIN_RIG_INNER=1 dbus-run-session -- "$0" "$@"
args=("$@")
[[ "${mode}" == x11 ]] && args=(--x11 "${args[@]}")
exec env CLIPBIN_RIG_INNER=1 dbus-run-session -- "$0" "${args[@]}"
fi
rig_dir="$(mktemp -d "${TMPDIR:-/tmp}/clipbin-rig.XXXXXX")"
@@ -33,25 +48,14 @@ trap cleanup EXIT
export XDG_RUNTIME_DIR="${rig_dir}/runtime"
export XDG_CONFIG_HOME="${rig_dir}/config"
export XDG_CURRENT_DESKTOP=sway
export XDG_SESSION_TYPE=wayland
mkdir -p "${XDG_RUNTIME_DIR}" "${XDG_CONFIG_HOME}/xdg-desktop-portal-wlr" "${XDG_CONFIG_HOME}/sway"
export XDG_CURRENT_DESKTOP=sway XDG_SESSION_TYPE=wayland
mkdir -p "${XDG_RUNTIME_DIR}" "${XDG_CONFIG_HOME}/sway" "${XDG_CONFIG_HOME}/xdg-desktop-portal-wlr"
chmod 700 "${XDG_RUNTIME_DIR}"
unset WAYLAND_DISPLAY DISPLAY
width="${CLIPBIN_RIG_WIDTH:-1920}"
height="${CLIPBIN_RIG_HEIGHT:-1080}"
cat > "${XDG_CONFIG_HOME}/sway/config" <<EOF
output HEADLESS-1 resolution ${width}x${height}@60Hz
default_border none
EOF
cat > "${XDG_CONFIG_HOME}/xdg-desktop-portal-wlr/config" <<EOF
[screencast]
chooser_type=none
output_name=HEADLESS-1
max_fps=60
EOF
wait_for() {
local what="$1" check="$2"
for _ in $(seq 100); do
@@ -72,28 +76,54 @@ wait_for pipewire-pulse '[[ -S "${XDG_RUNTIME_DIR}/pulse/native" ]]'
pactl load-module module-null-sink sink_name=clipbin-rig-sink > /dev/null
pactl set-default-sink clipbin-rig-sink
printf '%s\n' \
"output HEADLESS-1 resolution ${width}x${height}@60Hz" \
"default_border none" \
"xwayland force" \
"exec echo \"\$DISPLAY\" > ${rig_dir}/xwayland-display" \
> "${XDG_CONFIG_HOME}/sway/config"
printf '%s\n' \
"[screencast]" \
"chooser_type=none" \
"output_name=${CLIPBIN_RIG_PORTAL_OUTPUT:-HEADLESS-1}" \
"max_fps=60" \
> "${XDG_CONFIG_HOME}/xdg-desktop-portal-wlr/config"
WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 WLR_HEADLESS_OUTPUTS=1 \
sway --config "${XDG_CONFIG_HOME}/sway/config" > "${rig_dir}/sway.log" 2>&1 & pids+=($!)
wait_for sway 'compgen -G "${XDG_RUNTIME_DIR}/wayland-*" > /dev/null'
WAYLAND_DISPLAY="$(basename "$(compgen -G "${XDG_RUNTIME_DIR}/wayland-*" | grep -v lock | head -1)")"
export WAYLAND_DISPLAY
SWAYSOCK="$(compgen -G "${XDG_RUNTIME_DIR}/sway-ipc.*" | head -1)"
export SWAYSOCK
export WAYLAND_DISPLAY SWAYSOCK
dbus-update-activation-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_RUNTIME_DIR XDG_CONFIG_HOME SWAYSOCK
/usr/lib/xdg-desktop-portal-wlr > "${rig_dir}/xdpw.log" 2>&1 & pids+=($!)
echo "${pids[-1]}" > "${rig_dir}/xdpw.pid"
/usr/lib/xdg-desktop-portal -r > "${rig_dir}/xdp.log" 2>&1 & pids+=($!)
video_driver=wayland
if [[ "${mode}" == x11 ]]; then
wait_for Xwayland '[[ -s "${rig_dir}/xwayland-display" ]]'
DISPLAY="$(cat "${rig_dir}/xwayland-display")"
export DISPLAY XDG_SESSION_TYPE=x11
wait_for Xwayland 'xwininfo -root > /dev/null 2>&1'
unset WAYLAND_DISPLAY
video_driver=x11
fi
# A moving 60 FPS picture with a 440 Hz tone, standing in for a game.
SDL_VIDEODRIVER=wayland ffplay -loglevel error -fs -window_title clipbin-test-game \
SDL_VIDEODRIVER="${video_driver}" ffplay -loglevel error -fs -window_title clipbin-test-game \
-f lavfi "testsrc2=size=${width}x${height}:rate=60,format=yuv420p[out0];sine=frequency=440:sample_rate=48000[out1]" \
> "${rig_dir}/game.log" 2>&1 & pids+=($!)
echo "${pids[-1]}" > "${rig_dir}/game.pid"
if [[ "${mode}" == x11 ]]; then
wait_for "the test window" 'xwininfo -name clipbin-test-game > /dev/null 2>&1'
CLIPBIN_RIG_GAME_WINDOW="$(xwininfo -name clipbin-test-game | awk '/Window id:/ { print strtonum($4) }')"
export CLIPBIN_RIG_GAME_WINDOW
fi
sleep 1
export CLIPBIN_RIG_DIR="${rig_dir}"
echo "rig: ready (WAYLAND_DISPLAY=${WAYLAND_DISPLAY}, logs in ${rig_dir})" >&2
echo "rig: ready (${mode}, logs in ${rig_dir})" >&2
status=0
"$@" || status=$?
if [[ -n "${CLIPBIN_RIG_KEEP_LOGS:-}" ]]; then