Skip to content
FreeDOSDeep Dive Published Updated 8 min readViews unavailable

Programming the DOS Mouse API: INT 33h State, Coordinates, and Events

Build a robust DOS application around INT 33h: probe safely, interpret text and graphics coordinates, track button edges, and use callbacks carefully.

DOS mouse support is a useful example of a compatibility interface that is supplied by a resident driver rather than by the kernel itself. An application normally calls the mouse software interrupt, INT 33h, and expects an installed driver such as CuteMouse or a Microsoft-compatible driver to translate device-specific input into a common set of operations. That separation is why a DOS program can support serial, bus, PS/2, or emulated pointing devices without implementing each hardware protocol itself. It is also why the program must verify the interface and avoid assuming that every driver implements every extension.

This article is about the application-facing contract: safe presence checks, the core state and event functions, coordinate interpretation, callback design, and compatibility testing. It is not a driver-installation guide; the resident-memory and startup choices for CuteMouse are covered in the separate configuration walkthrough.

Treat INT 33h as an optional driver API

The interrupt vector is not guaranteed to point to a usable mouse handler on every machine. The interrupt list warns that an uninitialized vector on older systems may be zero or may point at an IRET instruction, so blindly calling a function is not a universally safe installation test. In a DOS program, the conventional first step is to ask DOS for the current interrupt vector with INT 21h, AH=35h, AL=33h, then apply the target’s known-safe vector checks before calling the driver. The exact check must be appropriate for the environment; do not read or execute an arbitrary far address as though it were a valid handler.

Once the vector has been checked, function AX=0000h is the standard reset-and-status operation. A compatible driver reports AX=FFFFh when installed and AX=0000h when it is not, and returns button-count information in BX. This call is also a reset, not a purely observational query: it can restore driver defaults and may affect application-visible mouse state. Call it at deliberate initialization time, not repeatedly as a polling primitive. If startup code must preserve preexisting settings, document that reset behavior and test it with the exact driver and version you support.

The interface has a long history of vendor and version-specific functions. The low-numbered MS Mouse-compatible calls are the sensible portability baseline. Wheel counters, alternate handlers, advanced sensitivity controls, state-save calls, and vendor-specific ranges need an explicit capability check or a tested driver contract. The fact that a function number appears in a reference does not mean every INT 33h implementation accepts it.

Poll current state without confusing coordinates

AX=0003h returns a button-state bit mask in BX, a horizontal coordinate in CX, and a vertical coordinate in DX. Bit zero represents the left button, bit one the right button, and some drivers also use bit two for a middle button. These are current-state bits: if the program polls only once per frame, it can miss a quick press and release that happens between polls.

The coordinate units depend on the active display mode. In text modes, the returned row and column are cell-oriented, commonly in multiples of an 8-by-8 pixel cell. In graphics modes, the driver exposes screen coordinates according to its current scaling and mode. Do not label the values “pixels” in a mode-independent API. Keep the raw driver coordinates separate from the application’s logical coordinates, and convert at the display boundary. That one decision prevents an 80-column text application from treating a reported column as an absolute framebuffer pixel, or a graphics game from assuming text-cell quantization has vanished on every adapter.

The cursor’s logical range is separate from its current position. Functions AX=0007h and AX=0008h set horizontal and vertical limits; text-mode ranges are quantized according to character cells on common drivers. AX=0004h positions the cursor. A program changing screen mode or page should re-evaluate the range and visible-cursor behavior instead of assuming coordinates from the previous mode remain meaningful. If the application draws directly into a graphics surface, hide the software cursor while changing pixels underneath it, then show it again after the update. The driver also offers an update-region call, but its exact effect should be tested with the display mode and mouse driver in use.

For applications where clicks matter more than instantaneous state, AX=0005h and AX=0006h return press and release counts for a selected button, together with the location of the most recent event. These calls preserve information that a slow polling loop could otherwise lose. They are still bounded counters with implementation-specific limits, not an unbounded event log. A game should choose a consistent policy: use current state for continuous movement and use press/release data for edge-triggered actions such as menu selection. Do not mix both paths without deciding which one consumes or clears event history.

Separate physical motion from cursor movement

The driver can report movement in “mickeys,” the smallest movement increments understood by the mouse hardware, through AX=000Bh. Positive values indicate movement down and right. A mickey is not a pixel, and the ratio between physical movement and cursor displacement is configurable. The classic AX=000Fh call sets the number of mickeys corresponding to eight pixels horizontally and vertically; its documented defaults differ between the axes. If an application only needs the on-screen pointer, use the position call. If it needs relative motion, such as a first-person look control or a drawing tool that accumulates deltas, read movement counters and establish a tested ratio rather than deriving physical motion from absolute coordinates.

Counter reads are interval measurements: the returned movement is accumulated since the previous read, so the timing and frequency of reads affect what each sample represents. Keep one subsystem responsible for consuming the counters. If both an input module and a rendering module read them independently, the first reader can clear movement that the second expected to observe. Test slow and fast loops, boundary behavior, acceleration settings, and both axes on each supported driver.

Use event callbacks as interrupt-adjacent code

Function AX=000Ch registers a far callback at ES:DX and takes an event mask in CX. The classic mask selects movement and button press/release events. The callback receives the event condition mask in AX, button state in BX, cursor coordinates in CX and DX, and horizontal and vertical mickey counts in SI and DI. Text-mode coordinates remain cell-oriented. Some driver documentation historically swapped the meaning of the delta registers, so test the target driver rather than relying on a mismatched reference table.

Callbacks are useful when a program cannot afford to poll at a high rate, but they create a more demanding lifetime and execution-context problem. The callback code and any data it touches must remain resident and valid for as long as the mouse driver can call it. Register the handler only after initializing its state, and unregister or replace it before freeing that state or returning to DOS. A conservative design makes the callback short: copy the event into a small application-owned queue, set a flag, and let the main loop perform drawing, file access, or other complex work. This is a defensive architecture recommendation, not a claim that every mouse driver invokes callbacks under an identical interrupt or reentrancy model.

The callback mask is not a substitute for checking the driver version. The standard AX=000Ch interface has event and register conventions, while later functions such as alternate user handlers have their own version requirements and masks. Keep a minimal callback path, guard against queue overflow, and make the main loop robust if several events arrive before it drains the queue. If the application does not need event callbacks, polling is often simpler to reason about and easier to test.

Make cursor visibility part of rendering correctness

The mouse driver owns the software cursor’s drawing and restoration behavior. A program that writes directly to video memory without hiding the cursor can leave stale pixels, corrupt the cursor background, or produce trails when the driver later restores an old image. The usual transaction is: hide the cursor, update the affected display region, then show the cursor again. If a routine can exit early, route every path through the matching show operation. Multiple hide calls may require multiple show calls, so do not scatter cursor ownership across unrelated routines without an explicit counter or single owner.

On mode changes, page switches, or graphics-library transitions, reinitialize the assumptions the application owns: screen dimensions, logical scaling, cursor bounds, and whether the driver cursor is visible. A virtual machine can reproduce the API while having different timing and coordinate scaling than physical hardware. Record the emulator model, video mode, mouse-driver name and version, and application loop cadence when diagnosing a rendering defect.

Validate behavior with a small compatibility matrix

Before calling the application production-ready, test at least the following with each driver and display target:

  1. An absent driver and an installed driver, including safe vector detection before the reset call.
  2. Left and right current-state bits, short press/release events, and the behavior of press counters when the main loop is delayed.
  3. Text mode, each supported graphics mode, screen edges, configured cursor ranges, and page changes.
  4. Relative motion in both axes at slow and fast loop rates, including whether a second subsystem accidentally consumes the same counter.
  5. Cursor hide/show around redraws, early-return paths, and repeated updates.
  6. Callback registration, queue overflow, unregistration, and exit while the driver remains loaded.

The objective is not to prove that every historic mouse driver is identical. It is to define which standard calls your application depends on, keep vendor extensions optional, and preserve a reproducible test matrix for the drivers and emulators you actually support. If the application works only with one extension, name that dependency rather than presenting it as a universal DOS mouse guarantee.

Related:

Sources:

Comments