1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
#!/usr/bin/env bash
#
# scripts/test-mouse.sh — automated mouse round trip (cursor model).
#
# Since v0.5.0 the cursor task owns the PS/2 ring: mouse input shows up
# as *cursor position + buttons* on the screen, not as an event drain.
# This script boots QEMU headless, injects movement/buttons via the
# monitor, and verifies:
# - the `cursor` command reports a position that moves with `mouse_move`
# - button presses ride along (L while held)
# - `/dev/mouse` is a position device (one snapshot line)
# - the `mouse` watch command prints live changes and stops on ^C
# - a `mouse <id>` forward routes real events to a task's queue
#
# Usage: ./scripts/test-mouse.sh
# Env: OVMF, QEMU (same defaults as other test scripts)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"
OVMF="${OVMF:-/usr/share/edk2/x64/OVMF.4m.fd}"
QEMU="${QEMU:-qemu-system-x86_64}"
ESP_DIR="$PROJECT_DIR/esp"
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 -30 "$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
}
wait_qemu_exit() {
local i=0
while [ "$i" -lt $((${1:-30} * 10)) ]; do
kill -0 "$(cat "$QPID" 2>/dev/null)" 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 — run `cursor`, return "x y buttons" of the LAST occurrence.
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 --package rsdos --target x86_64-unknown-uefi
echo "==> 2/6 deploy to ESP"
mkdir -p "$ESP_DIR/EFI/BOOT"
cp target/x86_64-unknown-uefi/debug/rsdos.efi "$ESP_DIR/EFI/BOOT/BOOTX64.EFI"
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=fat:rw:"$ESP_DIR",format=raw \
-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"
grep -aq "cursor: task " "$LOG" || 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 "==> 5/6 buttons ride along; /dev/mouse is a position device"
qemu_hmp "mouse_button 1"
S2=$(cursor_state)
B2=$(echo "$S2" | awk '{print $3}')
[ "$B2" = "L--" ] || fail "left press not seen (got $B2)"
qemu_hmp "mouse_move -5 -3"
S3=$(cursor_state)
X3=$(echo "$S3" | awk '{print $1}')
Y3=$(echo "$S3" | awk '{print $2}')
B3=$(echo "$S3" | awk '{print $3}')
[ "$X3" -eq $((X1 - 5)) ] || fail "held move x wrong ($X1 -5 = $((X1 - 5)), got $X3)"
[ "$Y3" -eq $((Y1 - 3)) ] || fail "held move y wrong ($Y1 -3 = $((Y1 - 3)), got $Y3)"
[ "$B3" = "L--" ] || fail "button state dropped during held move (got $B3)"
qemu_hmp "mouse_button 0"
S4=$(cursor_state)
B4=$(echo "$S4" | awk '{print $3}')
[ "$B4" = "---" ] || fail "release not seen (got $B4)"
python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "cat /dev/mouse"
sleep 0.5
grep -aq "x=$X3 y=$Y3 buttons=---" "$LOG" || fail "/dev/mouse does not report position snapshot (x=$X3 y=$Y3)"
grep -aq "mouse: no events" "$LOG" && fail "/dev/mouse is still an event drain"
echo "==> 6/6 live watch + forwarding + poweroff"
python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \
"msgrecv" \
"mouse 3"
# Live watch running: a move must appear as a changed line.
qemu_hmp "mouse_move 3 2"
# Forwarding live: the same move must reach the msgrecv task (spawned as
# task 3; task 2 is the cursor task itself).
sleep 1
qemu_hmp "sendkey ctrl-c"
grep -aq "mouse: routing events to task 3" "$LOG" || fail "mouse 3 did not arm routing"
grep -aq "msgrecv\[3\]: Mouse(+" "$LOG" || fail "task 3 queue did not receive the routed Mouse event"
# Watch line: x/y/buttons format (any occurrence after watching started).
grep -aq "mouse: x=$((X3 + 3)) y=$((Y3 + 2)) buttons=---" "$LOG" || fail "watch did not print the moved cursor (x=$((X3+3)) y=$((Y3+2)))"
grep -aq "mouse: stopped" "$LOG" || fail "^C did not stop the mouse watch"
python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "poweroff"
echo "==> done: wait for shutdown + verify"
wait_qemu_exit 30 || fail "QEMU did not exit after poweroff"
echo ""
echo "PASS — mouse round trip OK (cursor task, /dev/mouse, watch, forward)"
echo " moves: +20,+7 · -5,-3 held · +3,+2 watch"
echo " path: IRQ12 → ring → cursor task → overlay + queue"
|