joeylib2/scripts/verify-dvx-input.sh

211 lines
7.6 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-input.sh - Typed-character and present gate for DVX. Runs KEYS
# (examples/keys) as the desktop of a scratch DVX in DOSBox Staging under
# Xvfb and types the RetroNet definition-of-done string with xdotool (real X
# key events: ':' needs Shift, which DVX's keyboard layout applies before the
# window's onKey hands the character to jlInputGetChar). Three checks:
# 1. DVX's reserved chord: Ctrl+Esc (the Task Manager) must not reach KEYS
# as Esc, which would end it (its typed line appears in its log); the
# Task Manager's Switch To gives KEYS the focus back;
# 2. the present: a raw screenshot of the desktop must show KEYS's
# verification scanline (row 198: sentinel, count, every received
# character as two pixel nibbles) somewhere -- the window's content,
# expanded from the stage through the port's palette tables into the
# desktop's pixel format at the window's scale -- decoding to exactly
# the typed bytes, every stage pixel a whole scale x scale block
# (scripts/lib/keysRow.py);
# 3. the typed history: after Escape, KEYS logs every received character
# as hex to its joeylog.txt (in its settings folder), which must equal
# the typed ASCII.
# Each desktop given (default 640x480 at 16 bits, where the window is 1x) is
# a run of its own, the window 4:3 or (":square") with square pixels;
# DVX.LOG must show KEYS's window on that desktop at the scale and in the
# shape given.
#
# Usage: scripts/verify-dvx-input.sh [WxHxBPP:SCALE[:square]]...
# e.g. scripts/verify-dvx-input.sh 800x600x8:2 1024x768x32:3
# Requires: DOSBox Staging, Xvfb, xdotool, python3 + PIL, the dvx component;
# the keys program (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
program=keys
target="192.168.1.10:6510"
stageWidth=320
stageHeight=200
shape43Height=240 # a 4:3 window's rows a scale for the stage's 200
wall=180 # hard cap on DOSBox, whatever happens to this script
readySeconds=60 # DVX boot until its shell reports ready
settleSeconds=3 # then KEYS opening its window and taking the focus
stepSeconds=2 # a key, then DVX and KEYS acting on it
holdSeconds=0.3 # a key held that long: a quick tap can fall between two polls
logSeconds=30 # Escape until KEYS's typed line is in its log
specs=("$@")
[ ${#specs[@]} -gt 0 ] || specs=(640x480x16:1)
dvxNeedBuild "$program"
work=$(mktemp -d -t joeylib-dvx-input.XXXXXX)
guard=""
dosbox=""
xvfb=""
win=""
fails=""
# 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed".
cleanup() {
joeyStop "$dosbox" "$guard" "$xvfb" 2> /dev/null
rm -rf "$work"
}
trap cleanup EXIT
# fail SPEC WHAT -- note a failed check; the gate goes on to report the rest.
fail() {
echo "verify-dvx-input: FAIL - $1: $2"
fails="${fails:+$fails; }$1: $2"
}
# hold KEY... -- press the keys in order, each held holdSeconds before the
# next (a chord's modifier goes down first), release them in reverse, then
# give DVX and KEYS time to act.
hold() {
local k
local i
for k in "$@"; do
DISPLAY="$display" xdotool keydown --window "$win" "$k"
sleep "$holdSeconds"
done
for ((i = $#; i >= 1; i--)); do
DISPLAY="$display" xdotool keyup --window "$win" "${!i}"
done
sleep "$stepSeconds"
}
# run SPEC -- one desktop: boot, type, Ctrl+Esc, screenshot, Escape, judge.
run() {
local spec="$1"
local dir="$work/$1"
local w
local h
local bpp
local scale
local shape
local rows
local ready=""
local waited
local log
local png
if ! [[ "$spec" =~ ^([0-9]+)x([0-9]+)x([0-9]+):([0-9]+)(:square)?$ ]]; then
echo "verify-dvx-input: bad desktop \"$spec\" (WxHxBPP:SCALE[:square])" >&2
exit 2
fi
w=${BASH_REMATCH[1]}
h=${BASH_REMATCH[2]}
bpp=${BASH_REMATCH[3]}
scale=${BASH_REMATCH[4]}
DVX_VIDEO_W=$w DVX_VIDEO_H=$h DVX_VIDEO_BPP=$bpp dvxStage "$dir/c" "$(dvxAppDir "$program")"
if [ -n "${BASH_REMATCH[5]}" ]; then
shape=square
rows=$((stageHeight * scale))
dvxSettings "$dir/c" "$program" "$DVX_SETTINGS_SQUARE"
else
shape=4:3
rows=$((shape43Height * scale))
fi
dvxDosboxConf "$dir/dosbox.conf" "$dir/c"
printf '\n[capture]\ncapture_dir = %s\ndefault_image_capture_formats = raw\n' "$dir/shots" >> "$dir/dosbox.conf"
mkdir -p "$dir/xdg" "$dir/shots"
XDG_CONFIG_HOME="$dir/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
timeout -s KILL "$wall" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
--conf "$dir/dosbox.conf" > "$dir/dosbox.log" 2>&1 &
guard=$!
dosbox=$(joeyChild "$guard")
for ((waited = 0; waited < readySeconds; waited++)); do
if dvxDvxLog "$dir/c" 200 | grep -q "DVX Shell ready"; then
ready=1
break
fi
joeyAlive "$guard" || break
sleep 1
done
if [ -z "$ready" ]; then
fail "$spec" "DVX did not come up within ${readySeconds}s (DVX.LOG follows)"
dvxDvxLog "$dir/c" >&2
joeyStop "$dosbox" "$guard" 2> /dev/null
return
fi
sleep "$settleSeconds"
win=$(joeyFindWindow "$display" "$dosbox")
if [ -z "$win" ]; then
fail "$spec" "DOSBox window not found"
joeyStop "$dosbox" "$guard" 2> /dev/null
return
fi
DISPLAY="$display" xdotool windowactivate --sync "$win" 2> /dev/null || true
DISPLAY="$display" xdotool type --window "$win" --delay 150 -- "$target"
sleep "$stepSeconds"
# DVX's Task Manager chord, then its Switch To back to KEYS.
hold ctrl Escape
log=$(dvxProgramLog "$dir/c" "$program")
if [ -n "$log" ] && tr -d '\r' < "$log" | grep -q '^typed='; then
fail "$spec" "Ctrl+Esc reached KEYS as Esc (it ended)"
fi
hold alt s
# Ctrl+F5 = DOSBox screenshot hotkey.
DISPLAY="$display" xdotool key --window "$win" ctrl+F5
sleep "$stepSeconds"
DISPLAY="$display" xdotool key --window "$win" Escape
log=$(dvxAwaitLog "$dir/c" "$program" '^typed=' "$logSeconds" "$guard")
cp "$log" "$dir/joeylog.txt" 2> /dev/null
joeyStop "$dosbox" "$guard" 2> /dev/null
dosbox=""
guard=""
if ! dvxDvxLog "$dir/c" 400 | grep -qF "JoeyLib: Keys: $((stageWidth * scale))x$rows at ${scale}x, $shape, on a ${w}x${h} ${bpp}-bit desktop"; then
fail "$spec" "DVX.LOG does not show KEYS at ${scale}x, $shape, on a ${w}x${h} ${bpp}-bit desktop: $(dvxDvxLog "$dir/c" 400 | grep -F 'JoeyLib: Keys:')"
fi
png=$(ls "$dir"/shots/*.png 2> /dev/null | head -1)
if [ -z "$png" ]; then
fail "$spec" "no screenshot captured"
else
python3 "$repo/scripts/lib/keysRow.py" "$png" "$target" --scale "$scale" || fail "$spec" "the present"
fi
if [ -z "$log" ]; then
fail "$spec" "KEYS logged no typed line within ${logSeconds}s of Escape"
else
dvxKeysTyped "$dir/joeylog.txt" "$target" || fail "$spec" "the log"
fi
}
display=":$((RANDOM % 400 + 2500))"
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
xvfb=$!
sleep 1
for spec in "${specs[@]}"; do
echo "verify-dvx-input: $spec"
run "$spec"
done
if [ -n "$fails" ]; then
echo "verify-dvx-input: FAIL - $fails"
exit 1
fi
echo "verify-dvx-input: PASS (${specs[*]}: typed \"$target\", all ${#target} chars reached jlInputGetChar, Ctrl+Esc did not end KEYS, and the present shows them)"