Mouse track

Mouse

How LainDOS turns emulator PS/2 packets into DOS mouse-driver state: INT 33h calls, button edges, callbacks, mickey scaling, emulator capture, and the game-specific behavior this small driver is meant to cover.

From packet to callback

LainDOS does not load a TSR mouse driver. The kernel owns the INT 33h vector and the IRQ12 packet path directly, which keeps the implementation small and lets games see a mouse driver before any shell command or AUTOEXEC helper is involved.

01
Install
The kernel hooks INT 33hINT 33hThe conventional DOS mouse-driver interrupt implemented by LainDOS for game input. for DOS mouse calls and INT 74h for IRQ12 PS/2 packets.
02
Initialize
Boot code enables the auxiliary PS/2 device, resets defaults with F6, starts streaming with F4, and unmasks IRQ12.
03
Decode
Three-byte PS/2 packets update raw motion counters, scaled screen position, button state, and edge counts.
04
Serve
INT 33hINT 33hThe conventional DOS mouse-driver interrupt implemented by LainDOS for game input. calls poll pending PS/2 bytes first, then return position, motion, press/release, range, or ratio state.
05
Notify
If AX=000Ch registered a callback mask, movement and button edges call the game with DOS mouse registers.

INT 33h dispatch

Every mouse call polls hardware before serving state.

The INT 33hINT 33hThe conventional DOS mouse-driver interrupt implemented by LainDOS for game input. handler keeps a narrow implemented surface. It logs optional traces, polls any queued PS/2 bytes, then dispatches known AX values. Unknown functions return with a plain iret instead of pretending to implement a broad mouse-driver API.

Reset returns the installed-driver signature in AX and the two-button count in BX. It also resets ranges, position, motion counters, press/release counters, callback state, and the mickey/pixel ratio.

src/kernel/mouse.incNASM · 16-bit
1
19
20
23
29
33
35
37
41
43
45
47
48
53
54
64
68
74
75
76
int33_handler:
pusha
call mouse_poll_ps2
cmp ax, 0x0000
cmp ax, 0x0003
cmp ax, 0x0005
cmp ax, 0x0006
cmp ax, 0x0007
cmp ax, 0x000B
cmp ax, 0x000C
cmp ax, 0x000F
iret
.reset:
mov word [cs:mouse_x], 320
mov word [cs:mouse_y], 100
mov word [cs:mouse_callback_mask], 0
mov word [cs:mouse_ratio_x], 8
mov ax, 0xFFFF
mov bx, 2
iret

Position, ranges, and ratio

Coordinates are scaled and clamped inside the active range.

Position calls use the conventional BX/CX/DX register shape. AX=0004h stores CX/DX, clears scaling remainders, then clamps. Range calls accept either order for CX/DX and immediately clamp the current coordinate.

AX=000Fh changes the mickey/pixel ratio. LainDOS scales signed PS/2 deltas with delta * 8 / ratio and keeps signed remainders so slow movement still accumulates correctly.

src/kernel/mouse.incNASM · 16-bit
83
84
85
86
88
89
94
97
98
100
102
108
113
185
190
202
204
237
239
243
257
.get_pos:
mov bx, [cs:mouse_buttons]
mov cx, [cs:mouse_x]
mov dx, [cs:mouse_y]
.set_pos:
mov [cs:mouse_x], cx
call mouse_clamp_position
.set_x_range:
cmp cx, dx
xchg cx, dx
mov [cs:mouse_min_x], cx
.set_y_range:
mov [cs:mouse_min_y], cx
.set_ratio:
cmp cx, 2048
mov [cs:mouse_ratio_x], cx
mov word [cs:mouse_scale_rem_x], 0
mouse_clamp_position:
cmp ax, [cs:mouse_min_x]
cmp ax, [cs:mouse_max_x]
mov [cs:mouse_y], ax

Press, release, and raw motion queries

Edge queries latch data until the game consumes it.

Press and release calls take BX=0 for left or BX=1 for right. They return current button bits in AX, the latched edge count in BX, the last edge position in CX/DX, then clear that count.

Raw motion uses AX=000Bh. It returns accumulated signed mickeys in CX/DX and clears the motion counters, independent of the scaled/clamped screen position used by AX=0003h.

src/kernel/mouse.incNASM · 16-bit
119
130
132
133
135
146
157
159
160
162
173
174
175
176
177
.get_button_press:
.get_left_press:
mov bx, [cs:mouse_press_count_l]
mov cx, [cs:mouse_press_x_l]
mov word [cs:mouse_press_count_l], 0
.get_button_release:
.get_left_release:
mov bx, [cs:mouse_release_count_l]
mov cx, [cs:mouse_release_x_l]
mov word [cs:mouse_release_count_l], 0
.get_motion:
mov cx, [cs:mouse_motion_x]
mov dx, [cs:mouse_motion_y]
mov word [cs:mouse_motion_x], 0
mov word [cs:mouse_motion_y], 0

PS/2 packet path

QEMU/86Box mouse input arrives as standard three-byte PS/2 packets.

At boot the kernel enables the auxiliary device, sends mouse defaults and streaming commands, writes the controller command byte, unmasks IRQ12, and records whether PS/2 setup succeeded. The INT 33hINT 33hThe conventional DOS mouse-driver interrupt implemented by LainDOS for game input. path also polls the controller so tests can observe movement without waiting for an interrupt window.

The packet decoder rejects unsynchronized first bytes, sign-extends deltas, ignores overflowed axes, negates Y to match screen coordinates, adds raw motion counters, and applies scaled movement to the clamped cursor position.

src/kernel/mouse.incNASM · 16-bit
328
334
337
341
347
351
359
452
457
460
462
463
468
477
494
498
508
509
523
527
mouse_init_ps2:
mov al, 0xA8
mov al, 0xF6
mov al, 0xF4
mov al, 0x60
mov al, 0x47
mov byte [cs:mouse_ps2_enabled], 1
mouse_poll_ps2:
in al, 0x64
test al, 0x20
in al, 0x60
call mouse_ps2_byte
mouse_ps2_byte:
test al, 0x08
test byte [cs:mouse_packet0], 0x40
test byte [cs:mouse_packet0], 0x10
call mouse_scale_x
call mouse_apply_delta_x
neg ax
call mouse_apply_delta_y

Edges and callbacks

Movement and button edges can call back into a game.

Once a full packet has updated movement and button state, LainDOS sets event-mask bits for motion, left press/release, and right press/release. It records edge positions and updates current button bits before considering a callback.

Callback invocation is guarded against re-entry. The game receives AX=matched mask, BX=buttons, CX/DX=position, and SI/DI=movement deltas, then returns with RETF.

src/kernel/mouse.incNASM · 16-bit
530
531
536
537
546
547
557
567
574
575
579
581
591
592
595
597
605
615
616
619
620
mov bx, [cs:mouse_buttons]
mov ax, [cs:mouse_new_buttons]
or word [cs:mouse_event_mask], 0x0004
inc word [cs:mouse_release_count_l]
or word [cs:mouse_event_mask], 0x0002
inc word [cs:mouse_press_count_l]
or word [cs:mouse_event_mask], 0x0010
or word [cs:mouse_event_mask], 0x0008
mov [cs:mouse_buttons], ax
call mouse_invoke_callback
mouse_invoke_callback:
test ax, [cs:mouse_callback_mask]
and ax, [cs:mouse_callback_mask]
mov bx, [cs:mouse_buttons]
mov si, [cs:mouse_event_dx]
call far [cs:mouse_callback_off]
irq12_handler:
in al, 0x60
call mouse_ps2_byte
out 0xA0, al
out 0x20, al

Implemented narrowly on purpose

The mouse surface grows only when a target program or regression proves it needs more. Current support is enough for the known game paths without claiming complete Microsoft Mouse driver compatibility.

Unsupported INT 33hINT 33hThe conventional DOS mouse-driver interrupt implemented by LainDOS for game input. functions fall through as no-ops. There is no cursor bitmap drawing, cursor shape API, save/restore driver state, sensitivity query, light pen emulation, wheel data, or version string service.
Show/hide only update the visibility counter because current target games draw their own cursors or just need mouse state.
Button support is left and right only. The reset return advertises two buttons, matching the code path and tests.
The implementation is not a TSR mouse driver; it is part of the kernel and is cleared on reset or process termination where needed.