141 lines
5.4 KiB
Bash
Executable File
141 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Run a command inside a disposable headless desktop for capture tests.
|
|
#
|
|
# scripts/rig/headless.sh [--x11] <command> [args...]
|
|
#
|
|
# 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.
|
|
#
|
|
# 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 [--x11] <command> [args...]" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ -z "${CLIPBIN_RIG_INNER:-}" ]]; then
|
|
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")"
|
|
pids=()
|
|
cleanup() {
|
|
for pid in "${pids[@]}"; do kill "${pid}" 2>/dev/null || true; done
|
|
wait 2>/dev/null || true
|
|
# The D-Bus-activated document portal mounts a FUSE filesystem here.
|
|
fusermount3 -u "${XDG_RUNTIME_DIR}/doc" 2>/dev/null || true
|
|
if [[ -n "${CLIPBIN_RIG_KEEP_LOGS:-}" ]]; then
|
|
mkdir -p "${CLIPBIN_RIG_KEEP_LOGS}"
|
|
cp "${rig_dir}"/*.log "${CLIPBIN_RIG_KEEP_LOGS}/" 2>/dev/null || true
|
|
fi
|
|
rm -rf "${rig_dir}"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
export XDG_RUNTIME_DIR="${rig_dir}/runtime"
|
|
export XDG_CONFIG_HOME="${rig_dir}/config"
|
|
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}"
|
|
|
|
wait_for() {
|
|
local what="$1" check="$2"
|
|
for _ in $(seq 100); do
|
|
if eval "${check}"; then return 0; fi
|
|
sleep 0.1
|
|
done
|
|
echo "rig: ${what} did not start; logs in ${rig_dir}" >&2
|
|
exit 1
|
|
}
|
|
|
|
pipewire > "${rig_dir}/pipewire.log" 2>&1 & pids+=($!)
|
|
wait_for pipewire '[[ -S "${XDG_RUNTIME_DIR}/pipewire-0" ]]'
|
|
wireplumber > "${rig_dir}/wireplumber.log" 2>&1 & pids+=($!)
|
|
pipewire-pulse > "${rig_dir}/pipewire-pulse.log" 2>&1 & pids+=($!)
|
|
echo "${pids[-1]}" > "${rig_dir}/pulse.pid"
|
|
wait_for pipewire-pulse '[[ -S "${XDG_RUNTIME_DIR}/pulse/native" ]]'
|
|
# No sound card here: route playback to a virtual sink whose monitor OBS
|
|
# records as desktop audio.
|
|
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"
|
|
|
|
export SWAYSOCK="${XDG_RUNTIME_DIR}/clipbin-sway.sock"
|
|
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)")"
|
|
wait_for "sway IPC" '[[ -S "${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, optionally without any audio stream for idle tests.
|
|
pattern="testsrc2=size=${width}x${height}:rate=60,format=yuv420p[out0]"
|
|
if [[ "${CLIPBIN_RIG_NO_AUDIO:-0}" != 1 ]]; then
|
|
pattern+=';sine=frequency=440:sample_rate=48000[out1]'
|
|
fi
|
|
SDL_VIDEODRIVER="${video_driver}" ffplay -loglevel error -fs -window_title clipbin-test-game \
|
|
-f lavfi "${pattern}" \
|
|
> "${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 (${mode}, logs in ${rig_dir})" >&2
|
|
status=0
|
|
"$@" || status=$?
|
|
|
|
exit "${status}"
|