INT 16h AH=02h returns one byte, the shift flags the keyboard handler keeps at 0040:0017h. The low four bits say which modifier keys are held down right now; the high four say which toggles are on. It is the only BIOS way to see Shift, Ctrl or Alt on their own, since pressing them puts nothing in the buffer.
| Bit | Meaning when 1 |
|---|---|
| 0 | right Shift held |
| 1 | left Shift held |
| 2 | Ctrl held (either) |
| 3 | Alt held (either) |
| 4 | Scroll Lock on |
| 5 | Num Lock on |
| 6 | Caps Lock on |
| 7 | Insert on (toggled by the Ins key) |
The function only copies that byte, so the byte itself and the function always agree. The example tests the modifier bits, then flips the Insert bit in the BIOS data area by hand, reads the flags again, puts the bit back, and prints which bits changed. Insert has no LED, so the experiment is invisible to the keyboard.
MOV AH, 02h ; BIOS: get the shift flagsINT 16h ; AL = the flag byteMOV BL, AL ; keep it: printing changes ALMOV DX, OFFSET FREE1TEST BL, 0Fh ; bits 0-3: Shift, Ctrl, Alt held?JZ SHOW1MOV DX, OFFSET HELD1SHOW1:MOV AH, 09hINT 21hMOV AX, 40hMOV ES, AX ; ES = the BIOS data areaXOR BYTE PTR ES:[17h], 80h ; flip the Insert flag by handMOV AH, 02hINT 16h ; AL = the flags as they are nowXOR BYTE PTR ES:[17h], 80h ; and flip it backXOR AL, BL ; 1 bits: what changedMOV BL, ALMOV DX, OFFSET DIFF1MOV AH, 09h ; DOS: print the $-string at DS:DXINT 21hMOV AL, BLCALL PHEX2 ; 80: only bit 7FREE1 DB "no Shift, Ctrl or Alt held", 13, 10, "$"HELD1 DB "a Shift, Ctrl or Alt key is down", 13, 10, "$"DIFF1 DB "changed bits: $"PHEX2 PROC NEAR ; print AL as two hex digits; keeps every registerPUSH AXPUSH CXPUSH DXMOV DH, AL ; keep the byteMOV CL, 4SHR AL, CL ; the high nibbleCALL HEXN1MOV AL, DHAND AL, 0Fh ; the low nibbleCALL HEXN1POP DXPOP CXPOP AXRETHEXN1:ADD AL, "0"CMP AL, "9"JBE HEXN2ADD AL, 7 ; past "9": "A".."F"HEXN2:MOV DL, ALMOV AH, 02hINT 21hRETPHEX2 ENDP
More detail
The byte does not say which Ctrl or Alt is held. Enhanced keyboards answer that through INT 16h AH=12h: AL is the same byte, AH adds left Ctrl (bit 0), left Alt (bit 1), right Ctrl (bit 2), right Alt (bit 3) and SysRq (bit 7), taken from 0040:0018h and 0040:0096h. Remember the toggles are states, not keys: bit 6 says Caps Lock is on, not that the key is being pressed.
Pitfalls
Writing to 0040:0017h changes what the BIOS believes. Setting the Caps Lock bit really switches Caps Lock on: the next letters come out in capitals, and many BIOSes (DOSBox-X's among them) notice the change on the next timer tick and update the keyboard LEDs. Programs used that to force Num Lock on at startup. If you change the byte, change it back.