#!/usr/bin/env bash # Grab the GAME's image, not the emulator window. # # Every pixel oracle in this toolkit (at_menu, at_title, wait_not_black, the # HUD readers) was measured against a bare 1280x720 game image. xenia's window # is a GTK window with a menu bar, so the game surface actually starts ~25 px # down — and when it does, all of those constants read the wrong pixels. That # is not a hypothetical: a run sat on a plainly visible MAIN MENU for 300 s # reporting "no main menu", and a later one missed the title screen entirely and # let the attract movie loop for ten minutes. # # The offset is not guessed: it is the xenia window's height minus 720. So this # works whether or not the menu bar is there, and needs no per-display constant. # Put this directory first on PATH and existing scripts keep working unchanged. set -u REAL=/usr/local/bin/screenshot OUT="${1:-}" if [ -z "$OUT" ]; then dir="${HOME:-/tmp}/shots"; mkdir -p "$dir" n_file="$dir/.counter"; n=$(( $(cat "$n_file" 2>/dev/null || echo 0) + 1 )); echo "$n" > "$n_file" OUT="$dir/shot-$(printf '%04d' "$n").png" fi RAW="$(mktemp /tmp/shot-raw-XXXXXX.png)" trap 'rm -f "$RAW"' EXIT "$REAL" "$RAW" >/dev/null || exit 1 # "…": ("xenia_canary" "Xenia_canary") 1280x745+1+20 +1+45 # # TWO traps here, both measured (2026-08-18): # * `-children` only lists DIRECT children of the root, and openbox reparents # the game window, so the real one is not there at all; # * this app owns more than one window with the class "xenia_canary" — an # off-screen 10x10 helper as well as the 1280x745 game window — so `head -1` # picked the 10x10 one and every grab came back a 10-pixel sliver. Nothing # errored: the pixel oracles simply classified the sliver, and a whole # session's screen ids were noise. # So walk the full tree and take the LARGEST xenia window, by area. Use the # trailing ABSOLUTE geometry (the second +X+Y), because a reparented window's # own +X+Y is relative to its frame. geo=$(xwininfo -root -tree 2>/dev/null \ | grep '"xenia_canary"' \ | grep -oE '[0-9]+x[0-9]+\+-?[0-9]+\+-?[0-9]+[[:space:]]+\+-?[0-9]+\+-?[0-9]+' \ | awk '{ split($1, g, /[x+]/); a = g[1] * g[2]; # MIN_W: largest-by-area is not enough on its own. When the game # window is momentarily absent from the tree (during a load or a # mode switch) the only xenia_canary window left is the narrow # helper, it wins by default, and the crop below produces a # SLIVER. Measured 2026-08-26: a 10x710 grab that screen_id then # classified as `menu`, so a nav guard passed on garbage and the # run loaded the wrong stage. Ignore anything too narrow to be a # game frame and fall through to the raw root grab instead. if (g[1] >= 640 && a > best) { best = a; split($2, p, /\+/); out = g[1] "x" g[2] "+" p[2] "+" p[3] } } END { if (best) print out }') if [ -z "$geo" ]; then # no window found — hand back the raw grab cp "$RAW" "$OUT"; echo "$OUT"; exit 0 fi W=${geo%%x*}; rest=${geo#*x}; H=${rest%%+*}; rest=${rest#*+}; X=${rest%%+*}; Y=${rest#*+} OFF=$(( H - 720 )); [ "$OFF" -lt 0 ] && OFF=0 TOP=$(( Y + OFF )) [ "$TOP" -lt 0 ] && TOP=0 if [ "$OFF" -eq 0 ] && [ "$TOP" -eq 0 ] && [ "$X" -eq 0 ]; then cp "$RAW" "$OUT"; echo "$OUT"; exit 0 fi # Height is whatever is still on screen: a 1280x745 window on a 720-high root # loses its last rows, and every oracle point in use sits well above them. convert "$RAW" -crop "${W}x720+${X}+${TOP}" +repage "$OUT" 2>/dev/null \ || cp "$RAW" "$OUT" echo "$OUT"