The app owns TWO windows of class xenia_canary -- measured in the tree right now
as 10x10+10+10 and 1280x745+1+20. Largest-by-area picks the game window while
both are present, but during a load or mode switch the game window is briefly
absent from the tree, the 10x10 helper wins by default, and the crop produces a
10x710 SLIVER. That is exactly the grab screen_id classified as `menu` on
2026-08-26, which let a nav guard pass on garbage and loaded the wrong stage.
Ignore candidates narrower than 640 so nothing is selected in that case and the
existing fall-through hands back the raw root grab -- itself a valid full frame.
Verified on the selection logic directly: with both windows listed the pick is
unchanged (1280x745+1+45); with only the helper listed the old logic returned
10x10+10+10 and the new one selects nothing. Pairs with 30e53f5, which rejects
such a frame at the consumer.
72 lines
3.6 KiB
Bash
Executable File
72 lines
3.6 KiB
Bash
Executable File
#!/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"
|