joeylib2/scripts/verify-dos-mouse.sh

155 lines
5.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-dos-mouse.sh - Headless mouse acceptance gate for the DOS port.
# The port has the mouse driver call it on every move and button change
# (INT 33h function 0Ch, a DPMI real-mode callback, src/dos/input.c); a
# callback that never runs leaves the pointer where it started. Runs
# KEYS.EXE in DOSBox Staging under Xvfb with the mouse captured from the
# start, moves it with xdotool (real X pointer motion), and decodes KEYS's
# mouse verification scanline (row 199: 5A5A, the button flags, X, Y as
# nibble pixels) from a raw screenshot after each step. Covered: the start
# at the stage's middle, a move each way on both axes, clamping at all
# four edges, an odd column, and press and release of the left and right
# buttons.
# DOSBox scales host motion into mickeys its own way, so a move is checked
# by its direction and only the clamps by position, and a one-pixel nudge
# by landing on an odd column: in mode 13h the driver steps X by 2 in a
# 640-wide virtual screen, which the port halves, so every column counts.
#
# Usage: scripts/verify-dos-mouse.sh
# Requires: DOSBox Staging (install.sh install emulator:dos), Xvfb, xdotool, python3 + PIL;
# build/dos/bin/KEYS.EXE (make dos).
set -uo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo/scripts/lib/joeyEnv.sh"
source "$repo/scripts/lib/dosboxKeys.sh"
joeyNeed DOSBOX dosbox
bin_dir=$repo/build/dos/bin
conf=$repo/scripts/dosbox-386sx16.conf
label=verify-dos-mouse
[ -f "$bin_dir/KEYS.EXE" ] || { echo "$label: build KEYS.EXE first (make dos)" >&2; exit 2; }
work=$(mktemp -d -t joeylib-dos-mouse.XXXXXX)
display_num=$((RANDOM % 500 + 300))
cleanup() {
joeyStop "${dosbox_pid:-}"
[ -n "${xvfb_pid:-}" ] && kill "$xvfb_pid" 2>/dev/null
rm -rf "$work"
}
trap cleanup EXIT
mkdir -p "$work/shots"
dosboxKeysMapper "$work/mapper.txt"
Xvfb ":$display_num" -screen 0 1024x768x24 >/dev/null 2>&1 &
xvfb_pid=$!
sleep 1
XDG_CONFIG_HOME="$work/xdg" DISPLAY=":$display_num" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
--conf "$conf" \
--set "capture_dir=$work/shots" \
--set "mapperfile=$work/mapper.txt" \
--set "mouse_capture=onstart" \
-c "MOUNT C $bin_dir" -c "C:" -c "KEYS.EXE" \
>"$work/dosbox.log" 2>&1 &
dosbox_pid=$!
# Let DOS boot and KEYS reach its poll loop on the simulated 386SX-16.
sleep 12
win=$(joeyFindWindow ":$display_num" "$dosbox_pid")
if [ -z "$win" ]; then
echo "$label: FAIL - DOSBox window not found" >&2
exit 1
fi
DISPLAY=":$display_num" xdotool windowactivate --sync "$win" 2>/dev/null || true
# The mouse row of a fresh screenshot as "flags x y" (hex flags).
readRow() {
local png="$work/$1.png"
dosboxShot ":$display_num" "$win" "$work/shots" "$png" || return 1
python3 - "$png" <<'PY'
import sys
from PIL import Image
img = Image.open(sys.argv[1]).convert("RGB")
w, h = img.size
sx, sy = w // 320, h // 200
if sx < 1 or sy < 1 or w % 320 or h % 200:
sys.exit(1)
def dac8(c4):
v6 = ((c4 << 2) | (c4 >> 2)) & 0x3F
return (v6 << 2) | (v6 >> 4)
# KEYS palette 0 (as verify-dos-input.sh decodes it).
pal12 = [(i << 8) | (i << 4) | i for i in range(16)]
pal12[0], pal12[1], pal12[2], pal12[3] = 0x000, 0x333, 0x0F0, 0xFFF
pal12[15] = 0xF00
palRgb = [(dac8((v >> 8) & 0xF), dac8((v >> 4) & 0xF), dac8(v & 0xF)) for v in pal12]
def pix(px):
r, g, b = img.getpixel((px * sx, 199 * sy))
return min(range(16), key=lambda n: (r - palRgb[n][0]) ** 2 + (g - palRgb[n][1]) ** 2 + (b - palRgb[n][2]) ** 2)
nib = [pix(i) for i in range(16)]
words = [(nib[4 * k] << 12) | (nib[4 * k + 1] << 8) | (nib[4 * k + 2] << 4) | nib[4 * k + 3] for k in range(4)]
if words[0] != 0x5A5A:
sys.exit(1)
print("%04X %d %d" % (words[1], words[2], words[3]))
PY
}
fails=()
step() {
local tag="$1"
local row
shift
if [ $# -gt 0 ]; then
DISPLAY=":$display_num" xdotool "$@"
sleep 1
fi
if ! row=$(readRow "$tag"); then
fails+=("$tag: no mouse row in the screenshot")
row="---- -1 -1"
fi
read -r flags mx my <<< "$row"
echo " $tag: flags=$flags x=$mx y=$my"
}
step start
[ "$flags $mx $my" = "1000 160 100" ] || fails+=("start: expected flags 1000 at 160,100")
step upLeft mousemove_relative -- -60 -40
[ "$flags" = "1000" ] && [ "$mx" -lt 160 ] && [ "$my" -lt 100 ] || fails+=("a move up and left did not arrive")
lastX=$mx
lastY=$my
step downRight mousemove_relative -- 120 80
[ "$flags" = "1000" ] && [ "$mx" -gt "$lastX" ] && [ "$my" -gt "$lastY" ] || fails+=("a move down and right did not arrive")
step topLeft mousemove_relative -- -2000 -2000
[ "$flags $mx $my" = "1000 0 0" ] || fails+=("not held at the top left (0,0)")
step nudge mousemove_relative -- 1 0
[ "$flags" = "1000" ] && [ $((mx % 2)) -eq 1 ] || fails+=("a one-pixel nudge did not reach an odd column")
step bottomRight mousemove_relative -- 2000 2000
[ "$flags $mx $my" = "1000 319 199" ] || fails+=("not held at the bottom right (319,199)")
step leftDown mousedown 1
[ "$flags" = "1100" ] || fails+=("the left button down not seen")
step leftUp mouseup 1
[ "$flags" = "1000" ] || fails+=("the left button up not seen")
step rightDown mousedown 3
[ "$flags" = "1010" ] || fails+=("the right button down not seen")
step rightUp mouseup 3
[ "$flags" = "1000" ] || fails+=("the right button up not seen")
if [ ${#fails[@]} -eq 0 ]; then
echo "$label: PASS (moves both ways, all four edges, every column, both buttons down and up)"
exit 0
fi
for f in "${fails[@]}"; do
echo "$label: FAIL - $f" >&2
done
exit 1