#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: mangohud-position-lab.sh [app] [output_dir] Examples: mangohud-position-lab.sh ~/.config/mangotune/profiles/zz_test_right_sparse_top.conf glxgears mangohud-position-lab.sh ~/.config/mangotune/profiles/zz_test_right_full_top.conf vkcube /tmp/mh-lab Notes: - This runs MangoHud directly, outside MangoTune's preview pipeline. - OpenGL uses MANGOHUD_DLSYM=1. - If xdotool/import are installed and a real X11 session is available, a screenshot and basic window geometry will be written to the output directory. - Override DISPLAY/XAUTHORITY/WAIT_SECS/WIDTH/HEIGHT from the environment when needed. EOF } if [[ "${1:-}" == "-h" || "${1:-}" == "--help" || $# -lt 1 ]]; then usage exit 0 fi CONFIG_PATH="$1" APP="${2:-glxgears}" OUTPUT_DIR="${3:-/tmp/mangohud-position-lab}" DISPLAY_VALUE="${DISPLAY:-:0}" XAUTH_VALUE="${XAUTHORITY:-$HOME/.Xauthority}" WAIT_SECS="${WAIT_SECS:-4}" WIDTH="${WIDTH:-1400}" HEIGHT="${HEIGHT:-760}" mkdir -p "$OUTPUT_DIR" rm -f "$OUTPUT_DIR"/run.log "$OUTPUT_DIR"/window.geom "$OUTPUT_DIR"/window.png \ "$OUTPUT_DIR"/screen.png "$OUTPUT_DIR"/window_from_screen.png if [[ ! -f "$CONFIG_PATH" ]]; then echo "Config not found: $CONFIG_PATH" >&2 exit 2 fi cleanup() { if [[ -n "${PID:-}" ]]; then kill "$PID" 2>/dev/null || true wait "$PID" 2>/dev/null || true fi } trap cleanup EXIT case "$APP" in glxgears) MATCH_NAME="glxgears" DISPLAY="$DISPLAY_VALUE" \ XAUTHORITY="$XAUTH_VALUE" \ MANGOHUD_CONFIGFILE="$CONFIG_PATH" \ MANGOHUD_DLSYM=1 \ mangohud glxgears -geometry "${WIDTH}x${HEIGHT}" >"$OUTPUT_DIR/run.log" 2>&1 & ;; vkcube) MATCH_NAME="Vkcube" DISPLAY="$DISPLAY_VALUE" \ XAUTHORITY="$XAUTH_VALUE" \ MANGOHUD_CONFIGFILE="$CONFIG_PATH" \ WGPU_BACKEND="${WGPU_BACKEND:-gl}" \ mangohud vkcube --width "$WIDTH" --height "$HEIGHT" >"$OUTPUT_DIR/run.log" 2>&1 & ;; *) echo "Unsupported app: $APP" >&2 exit 2 ;; esac PID=$! echo "pid=$PID" >>"$OUTPUT_DIR/run.log" printf 'display=%s\nxauthority=%s\nwidth=%s\nheight=%s\n' \ "$DISPLAY_VALUE" "$XAUTH_VALUE" "$WIDTH" "$HEIGHT" >>"$OUTPUT_DIR/run.log" sleep "$WAIT_SECS" if command -v xwininfo >/dev/null 2>&1; then DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" \ xwininfo -root -tree >"$OUTPUT_DIR/xwininfo.tree" 2>/dev/null || true fi if command -v xdotool >/dev/null 2>&1; then WINDOW_ID="$(DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool search --name "$MATCH_NAME" 2>/dev/null | tail -n1 || true)" else WINDOW_ID="" fi if [[ -n "$WINDOW_ID" ]]; then echo "window_id=$WINDOW_ID" >>"$OUTPUT_DIR/run.log" DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" \ xdotool getwindowgeometry --shell "$WINDOW_ID" >"$OUTPUT_DIR/window.geom" 2>/dev/null || true if command -v import >/dev/null 2>&1; then DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" \ import -window "$WINDOW_ID" "$OUTPUT_DIR/window.png" 2>/dev/null || true DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" \ import -window root "$OUTPUT_DIR/screen.png" 2>/dev/null || true if [[ -f "$OUTPUT_DIR/window.geom" ]] && [[ -f "$OUTPUT_DIR/screen.png" ]] && command -v convert >/dev/null 2>&1; then # Crop the full-screen capture back down to the app window bounds. This preserves overlays # when the compositor draws them separately from the client window content. # shellcheck disable=SC1090 . "$OUTPUT_DIR/window.geom" if [[ -n "${WIDTH:-}" && -n "${HEIGHT:-}" && -n "${X:-}" && -n "${Y:-}" ]]; then convert "$OUTPUT_DIR/screen.png" -crop "${WIDTH}x${HEIGHT}+${X}+${Y}" +repage \ "$OUTPUT_DIR/window_from_screen.png" 2>/dev/null || true fi fi fi fi echo "Wrote artifacts to $OUTPUT_DIR"