157 lines
5.7 KiB
Bash
Executable file
157 lines
5.7 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-be-icon.sh - a Be API program's icon as Tracker shows it.
|
|
#
|
|
# make puts every Be API program's icons into its resources (make/be.mk:
|
|
# tools/iconBake.py be, 32x32 and 16x16 in the BeOS palette, attached by
|
|
# xres as BEOS:L:STD_ICON and BEOS:M:STD_ICON). Tracker shows a file by the
|
|
# icons in its attributes, which a program built here does not have; the
|
|
# backend copies them over from its resources when it starts
|
|
# (beBackend.cpp, iconAttributesEnsure). In the port's VM
|
|
# (scripts/lib/beGate.sh) the job runs DRAW (examples/draw) until its icon
|
|
# attribute is there (attributeSeconds at most), stops it, and opens a
|
|
# Tracker window on its folder: a screendump must hold the baked icon, one
|
|
# size or the other, every pixel it leaves opaque in the BeOS palette's
|
|
# colour (give or take TOLERANCE: BeOS R5 runs in 16-bit colour).
|
|
#
|
|
# Usage: scripts/verify-be-icon.sh [haiku|haiku32|beos] (default haiku)
|
|
# Env: BE_ICON_KEEP keep the last screendump as build/PORT/be-icon.png
|
|
# Exit: 0 PASS, 1 FAIL, 2 SKIP (the VM not installed, or DRAW not built)
|
|
# Requires: QEMU and the port's VM (./install.sh install emulator:PORT),
|
|
# python3 + PIL + numpy; build/PORT/bin/DRAW and its icon
|
|
# (make PORT).
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
source "$repo/scripts/lib/beGate.sh"
|
|
|
|
port=${1:-haiku}
|
|
label="verify-be-icon ($port)"
|
|
beNeed "$port"
|
|
program=$(joeyExampleProgram draw "$port")
|
|
iconDir=$repo/build/$port/icon/DRAW
|
|
[ -x "$program" ] && [ -f "$iconDir/icon32.raw" ] && [ -f "$iconDir/icon16.raw" ] || { echo "$label: build DRAW first (make $port)" >&2; exit 2; }
|
|
bootSeconds=120
|
|
# Seconds the gate looks for the icon in Tracker's window, and the job keeps
|
|
# the machine up after (longer: the screendumps must not outlast it).
|
|
lookSeconds=90
|
|
holdSeconds=150
|
|
# Seconds DRAW has to give itself its icon attribute, and the attributes
|
|
# then have to settle.
|
|
attributeSeconds=60
|
|
settleSeconds=5
|
|
case "$port" in
|
|
beos) tracker=/boot/beos/system/Tracker ;;
|
|
*) tracker=/system/Tracker ;;
|
|
esac
|
|
|
|
work=$(mktemp -d -t joeylib-be-icon.XXXXXX)
|
|
cleanup() {
|
|
beStop
|
|
rm -rf "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
|
|
# iconShown PNG -- true when the screendump PNG holds the icon, either size
|
|
# (Tracker's icon view shows the 32x32, its list view the 16x16); prints the
|
|
# verdict.
|
|
iconShown() {
|
|
python3 - "$1" "$iconDir" "$repo/tools" "$label" <<'PY'
|
|
import os
|
|
import sys
|
|
|
|
import numpy
|
|
from PIL import Image
|
|
|
|
png, iconDir, tools, label = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
sys.path.insert(0, tools)
|
|
import iconBake # noqa: E402
|
|
|
|
# BeOS R5's 16-bit screen keeps 5 or 6 bits a gun.
|
|
TOLERANCE = 8
|
|
screen = numpy.asarray(Image.open(png).convert("RGB"), dtype=numpy.int16)
|
|
sh, sw = screen.shape[:2]
|
|
palette = iconBake.BE_PALETTE
|
|
|
|
|
|
def matches(colour):
|
|
return numpy.all(numpy.abs(screen - colour) <= TOLERANCE, axis=2)
|
|
|
|
|
|
def find(size, raw):
|
|
"""Where the screen holds the baked SIZE x SIZE icon raw, or None."""
|
|
indices = open(raw, "rb").read()
|
|
opaque = [(i // size, i % size) for i, index in enumerate(indices) if index != iconBake.BE_TRANSPARENT]
|
|
colours = numpy.array([palette[indices[y * size + x] * 3:indices[y * size + x] * 3 + 3] for y, x in opaque], dtype=numpy.int16)
|
|
opaque = numpy.array(opaque)
|
|
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 + size > sh or left + size > sw:
|
|
continue
|
|
shown = screen[top + opaque[:, 0], left + opaque[:, 1]]
|
|
if numpy.all(numpy.abs(shown - colours) <= TOLERANCE):
|
|
return left, top, len(opaque)
|
|
return None
|
|
|
|
|
|
for size, name in ((iconBake.BE_LARGE, "icon32.raw"), (iconBake.BE_SMALL, "icon16.raw")):
|
|
at = find(size, os.path.join(iconDir, name))
|
|
if at is not None:
|
|
print(f"{label}: PASS (Tracker shows DRAW by its icon at {at[0]},{at[1]}: all {at[2]} opaque pixels of the baked {size}x{size})")
|
|
sys.exit(0)
|
|
print(f"{label}: FAIL - neither of DRAW's icons is on the screen")
|
|
sys.exit(1)
|
|
PY
|
|
}
|
|
|
|
|
|
mkdir -p "$work/job/DRAW"
|
|
cp "$program" "$work/job/DRAW/DRAW"
|
|
# The job: DRAW runs until it has given itself its icon attribute, then
|
|
# Tracker opens a window on its folder (Tracker runs already: a second start
|
|
# hands it the folder) and the machine stays up a while.
|
|
cat > "$work/job/job.sh" <<EOF
|
|
cd DRAW
|
|
./DRAW &
|
|
pid=\$!
|
|
waited=0
|
|
while [ \$waited -lt $attributeSeconds ] && ! catattr BEOS:L:STD_ICON DRAW > /dev/null 2>&1; do
|
|
sleep 1
|
|
waited=\$((waited + 1))
|
|
done
|
|
# All of it: on BeOS R5 a plain kill ends only the main thread, and the
|
|
# window stays. The registrar writes the other attributes after the first,
|
|
# and R5's Tracker keeps the icon it first finds: a moment for them.
|
|
kill -9 \$pid
|
|
sleep $settleSeconds
|
|
cd ..
|
|
$tracker "\$(pwd)/DRAW" &
|
|
sleep $holdSeconds
|
|
EOF
|
|
beJobDisk "$work/job.img" "$work/job"
|
|
|
|
beStart "$work" "$work/job.img"
|
|
if ! beWaitJob "$work" "$bootSeconds"; then
|
|
echo "$label: FAIL - the VM did not start the job within ${bootSeconds} s" >&2
|
|
exit 1
|
|
fi
|
|
lookEnd=$((SECONDS + lookSeconds))
|
|
while [ "$SECONDS" -lt "$lookEnd" ]; do
|
|
sleep 2
|
|
beQmp "$work" screendump "$work/screen.png"
|
|
[ -n "${BE_ICON_KEEP:-}" ] && cp "$work/screen.png" "$repo/build/$port/be-icon.png"
|
|
if iconShown "$work/screen.png" > "$work/verdict"; then
|
|
cat "$work/verdict"
|
|
exit 0
|
|
fi
|
|
done
|
|
cat "$work/verdict"
|
|
exit 1
|