223 lines
7.4 KiB
Bash
Executable file
223 lines
7.4 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-atarist-joy.sh - Joystick gate for the Atari ST and STe builds:
|
|
# both ports, every direction, the fire button and every release, driven
|
|
# through Hatari's keyboard joysticks and read back from what examples/joy
|
|
# draws.
|
|
#
|
|
# The port (src/atarist/input.c) decodes the IKBD's joystick packets: port 1,
|
|
# the dedicated stick port, is JOYSTICK_0 (stick A) and port 0, the mouse
|
|
# port, JOYSTICK_1 (stick B); one fire button each, both always connected.
|
|
# The port keeps TOS's IKBD mode, in which port 0 is the mouse's: the IKBD
|
|
# reports no stick there, so B must stay at rest whatever is held on it, and
|
|
# port 1's fire, the same line as the mouse's right button, arrives as that
|
|
# button.
|
|
# Hatari's [Joystick0] and [Joystick1] (a config file of the gate's own) are
|
|
# keyboard joysticks on host keys, which xdotool holds on a private Xvfb
|
|
# display; Hatari's own screenshots (its command FIFO's screenshot shortcut)
|
|
# go through scripts/lib/joyScreen.py (scripts/lib/joyGate.sh). Escape must
|
|
# then end JOY.
|
|
#
|
|
# Usage: scripts/verify-atarist-joy.sh [atarist|ste] [MACHINE]
|
|
# (default atarist on an st; ste on an ste)
|
|
# Env: JOY_SHOTS also keep the screenshots in this folder
|
|
# Requires: Hatari, EmuTOS, Xvfb, xdotool, python3 + PIL;
|
|
# build/<build>/bin/JOY.PRG (make atarist / make ste).
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
source "$repo/scripts/lib/joyGate.sh"
|
|
joeyNeed HATARI hatari
|
|
joeyNeed EMUTOS_IMG emutos
|
|
|
|
build="${1:-atarist}"
|
|
# The ST build writes 3-bit colours on any machine (joyScreen.py --st).
|
|
case "$build" in
|
|
atarist) machine="${2:-st}"; JOY_SCREEN_ARGS=(--st) ;;
|
|
ste) machine="${2:-ste}" ;;
|
|
*) echo "verify-atarist-joy: build must be atarist or ste" >&2; exit 2 ;;
|
|
esac
|
|
JOY_LABEL="verify-$build-joy"
|
|
program="$repo/build/$build/bin/JOY.PRG"
|
|
wall=300 # hard cap on Hatari, whatever happens to this script
|
|
readySeconds=90 # EmuTOS booting and JOY drawing
|
|
shotSeconds=5 # the screenshot shortcut until Hatari has written the file
|
|
exitSeconds=10 # Escape until JOY's screen is gone
|
|
|
|
[ -f "$program" ] || { echo "$JOY_LABEL: build JOY.PRG first (make $build)" >&2; exit 2; }
|
|
|
|
JOY_WORK=$(mktemp -d -t joeylib-st-joy.XXXXXX)
|
|
guard=""
|
|
hatari=""
|
|
xvfb=""
|
|
display=""
|
|
win=""
|
|
cleanup() {
|
|
joeyStop "$hatari" "$guard" "$xvfb" 2> /dev/null
|
|
rm -rf "$JOY_WORK"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Control, its host key (xdotool's name) and Hatari's name for the key.
|
|
# Hatari's Joystick1 is ST port 1 (stick A), Joystick0 port 0 (stick B).
|
|
keys=(
|
|
"A-left j J" "A-right l L" "A-up i I" "A-down k K" "A-button0 a A"
|
|
"B-left f F" "B-right h H" "B-up t T" "B-down g G" "B-button0 q Q"
|
|
)
|
|
|
|
|
|
# key CONTROL -- the host key for a control.
|
|
key() {
|
|
local entry
|
|
local control
|
|
local host
|
|
for entry in "${keys[@]}"; do
|
|
read -r control host _ <<< "$entry"
|
|
if [ "$control" = "$1" ]; then
|
|
printf '%s\n' "$host"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
|
|
# hatariKey CONTROL -- Hatari's name for the control's key.
|
|
hatariKey() {
|
|
local entry
|
|
local control
|
|
local name
|
|
for entry in "${keys[@]}"; do
|
|
read -r control _ name <<< "$entry"
|
|
if [ "$control" = "$1" ]; then
|
|
printf '%s\n' "$name"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
|
|
# hold keydown|keyup CONTROL...
|
|
hold() {
|
|
local action="$1"
|
|
local control
|
|
local host
|
|
shift
|
|
for control in "$@"; do
|
|
host=$(key "$control") || { joyFail "no key for $control"; continue; }
|
|
DISPLAY="$display" xdotool "$action" "$host"
|
|
done
|
|
}
|
|
|
|
|
|
joyPress() {
|
|
hold keydown "$@"
|
|
}
|
|
|
|
|
|
joyRelease() {
|
|
hold keyup "$@"
|
|
}
|
|
|
|
|
|
# Hatari makes the file before it writes it: it is done when it has bytes
|
|
# and one more poll adds none.
|
|
joyShot() {
|
|
local before
|
|
local found
|
|
local size=""
|
|
local deadline
|
|
before=$(find "$JOY_WORK/shots" -name '*.png' | wc -l)
|
|
timeout 5 bash -c 'echo "hatari-shortcut screenshot" > "$1"' _ "$JOY_WORK/cmd.fifo" || return 1
|
|
deadline=$((SECONDS + shotSeconds))
|
|
while [ "$SECONDS" -lt "$deadline" ]; do
|
|
sleep 0.2
|
|
[ "$(find "$JOY_WORK/shots" -name '*.png' | wc -l)" -gt "$before" ] || continue
|
|
found=$(ls -t "$JOY_WORK/shots"/*.png | head -1)
|
|
[ -s "$found" ] || continue
|
|
if [ "$(stat -c %s "$found")" = "$size" ]; then
|
|
mv "$found" "$1"
|
|
return 0
|
|
fi
|
|
size=$(stat -c %s "$found")
|
|
done
|
|
return 1
|
|
}
|
|
|
|
|
|
# joyStage -- whether a screenshot now shows JOY's stage.
|
|
joyStage() {
|
|
joyShot "$JOY_WORK/probe.png" && python3 "$repo/scripts/lib/joyScreen.py" "$JOY_WORK/probe.png" "${JOY_SCREEN_ARGS[@]}" > /dev/null 2>&1
|
|
}
|
|
|
|
|
|
# The keyboard joysticks: Joystick1 (port 1) stick A, Joystick0 stick B.
|
|
{
|
|
for port in 1 0; do
|
|
stick=$([ "$port" = 1 ] && echo A || echo B)
|
|
printf '[Joystick%s]\nnJoystickMode = 2\nkUp = %s\nkDown = %s\nkLeft = %s\nkRight = %s\nkFire = %s\n\n' "$port" \
|
|
"$(hatariKey "$stick-up")" "$(hatariKey "$stick-down")" "$(hatariKey "$stick-left")" \
|
|
"$(hatariKey "$stick-right")" "$(hatariKey "$stick-button0")"
|
|
done
|
|
} > "$JOY_WORK/joy.cfg"
|
|
|
|
display=":$((RANDOM % 400 + 5100))"
|
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
|
xvfb=$!
|
|
sleep 1
|
|
mkdir -p "$JOY_WORK/shots" "$JOY_WORK/home"
|
|
(cd "$JOY_WORK" && exec env HOME="$JOY_WORK/home" XDG_CONFIG_HOME="$JOY_WORK/home" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
|
timeout -s KILL "$wall" "$HATARI" --configfile "$JOY_WORK/joy.cfg" --machine "$machine" --tos "$EMUTOS_IMG" \
|
|
--harddrive "$(dirname "$program")" --gemdos-drive C --auto "C:\\$(basename "$program")" \
|
|
--sound off --statusbar false --crop true --screenshot-dir "$JOY_WORK/shots" \
|
|
--cmd-fifo "$JOY_WORK/cmd.fifo") > "$JOY_WORK/hatari.log" 2>&1 &
|
|
guard=$!
|
|
hatari=$(joeyChild "$guard")
|
|
|
|
ready=0
|
|
for ((waited = 0; waited < readySeconds; waited += 2)); do
|
|
if [ -p "$JOY_WORK/cmd.fifo" ] && joyStage; then
|
|
ready=1
|
|
break
|
|
fi
|
|
joeyAlive "$guard" || break
|
|
sleep 2
|
|
done
|
|
if [ "$ready" != 1 ]; then
|
|
echo "$JOY_LABEL: FAIL - Hatari with JOY did not come up (log follows)" >&2
|
|
grep -v '^Screen dump saved' "$JOY_WORK/hatari.log" | tail -20 >&2
|
|
if [ -n "${JOY_SHOTS:-}" ] && [ -f "$JOY_WORK/probe.png" ]; then
|
|
mkdir -p "$JOY_SHOTS"
|
|
cp "$JOY_WORK/probe.png" "$JOY_SHOTS/not-ready.png"
|
|
fi
|
|
exit 1
|
|
fi
|
|
# Xvfb has no window manager: the keyboard goes to the focused window, and
|
|
# xdotool's keys (XTEST, which SDL takes) to the one that has it.
|
|
win=$(joeyFindWindow "$display" "$hatari")
|
|
if [ -n "$win" ]; then
|
|
DISPLAY="$display" xdotool windowfocus --sync "$win" 2> /dev/null || true
|
|
DISPLAY="$display" xdotool mousemove --window "$win" 20 20
|
|
fi
|
|
|
|
joyCheck "rest" "A=$JOY_REST B=$JOY_REST"
|
|
# Stick A's steps (the first seven), then B held: the mouse's port.
|
|
joyRunSteps "${JOY_STEPS_ONE_BUTTON[@]:0:7}" "B held: port 0 is the mouse's|B-left B-up B-button0|REST|REST"
|
|
|
|
DISPLAY="$display" xdotool key Escape
|
|
gone=0
|
|
for ((waited = 0; waited < exitSeconds; waited++)); do
|
|
sleep 1
|
|
if ! joyStage; then
|
|
gone=1
|
|
break
|
|
fi
|
|
done
|
|
[ "$gone" = 1 ] || joyFail "Escape did not end JOY"
|
|
|
|
joyResult "$JOY_LABEL" "$machine: port 1 every direction, the fire button and every release; port 0 left to the mouse; Escape ends JOY"
|