VECTORS       SEGMENT	AT   0H
  ORG	      9H*4
  KEYBOARD_INT_VECTOR	LABEL	  DWORD
  ORG	      16H*4
  KEYBOARD_IO_VECTOR	LABEL	  DWORD
VECTORS       ENDS
;
ROM_BIOS_DATA SEGMENT	AT   40H
  ORG	      17H
  KBD_FLAG		DB   ?
  ORG	      1AH
  ROM_BUFFER_HEAD	DW   ?
  ROM_BUFFER_TAIL	DW   ?
  KB_BUFFER		DW   16 DUP (?)
  KB_BUFFER_END 	LABEL	  WORD
ROM_BIOS_DATA ENDS
;
CODE_SEG      SEGMENT
	      ASSUME	CS:CODE_SEG
	      ORG	100H
  BEGIN:      JMP	INIT_VECTORS	    ;Initialize vectors and attach to
;					     DOS
  ROM_KEYBOARD_INT	DD		    ;Address for ROM routine
  ROM_KEYBOARD_IO	DD
  BUFFER_HEAD		DW   OFFSET KEYBOARD_BUFFER
  BUFFER_TAIL		DW   OFFSET KEYBOARD_BUFFER
  KEYBOARD_BUFFER	DW   160 DUP (0)    ;159 character input buffer
  KEYBOARD_BUFFER_END	LABEL	  WORD
;
; This procedure sends a short beep when the buffer fills:
;
  KB_CONTROL		EQU  61H	    ;Control bits for keyboard and
;					     speaker
ERROR_BEEP		PROC NEAR
	      PUSH	AX
	      PUSH	BX
	      PUSH	CX
	      PUSHF			    ;Save the old interrupt enable flag
	      CLI			    ;Turn off beep during interrupt
	      MOV	BX,30		    ;Number of cycles for 1/8 sec beep
	      IN	AL,KB_CONTROL	    ;Get control info from speaker port
	      PUSH	AX		    ;Save the control information
  START_OF_ONE_CYCLE:
	      AND	AL,0FCH 	    ;Turn off the speaker
	      OUT	KB_CONTROL,AL
	      MOV	CX,60		    ;Delay for one half cycle
  OFF_LOOP:   LOOP	OFF_LOOP
	      OR	AL,2		    ;Turn on the speaker
	      OUT	KB_CONTROL,AL
	      MOV	CX,60		    ;Delay for second half of cycle
  ON_LOOP:    LOOP	ON_LOOP
	      DEC	BX		    ;200 cycles yet?
	      JNZ	START_OF_ONE_CYCLE
	      POP	AX		    ;Recover old keyboard information
	      OUT	KB_CONTROL,AL
	      POPF			    ;Restore interrupt flag
	      POP	CX
	      POP	BX
	      POP	AX
	      RET
ERROR_BEEP		ENDP
;
; This procedure checks the ROM keyboard buffer to see if some program tried to
; clear this buffer.  We know it's been cleared when the ROM tail and header
; overlap.  Normally, the new procedures below keep the dummy character,
; word 0, in the buffer.
;
; Uses BX,DS
; Writes:     BUFFER_HEAD, BUFFER_TAIL, ROM_BUFFER_HEAD, ROM_BUFFER_TAIL
; Reads:      KEYBOARD_BUFFER, KB_BUFFER
;
CHECK_CLEAR_BUFFER	PROC NEAR
	      ASSUME	DS:ROM_BIOS_DATA
	      MOV	BX,ROM_BIOS_DATA    ;Establish pointer to BIOS data
	      MOV	DS,BX
	      CLI			    ;Turn of interrupts during check
	      MOV	BX,ROM_BUFFER_HEAD  ;Check to see if buffer cleared
	      CMP	BX,ROM_BUFFER_TAIL  ;Is the buffer empty?
	      JNE	BUFFER_OK	    ;No, then everything is OK
;					     Yes, then clear internal buffer
	      MOV	BX,OFFSET KB_BUFFER ;Reset buffer with word 0 in buffer
	      MOV	ROM_BUFFER_HEAD,BX
	      ADD	BX,2
	      MOV	ROM_BUFFER_TAIL,BX
	      ASSUME	DS:CODE_SEG
	      MOV	BX,CS
	      MOV	DS,BX
	      MOV	BX,OFFSET KEYBOARD_BUFFER    ;Reset internal buffer
	      MOV	BUFFER_HEAD,BX
	      MOV	BUFFER_TAIL,BX
  BUFFER_OK:
	      ASSUME	DS:CODE_SEG
	      STI			    ;Interrupts back on
	      RET
CHECK_CLEAR_BUFFER	ENDP
;
; This procedure intercepts the keyboard interrupt and moves any new characters
; to the internal, 80 character buffer
;
INTERCEPT_KEYBOARD_INT	PROC NEAR
	      ASSUME	DS:NOTHING
	      PUSH	DS
	      PUSH	SI
	      PUSH	BX
	      PUSH	AX
	      CALL	CHECK_CLEAR_BUFFER  ;Check for buffer cleared
	      PUSHF
	      CALL	ROM_KEYBOARD_INT    ;Read scan code with BIOS routines
;
; Transfer any characters to the internal buffer
;
	      ASSUME	DS:ROM_BIOS_DATA
	      MOV	BX,ROM_BIOS_DATA
	      MOV	DS,BX
	      MOV	SI,BUFFER_TAIL
	      MOV	BX,ROM_BUFFER_HEAD  ;Check if real character in buffer
	      ADD	BX,2		    ;Skip over dummy character
	      CMP	BX,OFFSET KB_BUFFER_END
	      JB	DONT_WRAP	    ;No need to wrap pointer
	      MOV	BX,OFFSET KB_BUFFER
  DONT_WRAP:
	      CMP	BX,ROM_BUFFER_TAIL  ;Is there a real character?
	      JE	NO_NEW_CHARACTERS   ;No, then return to caller
	      MOV	AX,[BX] 	    ;Yes, move char to internal buffer
	      MOV	CS:[SI],AX
	      ADD	SI,2		    ;Move to next position
	      CMP	SI,OFFSET KEYBOARD_BUFFER_END
	      JB	NOT_AT_END
	      MOV	SI,OFFSET KEYBOARD_BUFFER
  NOT_AT_END:
	      CMP	SI,BUFFER_HEAD	    ;Buffer overrun?
	      JNE	WRITE_TO_BUFFER     ;Yes, beep and throw out character
	      CALL	ERROR_BEEP
	      JMP	SHORT NOT_AT_KB_END
  WRITE_TO_BUFFER:
	      MOV	BUFFER_TAIL,SI
  NOT_AT_KB_END:
	      MOV	ROM_BUFFER_HEAD,BX
;
; See if [Ctrl] + [Alt] pushed and clear buffer if so
;
  NO_NEW_CHARACTERS:
	      MOV	AL,KBD_FLAG	    ;Get status of shift keys into AL
	      AND	AL,0CH		    ;Isolate Ctrl and Alt shift flags
	      CMP	AL,0CH		    ;Both Ctrl and Alt keys pressed?
	      JNE	DONT_CLEAR_BUFFER   ;No, so don't clear buffer
	      MOV	AX,BUFFER_TAIL	    ;Yes, so clear buffer
	      MOV	BUFFER_HEAD,AX
  DONT_CLEAR_BUFFER:
	      POP	AX
	      POP	BX
	      POP	SI
	      POP	DS
	      IRET
INTERCEPT_KEYBOARD_INT	ENDP
;
; This procedure replaces the ROM BIOS routines for reading a character
;
	      ASSUME	DS:CODE_SEG
INTERCEPT_KEYBOARD_IO	PROC FAR
	      STI			    ;Interrupts back on
	      PUSH	DS		    ;Save current DS
	      PUSH	BX		    ;Save BX temporarily
	      CALL	CHECK_CLEAR_BUFFER  ;Check for buffer cleared
	      MOV	BX,CS		    ;Establish pointer to Data Area
	      MOV	DS,BX
	      OR	AH,AH		    ;AH = 0?
	      JZ	READ_CHARACTER	    ;Yes, read a character
	      DEC	AH		    ;AH = 1?
	      JZ	READ_STATUS	    ;Yes, return the status
	      DEC	AH		    ;AH = 2?
	      JZ	READ_SHIFT_STATUS   ;Yes, return the shift status
	      POP	BX		    ;Ignore other function numbers
	      POP	DS
	      IRET
;
; Read the character
;
  READ_CHARACTER:			    ;ASCII Read
	      STI			    ;Interrupts back on
	      NOP			    ;Allow an interrupt to occur
	      CLI			    ;Interrupts back off
	      MOV	BX,BUFFER_HEAD	    ;Get pointer to head of buffer
	      CMP	BX,BUFFER_TAIL	    ;Test end of buffer
	      JE	READ_CHARACTER	    ;Loop until something appears
	      MOV	AX,[BX] 	    ;Get scan code and ASCII code
	      ADD	BX,2		    ;Move to next word in buffer
	      CMP	BX,OFFSET KEYBOARD_BUFFER_END	  ;At end of buffer?
	      JNE	SAVE_POINTER	    ;No, continue
	      MOV	BX,OFFSET KEYBOARD_BUFFER	  ;Yes, reset to start
  SAVE_POINTER:
	      MOV	BUFFER_HEAD,BX	    ;Store value in variable
	      POP	BX
	      POP	DS
	      IRET			    ;Return to caller
;
; ASCII status
;
  READ_STATUS:
	      CLI			    ;Interrupts off
	      MOV	BX,BUFFER_HEAD	    ;Get head pointer
	      CMP	BX,BUFFER_TAIL	    ;If equal then nothing there
	      MOV	AX,[BX]
	      STI			    ;Interrupts back on
	      POP	BX		    ;Recover registers
	      POP	DS
	      RET	2		    ;Throw away flags
;
; Shift status
;
  READ_SHIFT_STATUS:
	      JMP	ROM_KEYBOARD_IO     ;Let ROM routine do this
INTERCEPT_KEYBOARD_IO	ENDP
;
; This procedure initializes the interrupt vectors
;
INIT_VECTORS		PROC NEAR
	      ASSUME	DS:VECTORS
	      PUSH	DS		    ;Save old Data Segment
	      MOV	AX,VECTORS	    ;Set up Data Segment for vectors
	      MOV	DS,AX
	      CLI			    ;Dont allow interrupts
	      MOV	AX,KEYBOARD_INT_VECTOR	     ;Save addresses of BIOS
	      MOV	ROM_KEYBOARD_INT,AX	     ;routines and set up new
	      MOV	AX,KEYBOARD_INT_VECTOR[2]    ;KEYBOARD_INT vector
	      MOV	ROM_KEYBOARD_INT[2],AX	     ;
	      MOV	KEYBOARD_INT_VECTOR,OFFSET INTERCEPT_KEYBOARD_INT
	      MOV	KEYBOARD_INT_VECTOR[2],CS
	      STI			    ;allow interrupts again
	      MOV	AX,KEYBOARD_IO_VECTOR	     ;Set up KEYBOARD_IO vector
	      MOV	ROM_KEYBOARD_IO,AX
	      MOV	AX,KEYBOARD_IO_VECTOR[2]
	      MOV	ROM_KEYBOARD_IO[2],AX
	      MOV	KEYBOARD_IO_VECTOR,OFFSET INTERCEPT_KEYBOARD_IO
	      MOV	KEYBOARD_IO_VECTOR[2],CS
	      ASSUME	DS:ROM_BIOS_DATA    ;Now set up the keyboard buffer,
	      MOV	AX,ROM_BIOS_DATA    ;etc.
	      MOV	DS,AX
	      CLI			    ;Dont allow interrupts
	      MOV	BX,OFFSET KB_BUFFER
	      MOV	ROM_BUFFER_HEAD,BX
	      MOV	WORD PTR[BX],0
	      ADD	BX,2
	      MOV	ROM_BUFFER_TAIL,BX
	      STI			    ;Allow interrupts again
	      MOV	DX,OFFSET INIT_VECTORS	     ;End of resident portion
	      INT	27H		    ;Terminate but stay resident
INIT_VECTORS		ENDP
;
CODE_SEG		ENDS
	      END	BEGIN
 DISK, JUMP IF ERROR	;
;									;
;---------------------------------------------------------