#!/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-iigs-mouse.sh - Headless gate for the IIgs's mouse buttons. Every # port has two, and the IIgs's ADB mouse has one: a left click made with # Ctrl held is the right button (src/core/input.c, mouseCtrlClick), read # through the asm snapshot and predicates (joeyDraw.s). Boots GS/OS under # MAME and launches the KEYS example off joey.2mg, which stamps the mouse's # state on its stage row 199: the sentinel 5A5A, then present, left, # right, and a zero nibble. Presses the emulated mouse button and Ctrl in # turn and reads the row straight out of SHR memory after each: # # start present, no button # click left # click let go none # Ctrl-click right, not left # Ctrl let go first still right (until the button is let go) # Ctrl-click let go none # click, then Ctrl left: Ctrl after the press changes nothing # both let go none # # Then Escape: KEYS must quit to the Finder. # # Usage: scripts/verify-iigs-mouse.sh # Requires: ./install.sh install iigs; build/iigs/bin/joey.2mg built # with the current KEYS (make iigs-disk). set -euo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) source "$repo/scripts/lib/joeyEnv.sh" joeyNeed MAME mame joeyNeed IIGS_ROMPATH iigsFirmware joeyNeed GSOS_SYSTEM_DISK iigsFirmware label=verify-iigs-mouse sys_disk=$GSOS_SYSTEM_DISK data_disk=$repo/build/iigs/bin/joey.2mg for f in "$sys_disk" "$data_disk"; do [ -f "$f" ] || { echo "$label: missing $f (run 'make iigs-disk')" >&2; exit 2; } done work=$(mktemp -d -t joeylib-mouse-verify.XXXXXX) trap 'rm -rf "$work"' EXIT cp "$sys_disk" "$work/boot.po" cp "$data_disk" "$work/joey.2mg" # Finder keystroke timeline as in verify-iigs-keys.sh. KEYS' mouse row is # stage row 199, the SHR bytes from $E1/2000 + 199*160 = $E1/9C60, two # nibble pixels a byte: 5A 5A, then the flags word (present 1000, left # 0100, right 0010). SCB 0 is $E1/9D00. Each step waits 90 frames (1.5 s) # before the row is read: KEYS restamps it at its next frame. cat > "$work/verify.lua" <<'LUA' local cpu = manager.machine.devices[":maincpu"] local mem = cpu.spaces["program"] local nat = manager.machine.natkeyboard local frame = 0 local idx = 1 local function field(port, name) local p = manager.machine.ioport.ports[port] if p == nil then return nil end return p.fields[name] end local key_cmd = field(":macadb:KEY3", "Command / Open Apple") local key_ctrl = field(":macadb:KEY3", "Control") local key_esc = field(":macadb:KEY3", "Esc") local button = field(":macadb:MOUSE0", "Mouse Button") local function press(f) if f then f:set_value(1) end end local function release(f) if f then f:set_value(0) end end local function report(tag) local row = "" for i = 0, 3 do row = row .. string.format("%02X", mem:read_u8(0xE19C60 + i)) end io.write(string.format("VERIFY-IIGS-MOUSE %s scb0=%02X row=%s\n", tag, mem:read_u8(0xE19D00), row)) io.flush() end local steps = { {3000, function() nat:post("J") end}, {3120, function() press(key_cmd) end}, {3126, function() nat:post("o") end}, {3180, function() release(key_cmd) end}, {3540, function() nat:post("KEYS") end}, {3660, function() press(key_cmd) end}, {3666, function() nat:post("o") end}, {3720, function() release(key_cmd) end}, {6600, function() report("start") end}, {6610, function() press(button) end}, {6700, function() report("click") end}, {6710, function() release(button) end}, {6800, function() report("clickLetGo") end}, {6810, function() press(key_ctrl) end}, {6840, function() press(button) end}, {6930, function() report("ctrlClick") end}, {6940, function() release(key_ctrl) end}, {7030, function() report("ctrlLetGoFirst") end}, {7040, function() release(button) end}, {7130, function() report("ctrlClickLetGo") end}, {7140, function() press(button) end}, {7170, function() press(key_ctrl) end}, {7260, function() report("clickThenCtrl") end}, {7270, function() release(button) end}, {7280, function() release(key_ctrl) end}, {7370, function() report("bothLetGo") end}, {7380, function() press(key_esc) end}, {7400, function() release(key_esc) end}, {9600, function() report("escape"); manager.machine:exit() end}, } emu.register_frame_done(function() frame = frame + 1 while idx <= #steps and frame >= steps[idx][1] do steps[idx][2]() idx = idx + 1 end end) LUA cd "$work" out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ timeout 300 "$MAME" apple2gs \ -rompath "$IIGS_ROMPATH" \ -flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \ -video none -sound none -nothrottle \ -autoboot_script "$work/verify.lua" &1) || true report=$(echo "$out" | grep -E '^VERIFY-IIGS-MOUSE ' || true) echo "$report" value() { echo "$report" | grep " $1 " | sed -E "s/.*$2=([0-9A-Fa-f]+).*/\1/" } fails=() [ "$(value start scb0)" = "00" ] || fails+=("KEYS never launched (SCB 0 $(value start scb0))") # step, the row it must show, what that means for check in "start 5A5A1000 present, no button" \ "click 5A5A1100 the left button" \ "clickLetGo 5A5A1000 no button" \ "ctrlClick 5A5A1010 the right button" \ "ctrlLetGoFirst 5A5A1010 the right button" \ "ctrlClickLetGo 5A5A1000 no button" \ "clickThenCtrl 5A5A1100 the left button" \ "bothLetGo 5A5A1000 no button"; do read -r step want meaning <<< "$check" got=$(value "$step" row) [ "$got" = "$want" ] || fails+=("$step: row ${got:-none}, want $want ($meaning)") done scb=$(value escape scb0) [ -n "$scb" ] && [ $((16#$scb)) -ge 128 ] || fails+=("Escape did not quit KEYS (SCB 0 ${scb:-none})") if [ ${#fails[@]} -eq 0 ]; then echo "$label: PASS (the mouse button is left, and right when Ctrl is held as it goes down, until it is let go; Escape quit KEYS)" exit 0 fi for f in "${fails[@]}"; do echo "$label: FAIL - $f" >&2 done exit 1