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
55
107
112
113
123
127
135
136
84
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
147
148
149
150
152
153
158
161
162
164
166
172
177
249
254
266
155
301
303
307
321
.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
183
194
196
197
118
210
221
223
224
120
237
238
239
240
241
.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
392
401
404
408
414
418
426
519
463
527
454
530
535
544
561
565
575
576
590
594
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 recursive mouse callbacks. The IRQ12 handler acknowledges both PICs before processing, and the callback itself runs with interrupts enabled so timer ticks and keystrokes keep flowing during long game callbacks. The game receives AX=matched mask, BX=buttons, CX/DX=position, and SI/DI=movement deltas, then returns with RETF. INT 21hINT 21hThe main DOS API interrupt. Programs select services such as open, read, EXEC, and exit with AH. calls made from the callback are rejected so DOS file I/O state is not re-entered.

src/kernel/mouse.incNASM · 16-bit
597
598
603
604
613
614
624
634
641
642
646
648
658
597
664
665
688
689
690
704
714
717
718
720
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_motion_x]
mov di, [cs:mouse_motion_y]
sti
call far [cs:mouse_callback_off]
cli
irq12_handler:
in al, 0x60
out 0xA0, al
out 0x20, al
call mouse_ps2_byte

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.