#!/usr/bin/env bash # verify-dvx-joy.sh - Joystick gate for DVX: both sticks, every direction, # both buttons and every release, driven through DOSBox Staging's emulated # game port and read back from what examples/joy draws; and no joystick at # all when the game port is empty, though DVX then offers its keyboard # joystick. # # The port (src/dvx/input.c) reads DVX's joystick library: its first two # devices that are real joysticks, each axis -32768..32767 >> 8, clamped to # -127..127. DOSBox Staging's joysticktype 4axis puts four axes and four # buttons on the game port with no host joystick, which DVX's game port # driver (two-stick mode, its default) reads as two sticks, A and B, exactly # as a Y cable would give it. A mapper file binds host keys to DOSBox's # joystick events (as DVX's own tests do, src/test/dos/joyDrive.py), and # xdotool holds them; the raw screenshot is bound to a key of its own, so no # Ctrl reaches the program. With no host joystick nothing else moves the # stick: it rests in the middle, buttons up. # # JOY (examples/joy) draws, for each stick, a dot (in the middle at rest, # grey; green when moved), two button squares and a patch that says whether # the port reports a stick; scripts/lib/joyScreen.py reads the WHOLE state of # BOTH sticks off each screenshot: each direction of each stick alone, two # diagonals each, each button alone and both together, with a release after # every press so a latched line shows up, and a step holding different # lines on both sticks at once so crosstalk shows up too. Axes are judged # as DVX's own tests judge them, beyond half way: an uncalibrated DVX axis # ranges itself as it moves, so a full push reads a little short of the # end. Escape then ends JOY, which must leave DVX cleanly ("reaped"). # # Second run, joysticktype disabled: DVX finds no game port stick and falls # back to its keyboard joystick (the arrows, LCtrl, Space...), which the # port leaves out, as every port reports no joystick then: JOY must show # both sticks disconnected at rest while the keyboard joystick's Left and # Space are held. # # Usage: scripts/verify-dvx-joy.sh # Env: DVX_JOY_SHOTS also copy the screenshots into this folder # Requires: DOSBox Staging, Xvfb, xdotool, python3 + PIL, the dvx component; # the joy 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=joy wall=240 # hard cap on each DOSBox run, whatever happens to this script readySeconds=60 # DVX boot until its shell reports ready settleSeconds=3 # then JOY opening its window and taking the focus pressSeconds=1 # keys held (or let go), then DVX's poll, JOY and the compositor catching up shotSeconds=5 # the screenshot key until DOSBox has written the file exitSeconds=10 # Escape until DVX reaps JOY rest="10000" # a stick connected, in the middle, buttons up absent="00000" # no stick # DVX.LOG when the game port gives two sticks, and when it has none and the # keyboard joystick stands in. twoSticksLog="Joystick: 2 device(s) found" keyboardLog="Joystick: keyboard0" reapedLog="Shell: reaped app 'Joy'" # Host key (xdotool name), its SDL scancode, and the DOSBox mapper event it # drives. Stick A = the game port's first two axes and buttons, stick B = # its last two (DOSBox 4axis: the first joystick's axes 2-3, buttons 2-3). mapKeys=( "j 13 jaxis_0_0-" "l 15 jaxis_0_0+" "i 12 jaxis_0_1-" "k 14 jaxis_0_1+" "a 4 jbutton_0_0" "s 22 jbutton_0_1" "f 9 jaxis_0_2-" "h 11 jaxis_0_2+" "t 23 jaxis_0_3-" "g 10 jaxis_0_3+" "q 20 jbutton_0_2" "w 26 jbutton_0_3" "F11 68 hand_rawshot" "Escape 41 key_esc" ) shotKey=F11 # name, keys held, stick A's state, stick B's state (joyScreen.py's # characters: connected, X, Y, button 0, button 1). steps=( "A left|j|1-000|$rest" "A right|l|1+000|$rest" "A up|i|10-00|$rest" "A down|k|10+00|$rest" "A up-left|j i|1--00|$rest" "A down-right|l k|1++00|$rest" "A button 0|a|10010|$rest" "A button 1|s|10001|$rest" "A both buttons|a s|10011|$rest" "B left|f|$rest|1-000" "B right|h|$rest|1+000" "B up|t|$rest|10-00" "B down|g|$rest|10+00" "B up-right|h t|$rest|1+-00" "B down-left|f g|$rest|1-+00" "B button 0|q|$rest|10010" "B button 1|w|$rest|10001" "B both buttons|q w|$rest|10011" "A left+button 1, B right+button 0|j s h q|1-001|1+010" ) dvxNeedBuild "$program" work=$(mktemp -d -t joeylib-dvx-joy.XXXXXX) guard="" dosbox="" xvfb="" win="" fails="" checked=0 # 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 # boot RUN JOYTYPE [MAPPER] -- DVX staged in $work/RUN with JOY its desktop, # DOSBox's joysticktype JOYTYPE and, if given, its mapper file; sets guard, # dosbox and win. boot() { local dir="$work/$1" local waited dvxStage "$dir/c" "$(dvxAppDir "$program")" dvxDosboxConf "$dir/dosbox.conf" "$dir/c" printf '\n[capture]\ncapture_dir = %s\ndefault_image_capture_formats = raw\n' "$dir/shots" >> "$dir/dosbox.conf" printf '\n[joystick]\njoysticktype = %s\n' "$2" >> "$dir/dosbox.conf" if [ -n "${3:-}" ]; then printf '\n[sdl]\nmapperfile = %s\n' "$3" >> "$dir/dosbox.conf" fi 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 sleep "$settleSeconds" win=$(joeyFindWindow "$display" "$dosbox") [ -n "$win" ] || return 1 DISPLAY="$display" xdotool windowactivate --sync "$win" 2> /dev/null || true return 0 fi joeyAlive "$guard" || return 1 sleep 1 done return 1 } # check RUN NAME HOTKEY WANT -- a screenshot (HOTKEY, e.g. F11 or ctrl+F5) # as $work/RUN/NAME.png, whose two sticks must read WANT ("A=... B=..."). check() { local dir="$work/$1" local before local png="" local got local waited before=$(ls "$dir/shots" | wc -l) DISPLAY="$display" xdotool key --window "$win" "$3" for ((waited = 0; waited < shotSeconds * 10; waited++)); do if [ "$(ls "$dir/shots" | wc -l)" -gt "$before" ]; then sleep 0.5 png=$(ls -t "$dir"/shots/*.png | head -1) break fi sleep 0.1 done checked=$((checked + 1)) if [ -z "$png" ]; then fail "$2: no screenshot" return fi mv "$png" "$dir/$(printf '%02d' "$checked") ${2}.png" got=$(python3 "$repo/scripts/lib/joyScreen.py" "$dir/$(printf '%02d' "$checked") ${2}.png" --scale 1) echo "verify-dvx-joy: $2: $got" [ "${got% scale=*}" = "$4" ] || fail "$2: read $got, want $4" } # fail WHAT -- note a failed check; the gate goes on to report the rest. fail() { echo "verify-dvx-joy: FAIL - $1" fails="${fails:+$fails; }$1" } # keys DOWN|UP KEY... -- press or let go of the keys. keys() { local action="key$1" local k shift for k in "$@"; do DISPLAY="$display" xdotool "$action" --window "$win" "$k" done } # stop -- end the DOSBox run. stop() { joeyStop "$dosbox" "$guard" 2> /dev/null dosbox="" guard="" } display=":$((RANDOM % 400 + 4300))" Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 & xvfb=$! sleep 1 # 1. Both sticks on the game port, moved through the mapper. mapper="$work/mapper.txt" for entry in "${mapKeys[@]}"; do read -r name code event <<< "$entry" printf '%s "key %s"\n' "$event" "$code" done > "$mapper" if ! boot sticks 4axis "$mapper"; then echo "verify-dvx-joy: FAIL - DVX with JOY did not come up (DVX.LOG follows)" >&2 dvxDvxLog "$work/sticks/c" >&2 exit 1 fi dvxDvxLog "$work/sticks/c" 400 | grep -q "$twoSticksLog" || fail "DVX.LOG has no \"$twoSticksLog\": $(dvxDvxLog "$work/sticks/c" 400 | grep 'Joystick:' | tr '\n' ' ')" check sticks "rest" "$shotKey" "A=$rest B=$rest" for step in "${steps[@]}"; do IFS='|' read -r name held wantA wantB <<< "$step" read -r -a heldKeys <<< "$held" keys down "${heldKeys[@]}" sleep "$pressSeconds" check sticks "$name" "$shotKey" "A=$wantA B=$wantB" keys up "${heldKeys[@]}" sleep "$pressSeconds" check sticks "$name released" "$shotKey" "A=$rest B=$rest" done keys down Escape sleep 0.3 keys up Escape for ((waited = 0; waited < exitSeconds; waited++)); do dvxDvxLog "$work/sticks/c" 400 | grep -qF "$reapedLog" && break sleep 1 done dvxDvxLog "$work/sticks/c" 400 | grep -qF "$reapedLog" || fail "Escape did not end JOY (no \"$reapedLog\" in DVX.LOG)" stop # 2. No stick on the game port: DVX's keyboard joystick stands in, and the # port must leave it out. Plain DOSBox keys (no mapper), the default # screenshot hotkey. if ! boot none disabled; then echo "verify-dvx-joy: FAIL - DVX with JOY and no joystick did not come up (DVX.LOG follows)" >&2 dvxDvxLog "$work/none/c" >&2 exit 1 fi dvxDvxLog "$work/none/c" 400 | grep -q "$keyboardLog" || fail "DVX.LOG has no \"$keyboardLog\" (the keyboard joystick): $(dvxDvxLog "$work/none/c" 400 | grep 'Joystick:' | tr '\n' ' ')" check none "no joystick" ctrl+F5 "A=$absent B=$absent" keys down Left space sleep "$pressSeconds" check none "no joystick, keyboard joystick held" ctrl+F5 "A=$absent B=$absent" keys up Left space stop if [ -n "${DVX_JOY_SHOTS:-}" ]; then mkdir -p "$DVX_JOY_SHOTS" cp "$work"/sticks/*.png "$work"/none/*.png "$DVX_JOY_SHOTS/" fi if [ -n "$fails" ]; then echo "verify-dvx-joy: FAIL - $fails" exit 1 fi echo "verify-dvx-joy: PASS ($checked screenshots: both sticks, every direction, both buttons and every release; no joystick without one, the keyboard joystick left out)"