INT 16h AH=05h adds the word in CX to the tail of the keyboard buffer, CL as the ASCII byte and CH as the scan code, exactly where the keyboard handler would have put a real key. To everyone reading the keyboard afterwards, the BIOS functions and DOS alike, it is indistinguishable from typing. It returns AL = 0 on success and AL = 1 when the buffer has no room.

It came with the AT's BIOS (1986 revisions) and the enhanced keyboard. Typical uses: keyboard macro programs, a menu that "types" a command into COMMAND.COM before it exits, and test programs like the examples in this chapter, which feed themselves keys instead of waiting for a person. Here DOS reads two stored keys:

prints: OK
MOV AH, 05h ; BIOS: store a keystroke
MOV CX, 184Fh ; "O": scan code 18h, ASCII 4Fh
INT 16h ; AL = 0: stored
MOV AH, 05h
MOV CX, 254Bh ; "K": scan code 25h, ASCII 4Bh
INT 16h
MOV AH, 08h ; DOS: read a key without echo
INT 21h ; it gets the "O" from the BIOS buffer
MOV DL, AL
MOV AH, 02h ; DOS: print the character in DL
INT 21h
MOV AH, 08h
INT 21h ; and the "K"
MOV DL, AL
MOV AH, 02h ; DOS: print the character in DL
INT 21h

How many fit

The standard buffer has 16 word slots, but one always stays free so that a full ring can be told from an empty one: 15 keys. The example fills an empty buffer until AH=05h says no, then reads everything out again so nothing is left for the command line.

prints: 15 keys fit in the buffer
FLUSH1:
MOV AH, 01h ; BIOS: is a key waiting?
INT 16h
JZ EMPTY1 ; ZF = 1: the buffer is empty
MOV AH, 00h ; take the key out
INT 16h
JMP FLUSH1
EMPTY1:
XOR BX, BX ; keys stored so far
STORE1:
MOV AH, 05h
MOV CX, 3920h ; the space bar: scan 39h, ASCII 20h
INT 16h
TEST AL, AL ; 0 = stored, 1 = full
JNZ FULL1
INC BX
CMP BX, 20 ; a safety stop
JB STORE1
FULL1:
MOV AX, BX
CALL PDEC ; 15
MOV DX, OFFSET FIT1
MOV AH, 09h ; DOS: print the $-string at DS:DX
INT 21h
DRAIN1:
MOV AH, 01h ; empty the buffer again
INT 16h
JZ DONE1
MOV AH, 00h
INT 16h
JMP DRAIN1
DONE1:
FIT1 DB " keys fit in the buffer$"
PDEC PROC NEAR ; print AX in decimal; keeps every register
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV BX, 10
XOR CX, CX ; digits on the stack
DECD1:
XOR DX, DX
DIV BX ; AX = AX / 10, DX = the last digit
PUSH DX
INC CX
TEST AX, AX
JNZ DECD1
DECD2:
POP DX ; the digits come back first to last
ADD DL, "0"
MOV AH, 02h
INT 21h
LOOP DECD2
POP DX
POP CX
POP BX
POP AX
RET
PDEC ENDP

Pitfalls

Pass the scan code as well as the character: many programs look only at AH, and a key stored as 0041h is an "A" with no key behind it. Any word is accepted, including ones no keyboard produces. A program that stores keys and exits without reading them leaves them for COMMAND.COM to execute, which is the trick on purpose and a nuisance by accident. The original PC and XT BIOSes do not have this function (it returns with nothing done); there a program writes the word into the buffer and moves the tail pointer at 0040:001Ch itself, with interrupts disabled.