#!/usr/bin/env bash # # scripts/test-mouse.sh — automated PS/2 mouse round-trip test. # # Boots QEMU headless, injects mouse movement via QEMU monitor, # verifies the on-screen cursor tracks it, and tests the mouse # stream routing command. # # Env: OVMF, QEMU, SYSTEM_IMG # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$PROJECT_DIR" OVMF="${OVMF:-/usr/share/edk2/x64/OVMF.4m.fd}" QEMU="${QEMU:-qemu-system-x86_64}" SYSTEM_IMG="${SYSTEM_IMG:-/tmp/system.img}" QPID=/tmp/rsdos-mouse-qpid SOCK=/tmp/rsdos-mouse-qmon.sock LOG=/tmp/rsdos-mouse-serial.log QLOG=/tmp/rsdos-mouse-qemu.log trap 'kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true' EXIT fail() { echo "FAIL: $1" [ -f "$LOG" ] && tail -40 "$LOG" exit 1 } wait_log() { local i=0 while [ "$i" -lt $((${3:-60} * 10)) ]; do grep -q "$2" "$1" 2>/dev/null && return 0 sleep 0.1 i=$((i + 1)) done return 1 } qemu_hmp() { python3 - "$SOCK" "$1" <<'EOF' import socket, sys, time s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(sys.argv[1]) s.sendall((sys.argv[2] + "\n").encode()) time.sleep(0.4) EOF } cursor_state() { python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "cursor" sleep 0.5 sed -n 's/.*cursor: x=\([0-9]*\) y=\([0-9]*\) buttons=\([A-Za-z-]*\).*/\1 \2 \3/p' "$LOG" | tail -1 } echo "==> 1/6 build kernel (debug)" cargo build --release --package rsdos --target x86_64-unknown-uefi echo "==> 2/6 stage system image" "$SCRIPT_DIR/mksystem.sh" --force "$SYSTEM_IMG" echo "==> 3/6 boot QEMU (headless)" kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true sleep 1 rm -f "$SOCK" "$LOG" "$QEMU" -bios "$OVMF" \ -drive file="$SYSTEM_IMG",format=raw,if=none,id=disk \ -device ide-hd,drive=disk,bus=ide.0 \ -serial file:"$LOG" \ -monitor unix:"$SOCK",server,nowait \ -m 256M -machine q35 -cpu qemu64 -no-reboot -display none \ >"$QLOG" 2>&1 & echo $! >"$QPID" wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" grep -aq "ps2: mouse ready" "$LOG" || fail "mouse driver did not come up at boot" wait_log "$LOG" "cursor: task " 10 || fail "cursor task did not spawn" echo "==> 4/6 cursor position tracks injected movement" INIT=$(cursor_state) [ -n "$INIT" ] || fail "no initial cursor position" IX=$(echo "$INIT" | awk '{print $1}') IY=$(echo "$INIT" | awk '{print $2}') IB=$(echo "$INIT" | awk '{print $3}') echo " initial: x=$IX y=$IY buttons=$IB" [ "$IB" = "---" ] || fail "expected no buttons at boot, got $IB" qemu_hmp "mouse_move 20 7" S1=$(cursor_state) X1=$(echo "$S1" | awk '{print $1}') Y1=$(echo "$S1" | awk '{print $2}') [ "$X1" -eq $((IX + 20)) ] || fail "x did not move +20 (was $IX, now $X1)" [ "$Y1" -eq $((IY + 7)) ] || fail "y did not move +7 (was $IY, now $Y1)" echo " moved: x=$X1 y=$Y1 (delta +20,+7)" echo "==> 5/6 left button edge detection" qemu_hmp "mouse_button 1" S2=$(cursor_state) IB2=$(echo "$S2" | awk '{print $3}') [ "$IB2" = "L--" ] || [ "$IB2" = "L--" ] || fail "expected L--, got $IB2" qemu_hmp "mouse_button 0" S3=$(cursor_state) IB3=$(echo "$S3" | awk '{print $3}') [ "$IB3" = "---" ] || fail "expected --- after release, got $IB3" echo " button: $IB2 → $IB3" echo "==> 6/6 mouse stream routing to task 3" python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "msgrecv" sleep 1 python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "mouse 3" qemu_hmp "mouse_move 20 7" qemu_hmp "mouse_button 1" sleep 0.5 qemu_hmp "mouse_button 0" sleep 0.5 python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "ctrl-c" sleep 0.5 grep -aq "Mouse(+20,+7" "$LOG" || fail "routed mouse move not logged" echo "" echo "PASS — mouse round trip"