223 lines
7.5 KiB
Bash
Executable file
223 lines
7.5 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-amiga-joy.sh - Joystick gate for the Amiga: both ports, every
|
|
# direction, both buttons and every release, driven through FS-UAE's
|
|
# joystick ports and read back from what examples/joy draws.
|
|
#
|
|
# The port (src/amiga/input.c) reads both ports through lowlevel.library's
|
|
# ReadJoyPort: port 1, the joystick port, is JOYSTICK_0 (stick A) and port 0,
|
|
# the mouse port, JOYSTICK_1 (stick B), each a stick while lowlevel.library
|
|
# takes it for one; the red button is button 0 and the blue (a two-button
|
|
# stick's second) button 1. lowlevel.library cannot tell an idle stick from
|
|
# an empty port, so it reports a stick only once it has moved: both must
|
|
# read unplugged until a tap on each, connected after. Kickstart 3.1 has no lowlevel.library in ROM, so
|
|
# the boot drive carries Workbench's (component amigaLowlevel). FS-UAE's
|
|
# ports are both in joystick mode, every line of each on a host key of its
|
|
# own (keyboard_key_*), and xdotool holds the keys; FS-UAE's screenshots
|
|
# (its screenshot action, on F11) go through scripts/lib/joyScreen.py
|
|
# (scripts/lib/joyGate.sh). Escape must then end JOY.
|
|
#
|
|
# Usage: scripts/verify-amiga-joy.sh
|
|
# Env: JOY_SHOTS also keep the screenshots in this folder
|
|
# Requires: FS-UAE, the Kickstart, lowlevel.library (install emulator:amiga),
|
|
# Xvfb, xdotool, python3 + PIL; build/amiga/bin/Joy (make amiga).
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
source "$repo/scripts/lib/joyGate.sh"
|
|
joeyNeed FSUAE fsuae
|
|
source "$repo/scripts/lib/amigaGate.sh"
|
|
amigaMachine
|
|
joeyNeed AMIGA_LOWLEVEL_LIBRARY amigaLowlevel
|
|
|
|
JOY_LABEL=verify-amiga-joy
|
|
program="$repo/build/amiga/bin/Joy"
|
|
wall=300 # hard cap on FS-UAE, whatever happens to this script
|
|
readySeconds=90 # AmigaDOS booting and JOY drawing
|
|
focusSeconds=30 # the full-screen window taking the keyboard
|
|
shotSeconds=5 # the screenshot key until FS-UAE has written the file
|
|
exitSeconds=10 # Escape until JOY's screen is gone
|
|
shotKey=F11
|
|
|
|
[ -f "$program" ] || { echo "$JOY_LABEL: build Joy first (make amiga)" >&2; exit 2; }
|
|
|
|
JOY_WORK=$(mktemp -d -t joeylib-amiga-joy.XXXXXX)
|
|
guard=""
|
|
fsuae=""
|
|
xvfb=""
|
|
display=""
|
|
cleanup() {
|
|
joeyStop "$fsuae" "$guard" "$xvfb" 2> /dev/null
|
|
rm -rf "$JOY_WORK"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Control, its host key (xdotool's name, and FS-UAE's in lower case) and
|
|
# the FS-UAE action it drives. Stick A is Amiga port 1, stick B port 0.
|
|
keys=(
|
|
"A-left j action_joy_1_left" "A-right l action_joy_1_right"
|
|
"A-up i action_joy_1_up" "A-down k action_joy_1_down"
|
|
"A-button0 a action_joy_1_fire_button" "A-button1 s action_joy_1_2nd_button"
|
|
"B-left f action_joy_0_left" "B-right h action_joy_0_right"
|
|
"B-up t action_joy_0_up" "B-down g action_joy_0_down"
|
|
"B-button0 q action_joy_0_fire_button" "B-button1 w action_joy_0_2nd_button"
|
|
"shot $shotKey action_screenshot"
|
|
)
|
|
|
|
|
|
# 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
|
|
}
|
|
|
|
|
|
# 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 "$@"
|
|
}
|
|
|
|
|
|
# FS-UAE writes more than one picture of each screenshot; the one cropped
|
|
# to the Amiga's display is the stage's. It 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 '*crop*.png' | wc -l)
|
|
hold keydown shot
|
|
sleep 0.3
|
|
hold keyup shot
|
|
deadline=$((SECONDS + shotSeconds))
|
|
while [ "$SECONDS" -lt "$deadline" ]; do
|
|
sleep 0.2
|
|
[ "$(find "$JOY_WORK/shots" -name '*crop*.png' | wc -l)" -gt "$before" ] || continue
|
|
found=$(ls -t "$JOY_WORK"/shots/*crop*.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" > /dev/null 2>&1
|
|
}
|
|
|
|
|
|
mkdir -p "$JOY_WORK/hd/s" "$JOY_WORK/hd/Libs" "$JOY_WORK/shots"
|
|
cp "$program" "$JOY_WORK/hd/"
|
|
cp "$AMIGA_LOWLEVEL_LIBRARY" "$JOY_WORK/hd/Libs/lowlevel.library"
|
|
printf ':%s\n' "$(basename "$program")" > "$JOY_WORK/hd/s/startup-sequence"
|
|
mapping=()
|
|
for entry in "${keys[@]}"; do
|
|
read -r _ host action <<< "$entry"
|
|
mapping+=("--keyboard_key_${host,,}=$action")
|
|
done
|
|
|
|
display=":$((RANDOM % 400 + 5500))"
|
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
|
xvfb=$!
|
|
sleep 1
|
|
# --fullscreen=1: under a WM-less Xvfb SDL takes the keyboard only once its
|
|
# window has the focus, which a full-screen window takes by itself.
|
|
DISPLAY="$display" ALSOFT_DRIVERS=null timeout -s KILL "$wall" "$FSUAE" \
|
|
"${AMIGA_MACHINE_ARGS[@]}" --fast_memory=2048 \
|
|
--hard_drive_0="$JOY_WORK/hd" --hard_drive_0_label=JOEYLIB \
|
|
--fullscreen=1 --initial_input_grab=1 --floppy_drive_speed=800 \
|
|
--base_dir="$JOY_WORK/fs-uae" --screenshots_output_dir="$JOY_WORK/shots" \
|
|
--joystick_port_0=none --joystick_port_0_mode=joystick \
|
|
--joystick_port_1=none --joystick_port_1_mode=joystick \
|
|
"${mapping[@]}" > "$JOY_WORK/fs-uae.log" 2>&1 &
|
|
guard=$!
|
|
fsuae=$(joeyChild "$guard")
|
|
|
|
# XTEST keys land in whatever window has the focus, which the full-screen
|
|
# window latches on its own schedule.
|
|
win=""
|
|
for ((waited = 0; waited < focusSeconds; waited++)); do
|
|
[ -n "$win" ] || win=$(DISPLAY="$display" xdotool search --name -- "FS-UAE" 2> /dev/null | head -1)
|
|
if [ -n "$win" ] && [ "$(DISPLAY="$display" xdotool getwindowfocus 2> /dev/null)" = "$win" ]; then
|
|
break
|
|
fi
|
|
[ -n "$win" ] && DISPLAY="$display" xdotool windowfocus --sync "$win" 2> /dev/null
|
|
sleep 1
|
|
done
|
|
|
|
ready=0
|
|
for ((waited = 0; waited < readySeconds; waited += 2)); do
|
|
if joyStage; then
|
|
ready=1
|
|
break
|
|
fi
|
|
joeyAlive "$guard" || break
|
|
sleep 2
|
|
done
|
|
if [ "$ready" != 1 ]; then
|
|
echo "$JOY_LABEL: FAIL - FS-UAE with JOY did not come up" >&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
|
|
|
|
joyCheck "before either stick moves" "A=$JOY_ABSENT B=$JOY_ABSENT"
|
|
joyPress A-left B-left
|
|
sleep "$JOY_PRESS_SECONDS"
|
|
joyRelease A-left B-left
|
|
sleep "$JOY_PRESS_SECONDS"
|
|
joyCheck "rest" "A=$JOY_REST B=$JOY_REST"
|
|
joyRunSteps "${JOY_STEPS_TWO_STICKS[@]}"
|
|
|
|
DISPLAY="$display" xdotool keydown Escape
|
|
sleep 0.5
|
|
DISPLAY="$display" xdotool keyup 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" "both ports unplugged until moved, then every direction, both buttons and every release; Escape ends JOY"
|