146 lines
5.1 KiB
Bash
Executable file
146 lines
5.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
# See LICENSE.md for license information.
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# verify-dvx-icon.sh - a DVX program's icon as Program Manager shows it.
|
|
#
|
|
# make gives every DVX program its icon (make/dvx.mk: tools/iconBake.py bmp,
|
|
# a 32x32 24-bit BMP on DVX's grey, appended by dvxres as the icon32
|
|
# resource). DVX is staged with DRAW (examples/draw) installed and Program
|
|
# Manager, DVX's own, as the desktop (scripts/lib/dvxGate.sh), and boots in
|
|
# DOSBox Staging; Program Manager draws each program by its icon32, so
|
|
# DOSBox's own screenshot must hold the baked icon: every pixel of it that
|
|
# is not the grey, in its colour (give or take TOLERANCE: the desktop is
|
|
# 16-bit).
|
|
#
|
|
# Usage: scripts/verify-dvx-icon.sh
|
|
# Env: DVX_ICON_KEEP keep the last screenshot as build/dvx/dvx-icon.png
|
|
# Exit: 0 PASS, 1 FAIL, 2 SKIP (DOSBox or DVX not installed, or DRAW not
|
|
# built)
|
|
# Requires: DOSBox Staging, Xvfb, xdotool, python3 + PIL + numpy, the dvx
|
|
# component; DRAW (make dvx).
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
source "$repo/scripts/lib/dvxGate.sh"
|
|
joeyNeed DOSBOX dosbox
|
|
joeyNeed DVX_ROOT dvx
|
|
|
|
label=verify-dvx-icon
|
|
program=draw
|
|
# DVX's own desktop program, from its DVX.INI.
|
|
progman=apps/kpunch/progman/progman.app
|
|
wall=180 # hard cap on DOSBox, whatever happens to this script
|
|
readySeconds=60 # DVX boot until its shell reports ready
|
|
lookSeconds=60 # then screenshots until the icon shows
|
|
shotSeconds=3
|
|
|
|
dvxNeedBuild "$program"
|
|
icon=$repo/build/dvx/apps/$program/icon32.bmp
|
|
[ -f "$icon" ] || { echo "$label: build DRAW first (make dvx)" >&2; exit 2; }
|
|
|
|
work=$(mktemp -d -t joeylib-dvx-icon.XXXXXX)
|
|
guard=""
|
|
dosbox=""
|
|
xvfb=""
|
|
cleanup() {
|
|
joeyStop "$dosbox" "$guard" "$xvfb" 2> /dev/null
|
|
rm -rf "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
|
|
# iconShown PNG -- true when PNG holds the icon; prints the verdict.
|
|
iconShown() {
|
|
python3 - "$1" "$icon" "$repo/tools" "$label" <<'PY'
|
|
import sys
|
|
|
|
import numpy
|
|
from PIL import Image
|
|
|
|
png, bmp, tools, label = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
sys.path.insert(0, tools)
|
|
import iconBake # noqa: E402
|
|
|
|
# A 16-bit desktop keeps 5 or 6 bits a gun.
|
|
TOLERANCE = 8
|
|
icon = numpy.asarray(Image.open(bmp).convert("RGB"), dtype=numpy.int16)
|
|
opaque = numpy.argwhere(numpy.any(icon != numpy.array(iconBake.DVX_GREY), axis=2))
|
|
colours = icon[opaque[:, 0], opaque[:, 1]]
|
|
screen = numpy.asarray(Image.open(png).convert("RGB"), dtype=numpy.int16)
|
|
ih, iw = icon.shape[:2]
|
|
sh, sw = screen.shape[:2]
|
|
|
|
|
|
def matches(colour):
|
|
return numpy.all(numpy.abs(screen - colour) <= TOLERANCE, axis=2)
|
|
|
|
|
|
counts = [int(matches(c).sum()) for c in colours]
|
|
anchor = min(range(len(opaque)), key=lambda i: counts[i])
|
|
ay, ax = opaque[anchor]
|
|
for y, x in numpy.argwhere(matches(colours[anchor])):
|
|
top, left = y - ay, x - ax
|
|
if top < 0 or left < 0 or top + ih > sh or left + iw > sw:
|
|
continue
|
|
shown = screen[top + opaque[:, 0], left + opaque[:, 1]]
|
|
if numpy.all(numpy.abs(shown - colours) <= TOLERANCE):
|
|
print(f"{label}: PASS (Program Manager shows DRAW by its icon at {left},{top}: all {len(opaque)} pixels of the baked {iw}x{ih} that are not the grey)")
|
|
sys.exit(0)
|
|
print(f"{label}: FAIL - DRAW's icon is not on the screen ({len(opaque)} pixels looked for)")
|
|
sys.exit(1)
|
|
PY
|
|
}
|
|
|
|
|
|
display=":$((RANDOM % 400 + 2500))"
|
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
|
xvfb=$!
|
|
sleep 1
|
|
|
|
dvxStage "$work/c" "$(dvxAppDir "$program")"
|
|
dvxIniSet "$work/c/DVX/config/DVX.INI" "$progman"
|
|
dvxDosboxConf "$work/dosbox.conf" "$work/c"
|
|
printf '\n[capture]\ncapture_dir = %s\ndefault_image_capture_formats = raw\n' "$work/shots" >> "$work/dosbox.conf"
|
|
mkdir -p "$work/xdg" "$work/shots"
|
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
|
timeout -s KILL "$wall" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
|
--conf "$work/dosbox.conf" > "$work/dosbox.log" 2>&1 &
|
|
guard=$!
|
|
dosbox=$(joeyChild "$guard")
|
|
|
|
ready=""
|
|
for ((waited = 0; waited < readySeconds; waited++)); do
|
|
if dvxDvxLog "$work/c" 200 | grep -q "DVX Shell ready"; then
|
|
ready=1
|
|
break
|
|
fi
|
|
joeyAlive "$guard" || break
|
|
sleep 1
|
|
done
|
|
if [ -z "$ready" ]; then
|
|
echo "$label: FAIL - DVX did not come up within ${readySeconds}s (DVX.LOG follows)" >&2
|
|
dvxDvxLog "$work/c" >&2
|
|
exit 1
|
|
fi
|
|
win=$(joeyFindWindow "$display" "$dosbox")
|
|
[ -n "$win" ] || { echo "$label: FAIL - DOSBox window not found" >&2; exit 1; }
|
|
lookEnd=$((SECONDS + lookSeconds))
|
|
while [ "$SECONDS" -lt "$lookEnd" ]; do
|
|
sleep "$shotSeconds"
|
|
rm -f "$work/shots/"*.png
|
|
# Ctrl+F5: DOSBox's screenshot hotkey.
|
|
DISPLAY="$display" xdotool key --window "$win" ctrl+F5
|
|
sleep 1
|
|
png=$(find "$work/shots" -name '*.png' -print -quit)
|
|
[ -n "$png" ] || continue
|
|
[ -n "${DVX_ICON_KEEP:-}" ] && cp "$png" "$repo/build/dvx/dvx-icon.png"
|
|
if iconShown "$png" > "$work/verdict"; then
|
|
cat "$work/verdict"
|
|
exit 0
|
|
fi
|
|
done
|
|
cat "$work/verdict" 2> /dev/null || echo "$label: FAIL - DOSBox took no screenshot"
|
|
exit 1
|