PAGE 60,132
TITLE BASIC_KB.ASM - KEYBOARD MONITOR ROUTINE
;
COMMENT  @  KEYBOARD.ASM,  Copyright (c) 1983 by Jeffrey W. Sutherland, for the
            enjoyment  of  PC  lovers everywhere. Please feel free to pass this
            program on to your friends and colleagues.

       This  is  a  short  routine  intended  to reside above DOS in memory and
remain  there  for all BASIC(A) applications. As part of the keyboard interrupt
routine,  it monitors the state of the Caps Lock and Num Lock keys. Blinking up
arrows  will be displayed to the left of the function key #8 area on line 25 of
the  IBM  MONOCHROME screen, and blinking `##' will be displayed to the left of
the  function  key  #10  area.  If the function keys are being displayed, their
displayed  values  will  be  restored  when  the Caps Lock or Num Lock keys are
toggled  OFF. If the function keys are NOT being displayed, then blanks will be
inserted  in  line  25.  If  you  use  line  25  for  displaying  any  kinds of
information,  you  may not want to use this program, or else stay away from the
arrows  and  ##  display areas or you may get strange displays whenever you hit
any key. (This program will NOT function using the Color Graphics adapter!) The
values  of the function keys do not change when the arrows or ## are displayed.
@
;
;        No Macros
;
NORM_ATTR  EQU  0700H        ;Equivalent to stating: `COLOR 7'.
KEYON_ATTR EQU  70H          ;This is the reverse video attribute.
                             ;Note that the KEYON_ATTR is one byte in length,
                             ; while NORM_ATTR and ATTRIBUTE are WORD lengths.
                             ; This is so the = arithmetic will work.  The
                             ; attribute of a screen character is located at
                             ; the odd addresses in the screen buffer,
                             ; while the characters are all at even addresses.
ARROW      EQU  24           ;ASCII 24, the up arrow character.
NUMSYMB    EQU  '#'          ;Speaks for itself.
SPACE      EQU  20H          ;ASCII for the space character.
ATTRIBUTE  EQU  9800H        ;Screen attribute for blinking highlighted.
CAPCHAR = ARROW+ATTRIBUTE    ;Combine the new characters with the proper
NUMCHAR = NUMSYMB+ATTRIBUTE  ; screen attribute.
NORM_CHAR = SPACE+NORM_ATTR  ;All this work just to put a blank spot in screen.
KEY8_CHAR = '8'+NORM_ATTR    ;Character to use for key 8 when keys are on.
KEY10_CHAR = '0'+NORM_ATTR   ;Key 10's character.
DOSINT     EQU  27H          ;DOS `end but stay resident' return interrupt.
INTADDR    EQU  9H*4         ;Address of KEYBOARD_INT service routine.
KB_FLAG    EQU  417H         ;Offset into BIOS data area for the keyboard flag.
CAPS_STATE EQU  40H          ;Bit masks
NUM_STATE  EQU  20H          ;
VIDSEGMENT EQU  0B000H       ;Monochrome screen buffer segment. (Color's=0B800H)
CAP_OFFSET EQU  0F6EH        ;Offset to place for arrows characters.
NUM_OFFSET EQU  0F8EH        ;Offset to place for `##' characters.
;
STACK     SEGMENT PARA STACK 'STACK' ;This stack segment used only
         DB 64 DUP ('STACK   ')      ;while initializing program.
STACK    ENDS
;
;
CSEG     SEGMENT   PARA PUBLIC  'DATA'                   ;The main routines.
         ASSUME CS:CSEG,DS:CSEG,SS:STACK,ES:NOTHING
KBOARD   PROC   FAR
         CALL  INIT_CODE     ;Set up the INT 9 vector to point to this routine.
         RET                 ;Return to DOS, program fixed in memory and
;                              initialized.
DWORD_ADDR   DW  0,0         ;Data storage area for BIOS routine seg/offset.
;
START_UP:                    ;
         PUSH DS             ;Prepare for the BIOS call. We must first see what
         PUSH SI             ; the keyboard interrupt is all about.
         PUSHF                     ;You see, BIOS keyboard svc expects INT call.
         MOV SI,SEG DWORD_ADDR     ;
         MOV DS,SI                 ;Set up pointer to seg/offset of
         MOV SI,OFFSET DWORD_ADDR  ; subroutine call to the BIOS.
         CALL DWORD PTR [SI]       ;Program goes to service keyboard.
         PUSH AX             ;Now, check the keyboard status flags to see if
         PUSH BX             ; this particular interrupt changed anything.
         PUSH CX             ;
         PUSH DX             ;
         MOV AX,VIDSEGMENT   ;Establish addressing for the monochrome screen
         MOV DS,AX           ;buffer, then save it for a moment.
         MOV BX,CAP_OFFSET   ;Are the function keys turned on?
         MOV AL,[BX+5]       ;Obtain attribute to see if set for function ky on
         MOV CH,KEYON_ATTR   ;See if attribute is that of a key on.
         AND AL,CH           ;
         JNZ KEYS_ON         ;If not 0 then keys are turned on.
         MOV CX,NORM_ATTR    ;
KEYS_ON: PUSH DS             ;
         MOV AX,0            ;Establish addressability for the status byte
         MOV DS,AX           ; at 0000:0417 (KB_FLAG).
         MOV BX,KB_FLAG      ;
         MOV AL,[BX]         ;Put status byte in AL for toggle state tests.
         POP DS              ;Get our buffer segment back now.
         MOV DX,CAPCHAR      ;DX now has the character to be used if caps on.
         MOV BX,CAP_OFFSET   ;BX now has position to place characters for caps.
         TEST AL,CAPS_STATE  ;If caps toggled, turn on arrows, else put back the
         JNZ CAPS_ON         ; function key char/attribute.
         MOV DX,NORM_CHAR    ;
         MOV [BX],DX         ;Always put a blank in first position, keys on/off.
         TEST CH,KEYON_ATTR  ;Keys on?
         JZ UH_UH            ;Guess not.
         MOV DX,KEY8_CHAR    ;Keys are on; put character '8' in screen next to
         MOV [BX+2],DX       ; function key 8 character string.
         JMP SHORT NUMBS     ;Go see what the Num Lock key is doing.
UH_UH:                       ;
         MOV [BX+2],DX       ;Put a blank where '8' should be (keys are off).
         JMP SHORT NUMBS     ;Go see what the Num Lock key is doing.
CAPS_ON:                     ;
         MOV [BX],DX         ;DX now has caps character, because of AL test.
         MOV [BX+2],DX       ;Must fill two character positions with characters.
NUMBS:   MOV DX,NUMCHAR      ;Ok.- ready for next test, might need nums on char.
         MOV BX,NUM_OFFSET   ;Establish addressability for next set of chars.
         TEST AL,NUM_STATE   ;NUM LOCK toggle bit set?
         JNZ NUMS_ON         ;If so, display `##'.
         MOV DX,NORM_CHAR    ;A space, recall.
         MOV [BX],DX         ;Put it in the screen.
         TEST CH,KEYON_ATTR  ;Function keys on?
         JZ NOPE             ;If not,...
         MOV DX,KEY10_CHAR   ;Put a '0' next to key 10's char string (keys on).
         MOV [BX+2],DX       ;
         JMP SHORT GOBACK    ;Almost done with interrupt service.
NOPE:                        ;
         MOV [BX],DX         ;Put blanks in the screen (keys are off).
         MOV [BX+2],DX       ;
         JMP SHORT GOBACK    ;Let's go home.
NUMS_ON:                     ;
         MOV [BX],DX         ;Num Lock key is toggled on; put `##' in the
         MOV [BX+2],DX       ; screen at place pointed to by BX.
;
GOBACK:  POP DX              ;Ok. Let's clean up the stack and
         POP CX              ; go back to the system.
         POP BX              ;
         POP AX              ;
         POP SI              ;
         POP DS              ;All Pau.
LASTONE: IRET                ;Return from keyboard interrupt.
KBOARD   ENDP                ;
;
;        This subroutine is used to initialize the preceeding program when
;        it is loaded, then returns control to DOS.
;
INIT_CODE  PROC  NEAR
         POP AX              ;Remove return address of call for a moment.
         PUSH DS             ;Push segment base of DOS Program Segment Prefix
         MOV DI,0            ; created for this entire program, and push offset
         PUSH DI             ; to first instruction therein (will be an INT 27).
         PUSH AX             ;All set to return to DOS when this is done.
         MOV AL,DOSINT       ;Set up addr to INT 27 vector (end but
         MOV [DI+1],AL       ; stay resident).
         MOV AX,0            ;Establish addressability for vector table.
         MOV DS,AX           ;
         MOV BX,INTADDR         ;Offset to seg/offset storage area for BIOS
                                ; interrupt vector in the system vector table.
         LES DI,DWORD PTR [BX]  ;This step puts the code seg in the ES and the
                                ; offset into DI, at the address pointed to by
                                ; the DS/BX regs.
         MOV AX,SEG DWORD_ADDR  ;Now set up addr to store BIOS routine vector
         MOV DS,AX              ; in the main program so we can use this
         MOV DWORD_ADDR,DI      ; routine.
         MOV AX,ES              ;
         MOV DWORD_ADDR+2,AX    ;Seg/offset now stored in DWORD_ADDR.
         MOV AX,SEG START_UP    ;
         MOV ES,AX              ;Restore ES segment to = main program CS.
         MOV DI,OFFSET START_UP ;Prepare to store the address to the main
         MOV AX,0               ; program in the system vector table.
         MOV DS,AX              ;Establish addressability for the vector table.
         MOV BX,INTADDR         ;
         MOV [BX],DI            ;Store offset to main program entry point in
         MOV DI,ES              ; vector table. ES has segment of main program,
         MOV [BX+2],DI          ; so put it in the table above the offset.
         MOV DX,OFFSET LASTONE  ;Save all code, except stack and initialization
         ADD DX,0110H           ; routine, from being overlaid by dos.
         RET                 ;Return to main program (which immediately returns
INIT_CODE ENDP               ; to the DOS command processor.)
CSEG     ENDS                ;That are it!
         END       ;basic_kb.asm
