#!/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-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 (scripts/lib/dosboxJoy.sh, 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. It runs with square pixels (dvxSettings): the # 4:3 window repeats some rows, and the dots are read pixel for pixel. # tests/gate/joyScreen.c reads the WHOLE state of # BOTH sticks off each screenshot (scripts/lib/joyGate.sh's steps: 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 (the mapper binds them too in that run). # # Usage: scripts/verify-dvx-joy.sh # Env: JOY_SHOTS also keep the screenshots in this folder # Requires: DOSBox Staging, Xvfb, xdotool, 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/joyGate.sh" source "$repo/scripts/lib/dosboxJoy.sh" source "$repo/scripts/lib/dvxGate.sh" joeyNeed DOSBOX dosbox joeyNeed DVX_ROOT dvx JOY_LABEL=verify-dvx-joy 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 exitSeconds=10 # Escape until DVX reaps JOY # 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'" # DVX's keyboard joystick keys the second run holds (xdotool's names), and # their mapper bindings: Left and Space (SDL scancodes 80 and 44). keyboardKeys=(Left space) keyboardBinds='key_left "key 80" key_space "key 44"' dvxNeedBuild "$program" JOY_WORK=$(mktemp -d -t joeylib-dvx-joy.XXXXXX) guard="" dosbox="" xvfb="" # 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed". cleanup() { joeyStop "$dosbox" "$guard" "$xvfb" 2> /dev/null rm -rf "$JOY_WORK" } trap cleanup EXIT # boot RUN JOYTYPE [BINDS] -- DVX staged in $JOY_WORK/RUN with JOY its # desktop, DOSBox's joysticktype JOYTYPE and the joystick mapper (with BINDS # appended); sets guard, dosbox and DOSBOX_JOY_WINDOW. boot() { local dir="$JOY_WORK/$1" local waited dvxStage "$dir/c" "$(dvxAppDir "$program")" dvxSettings "$dir/c" "$program" "$DVX_SETTINGS_SQUARE" dvxDosboxConf "$dir/dosbox.conf" "$dir/c" mkdir -p "$dir/xdg" "$dir/shots" dosboxJoyMapper "$dir/mapper.txt" [ -z "${3:-}" ] || printf '%s\n' "$3" >> "$dir/mapper.txt" DOSBOX_JOY_SHOTS="$dir/shots" printf '\n[capture]\ncapture_dir = %s\n' "$dir/shots" >> "$dir/dosbox.conf" printf '\n[joystick]\njoysticktype = %s\n' "$2" >> "$dir/dosbox.conf" printf '%s\n' "${DOSBOX_JOY_SETTINGS[@]}" >> "$dir/dosbox.conf" printf '\n[sdl]\nmapperfile = %s\n' "$dir/mapper.txt" >> "$dir/dosbox.conf" XDG_CONFIG_HOME="$dir/xdg" DISPLAY="$DOSBOX_JOY_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" DOSBOX_JOY_WINDOW=$(joeyFindWindow "$DOSBOX_JOY_DISPLAY" "$dosbox") [ -n "$DOSBOX_JOY_WINDOW" ] || return 1 DISPLAY="$DOSBOX_JOY_DISPLAY" xdotool windowactivate --sync "$DOSBOX_JOY_WINDOW" 2> /dev/null || true return 0 fi joeyAlive "$guard" || return 1 sleep 1 done return 1 } # keyboardJoystick keydown|keyup -- hold or let go of DVX's keyboard # joystick keys. keyboardJoystick() { local k for k in "${keyboardKeys[@]}"; do DISPLAY="$DOSBOX_JOY_DISPLAY" xdotool "$1" --window "$DOSBOX_JOY_WINDOW" "$k" done } # stop -- end the DOSBox run. stop() { joeyStop "$dosbox" "$guard" 2> /dev/null dosbox="" guard="" } DOSBOX_JOY_DISPLAY=":$((RANDOM % 400 + 4300))" Xvfb "$DOSBOX_JOY_DISPLAY" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 & xvfb=$! sleep 1 # 1. Both sticks on the game port, moved through the mapper. if ! boot sticks 4axis; then echo "$JOY_LABEL: FAIL - DVX with JOY did not come up (DVX.LOG follows)" >&2 dvxDvxLog "$JOY_WORK/sticks/c" >&2 exit 1 fi dvxDvxLog "$JOY_WORK/sticks/c" 400 | grep -q "$twoSticksLog" || joyFail "DVX.LOG has no \"$twoSticksLog\": $(dvxDvxLog "$JOY_WORK/sticks/c" 400 | grep 'Joystick:' | tr '\n' ' ')" joyCheck "rest" "A=$JOY_REST B=$JOY_REST" joyRunSteps "${JOY_STEPS_TWO_STICKS[@]}" dosboxJoyTap escape for ((waited = 0; waited < exitSeconds; waited++)); do dvxDvxLog "$JOY_WORK/sticks/c" 400 | grep -qF "$reapedLog" && break sleep 1 done dvxDvxLog "$JOY_WORK/sticks/c" 400 | grep -qF "$reapedLog" || joyFail "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. if ! boot none disabled "$keyboardBinds"; then echo "$JOY_LABEL: FAIL - DVX with JOY and no joystick did not come up (DVX.LOG follows)" >&2 dvxDvxLog "$JOY_WORK/none/c" >&2 exit 1 fi dvxDvxLog "$JOY_WORK/none/c" 400 | grep -q "$keyboardLog" || joyFail "DVX.LOG has no \"$keyboardLog\" (the keyboard joystick): $(dvxDvxLog "$JOY_WORK/none/c" 400 | grep 'Joystick:' | tr '\n' ' ')" joyCheck "no joystick" "A=$JOY_ABSENT B=$JOY_ABSENT" keyboardJoystick keydown sleep "$JOY_PRESS_SECONDS" joyCheck "no joystick, keyboard joystick held" "A=$JOY_ABSENT B=$JOY_ABSENT" keyboardJoystick keyup stop joyResult "$JOY_LABEL" "both sticks, every direction, both buttons and every release; no joystick without one, the keyboard joystick left out"