; revised by rwt for dual-display systems, tests display type each time
; the window is activated.  Done through calls to set-display-type, which
; is a re-written portion of the initialization routine.  Also eliminated
; possibility of entering ASCII 127 (in SCR_TABLE).
;
; All changes to the program are in UPPER-case letters.  Un-commented
; portions are closely related to the original, commented out but
; commented, source code.  This should work with any normal two display
; system, e.g. mono with either cga or ega.
;
;KBX.COM for the IBM Personal Computer - 1987 by Jeff Prosise
;
kb_data       equ 60h                       ;keyboard data port
kb_ctrl       equ 61h                       ;keyboard control port
eoi           equ 20h                       ;8259 EOI value
int_ctrl      equ 20h                       ;8259 port address
;
bios_data     segment at 40h                ;BIOS data area
              org 17h
kb_status     db ?                          ;keyboard status byte
              org 1Ah
buffer_head   dw ?                          ;pointer to keyboard buffer head
buffer_tail   dw ?                          ;pointer to keyboard buffer tail
              org 80h
buffer_start  dw ?                          ;starting keyboard buffer address
buffer_end    dw ?                          ;ending keyboard buffer address
bios_data     ends
;
code          segment para public 'code'    ;code segment
              assume cs:code
              org 100h
begin:        jmp initialize                ;goto initialization code
;
copyright          db 'Copyright 1987 Ziff-Davis Publishing Co.',1Ah
enable_values      db 2Ch,28h,2Dh,29h,2Ah,2Eh,1Eh
screen_buffer      dw offset initialize     ;pointer to screen buffer
aux_shift          db 0                     ;auxiliary keyboard status byte
old_shift          db 0                     ;storage for shift status
window_status      db 0                     ;window display status
adapter            db 2                     ;0 = MDA, 1 = CGA, 2 = EGA
video_segment      dw 0B800h                ;default video segment
border_attr        db 2Fh                   ;window border attribute
text_attr          db 1Bh                   ;window border attribute
cursor_mode        dw 0607h                 ;default cursor shape
cursor_position    dw ?                     ;saved cursor position
video_page         db ?                     ;saved video page number
video_offset       dw ?                     ;starting window offset address
addr_6845          dw ?                     ;port address of CRTC
ibm                db 'IBM'                 ;EGA ASCII signature
old9h              label dword
old9h_vector       dw 2 dup (?)             ;storage for interrupt 9 vector
;
fill_parms    dw 1,030Fh,12
              dw 2,050Fh,12
              dw 2,070Fh,12
              dw 2,090Fh,10
              dw 17,0537h,4
              dw 0,0737h,4
              dw 0,0937h,3
;
def_table     db 0,'1234567890-=',0
              db 0,'QWERTYUIOP[]',0
              db 0,'ASDFGHJKL;',39,96
              db 0,0,'ZXCVBNM,./'
              db 17 dup (0)
              db '789-456+123',0,0
;
scr_table     db 0,000,128,129,130,131,132,133,134,135,136,137,138,0
; NOTE 127 ABOVE CHANGED TO 000
              db 0,139,140,141,142,143,144,145,146,147,148,149,150,0
              db 0,151,152,153,154,155,156,157,158,159,160,161,162
              db 0,0,163,164,165,166,167,168,0,0,0,0
              db 17 dup (0)
              db 201,203,187,205,204,206,185,186,200,202,188,0,0
;
num_table     db 0,169,170,171,172,173,174,175,176,177,178,219,220,0
              db 0,221,222,223,224,225,226,227,228,229,230,231,232,0
              db 0,233,234,235,236,237,238,239,240,241,242,243,244
              db 0,0,245,246,247,248,249,250,251,252,253,254
              db 17 dup (0)
              db 218,194,191,196,195,197,180,179,192,193,217,0,0
;
;------------------------------------------------------------------------------
;Interrupt 9 handler.  Execution comes here when a key is pressed or released.
;------------------------------------------------------------------------------
new9h         proc near
              sti                           ;set interrupt enable flag
              push ax                       ;save AX
              in al,kb_data                 ;get scan code from keyboard
              cmp al,69                     ;NumLock key pressed?
              je numlock_down               ;yes, then branch
              cmp al,69+128                 ;NumLock key released?
              je numlock_up                 ;yes, then branch
              cmp al,70                     ;ScrLock key pressed?
              je scrlock_down               ;yes, then branch
              cmp al,70+128                 ;ScrLock key released?
              je scrlock_up                 ;yes, then branch
              cmp al,57                     ;spacebar pressed?
              je spacebar                   ;yes, then branch
              cmp aux_shift,0               ;NumLock or ScrLock depressed?
              je exit                       ;no, then exit
              jmp newkey                    ;yes, then branch
exit:         pop ax                        ;restore AX
              jmp old9h                     ;goto old interrupt handler
;
;The NumLock key was pressed.  Toggle NumLock state or set shift bit.
;
numlock_down: call get_status               ;get main shift status byte
              test al,12                    ;is either Ctrl or Alt pressed?
              jnz exit                      ;yes, then goto normal handler
              push ax                       ;save shift code
              call reset_kb                 ;reset the keyboard
              pop ax                        ;retrieve shift code
              test al,3                     ;is either Shift key depressed?
              jnz numlock1                  ;yes, then branch
              or aux_shift,2                ;set NumLock shift bit
              jmp end_int                   ;exit
numlock1:     push ds                       ;save DS
              mov ax,bios_data              ;point DS to BIOS data area
              mov ds,ax
              assume ds:bios_data
              xor kb_status,32              ;toggle NumLock state
              pop ds                        ;restore DS
              assume ds:nothing
              jmp end_int                   ;exit
;
;The NumLock key was released.  Clear the NumLock shift bit.
;
numlock_up:   call reset_kb                 ;reset the keyboard
              and aux_shift,253             ;clear shift bit
              jmp end_int                   ;exit
;
;The ScrLock key was pressed.  Toggle ScrLock state or set shift bit.
;
scrlock_down: call get_status               ;get main shift status byte
              test al,12                    ;is either Ctrl or Alt pressed?
              jnz exit                      ;yes, then goto normal handler
              push ax                       ;save shift code
              call reset_kb                 ;reset the keyboard
              pop ax                        ;retrieve shift code
              test al,3                     ;is either Shift key depressed?
              jnz scrlock1                  ;yes, then branch
              or aux_shift,1                ;set ScrLock shift bit
              jmp end_int                   ;exit
scrlock1:     push ds                       ;save DS
              mov ax,bios_data              ;point DS to BIOS data area
              mov ds,ax
              assume ds:bios_data
              xor kb_status,16              ;toggle ScrLock state
              pop ds                        ;restore DS
              assume ds:nothing
              jmp end_int                   ;exit
;
;The ScrLock key was released.  Clear the ScrLock shift bit.
;
scrlock_up:   call reset_kb                 ;reset the keyboard
              and aux_shift,254             ;clear shift bit
end_int:      mov al,eoi                    ;issue EOI to 8259 controller
              out int_ctrl,al
              pop ax                        ;clean up the stack
              iret                          ;return from interrupt
;
;The spacebar was pressed.  Pop up the window if Alt is depressed.
;
spacebar:     call get_status               ;get current shift status
              test al,8                     ;is the Alt key depressed?
              jz exit                       ;no, then exit to normal handler
              call reset_kb                 ;yes, then reset keyboard
              mov al,eoi                    ;issue end-of-interrupt signal
              out int_ctrl,al
              cmp window_status,0           ;is the window already up?
              jne space1                    ;yes, then ignore keypress
              push bx                       ;save BX
              call kb_display               ;pop up keyboard display
              pop bx                        ;restore BX
space1:       pop ax                        ;clean up the stack
              iret                          ;exit
;
;A key was pressed or released with NumLock or ScrLock held down.
;
newkey:       push ax                       ;save scan code
              call reset_kb                 ;reset keyboard
              pop ax                        ;recover scan code
              test al,80h                   ;is the high bit set?
              jnz newkey3                   ;yes, then don't process it
              push bx                       ;save BX
              mov bx,offset num_table       ;point BX to NumLock key table
              cmp aux_shift,1               ;ScrLock depressed?
              jne newkey1                   ;no, then continue
              mov bx,offset scr_table       ;yes, then adjust BX
newkey1:      mov ah,al                     ;transfer scan code to AH
              dec al                        ;set AL relative to zero base
              xlat num_table                ;get ASCII code from table
              or al,al                      ;is it zero?
              je newkey2                    ;yes, then don't process this key
              call insert_char              ;insert character into kb buffer
newkey2:      pop bx                        ;restore BX register value
newkey3:      mov al,eoi                    ;issue end-of-interrupt signal
              out int_ctrl,al
              pop ax                        ;restore AX and clean up the stack
              iret
new9h         endp
;
;------------------------------------------------------------------------------
;RESET_KB resets the keyboard.
;------------------------------------------------------------------------------
reset_kb      proc near
              in al,kb_ctrl                 ;get control port value
              mov ah,al                     ;save it
              or al,80h                     ;set high bit of control value
              out kb_ctrl,al                ;send reset value
              mov al,ah                     ;get original control value
              out kb_ctrl,al                ;enable keyboard
              ret                           ;done
reset_kb      endp
;
;------------------------------------------------------------------------------
;GET_STATUS returns the main keyboard shift status byte in AL.
;------------------------------------------------------------------------------
get_status    proc near
              push ds                       ;save DS
              mov ax,bios_data              ;point DS to BIOS data area
              mov ds,ax
              assume ds:bios_data
              mov al,kb_status              ;get status byte in AL
              pop ds                        ;restore DS
              assume ds:nothing
              ret                           ;exit
get_status    endp
;
;------------------------------------------------------------------------------
;INSERT_CHAR inserts the character code in AX into the keyboard buffer.
;Entry:  AH,AL - scan code, ASCII code
;------------------------------------------------------------------------------
insert_char   proc near
              push dx                       ;save DX and DS
              push ds
              mov bx,bios_data              ;point DS to BIOS data area
              mov ds,bx
              assume ds:bios_data
              mov bx,buffer_tail            ;get current buffer tail address
              mov dx,bx                     ;transfer it to DX
              add dx,2                      ;calculate next buffer position
              cmp dx,buffer_end             ;did we overshoot the end?
              jne insert1                   ;no, then continue
              mov dx,buffer_start           ;yes, then wrap around
insert1:      cmp dx,buffer_head            ;is the buffer full?
              je insert2                    ;yes, then branch
              mov [bx],ax                   ;deposit character into buffer
              mov bx,dx                     ;advance buffer tail
              mov buffer_tail,bx            ;record its new value
insert2:      pop ds                        ;restore DS
              assume ds:nothing
              pop dx                        ;restore DX
              ret                           ;exit
insert_char   endp
;
;------------------------------------------------------------------------------
;KB_DISPLAY opens a window showing all possible key definitions.
;------------------------------------------------------------------------------
kb_display    proc near
;
;Make sure current video mode is an 80-column text mode.
;
              mov ah,15                     ;get current video mode
              int 10h
              cmp al,2                      ;video mode 2?
              je kb1                        ;yes, then continue
              cmp al,3                      ;video mode 3?
              je kb1                        ;yes, then continue
              cmp al,7                      ;video mode 7 (monochrome)?
              je kb1
              ret                           ;unsupported mode - terminate
kb1:          mov window_status,1           ;set status flag
              push cx                       ;save register values
              push dx
              push si
              push di
              push ds
              push es
              push cs                       ;set DS and ES to the code segment
              pop ds
              assume ds:code
              push cs
;             pop es    ;THIS POP MOVED DOWN 3 LINES
;
;Save page number, cursor mode, and cursor position.  Then blank the cursor.
;
              mov video_page,bh             ;store video page number
        CALL    SET_DISPLAY_TYPE ; CHECK DISPLAY TYPE EACH TIME.
        POP     ES              ; SEE 3 LINES UP
              mov ah,3                      ;get current cursor mode
              int 10h
              mov cursor_mode,cx            ;store cursor mode
              call read_cursor              ;get cursor position
              mov cursor_position,ax        ;save it
              mov ah,1                      ;hide the cursor
              mov ch,20h
              int 10h
;
;Save the portion of video memory that will be overwritten.
;
              cmp adapter,1                 ;disable video if this is a CGA
              jne kb2
              call disable_cga
kb2:          call save_screen              ;copy video memory into buffer
;
;Open the keyboard display window.
;
              call open_window              ;open the window
              cmp adapter,1                 ;enable CGA video
              jne kb3
              call enable_cga
kb3:          lea si,def_table              ;point SI to default key table
              call fill_window              ;draw unshifted key definitions
;
;The window is open.  Loop until the ESC key is pressed, continually monitoring
;the auxiliary shift byte to determine what key definition set to display.
;
kb4:          mov ah,1                      ;check buffer for character
              int 16h
              jz kb5                        ;branch if buffer is empty
              mov ah,0                      ;get the character
              int 16h
              cmp al,27                     ;is it the ESC key?
              je kb7                        ;yes, then close window and exit
kb5:          mov al,aux_shift              ;get auxiliary shift byte
              cmp al,2                      ;is it <= 2?
              jna kb6                       ;yes, then branch
              mov al,2                      ;no, then set it to 2
kb6:          cmp al,old_shift              ;has the shift status changed?
              je kb4                        ;no, then loop back
              mov old_shift,al              ;record current shift status
              mov bl,83                     ;calculate table address from AL
              mul bl
              mov si,ax                     ;transfer it to SI
              add si,offset def_table       ;complete offset address in SI
              call fill_window              ;write key equivalents to window
              jmp kb4                       ;go back for more
;
;The ESC key was pressed.  Close the window and exit.
;
kb7:          cmp adapter,1                 ;blank CGA video
              jne kb8
              call disable_cga
kb8:          call restore_screen           ;restore contents of video memory
              cmp adapter,1                 ;enable CGA video
              jne kb9
              call enable_cga
kb9:          mov ah,2                      ;restore cursor position
              mov bh,video_page
              mov dx,cursor_position
              int 10h
              mov ah,1                      ;unblank the cursor
              mov cx,cursor_mode
              int 10h
              mov window_status,0           ;reset status flag
              pop es                        ;restore register values
              pop ds
              pop di
              pop si
              pop dx
              pop cx
              ret                          ;return to calling routine
kb_display    endp
;
;------------------------------------------------------------------------------
;SAVE_SCREEN saves the block of video memory that will be overwritten.
;------------------------------------------------------------------------------
save_screen   proc near
              mov si,182                    ;set zero page window offset in SI
              mov cl,video_page             ;get video page in CX
              xor ch,ch
              jcxz save2                    ;branch if page zero
save1:        add si,1000h                  ;add one page length
              loop save1                    ;loop until offset is correct
save2:        mov video_offset,si           ;save starting window address
              push ds                       ;save DS
              mov ds,video_segment          ;point DS to video memory
              assume ds:nothing
              mov di,screen_buffer          ;point ES:DI to screen buffer
              mov cx,11                     ;11 lines to save
save3:        push cx                       ;save line counter
              mov cx,58                     ;58 characters per line
              cld                           ;clear DF
              rep movsw                     ;transfer one line to storage
              pop cx                        ;retrieve counter
              add si,44                     ;point SI to next line
              loop save3                    ;loop until all lines are saved
              pop ds                        ;restore DS
              assume ds:code
              ret
save_screen   endp
;
;------------------------------------------------------------------------------
;RESTORE_SCREEN restores saved video memory.
;------------------------------------------------------------------------------
restore_screen proc near
              mov es,video_segment          ;point ES to video segment
              mov di,video_offset           ;point DI to window area
              mov si,screen_buffer          ;point DS:SI to screen buffer
              mov cx,11                     ;11 lines to restore
restore1:     push cx                       ;save line count
              mov cx,58                     ;58 characters per line
              rep movsw                     ;restore one line
              pop cx                        ;retrieve count
              add di,44                     ;advance DI to next line
              loop restore1                 ;loop until done
              ret
restore_screen endp
;
;------------------------------------------------------------------------------
;DISABLE_CGA and ENABLE_CGA control CGA video output.
;------------------------------------------------------------------------------
disable_cga   proc near
              mov dx,3DAh                   ;Status Register port address
disable1:     in al,dx                      ;read status
              test al,8                     ;vertical retrace active?
              jz disable1                   ;no, then wait until it is
              sub dx,2                      ;point DX to MSR
              mov al,25h                    ;load disable value
              out dx,al                     ;disable video
              ret
disable_cga   endp
;
enable_cga    proc near
              mov ah,15                     ;get current video mode
              int 10h
              lea bx,enable_values          ;point BX to table of values
              xlat enable_values            ;get value to enable signal
              mov dx,3D8h                   ;MSR address
              out dx,al                     ;enable video output
              ret
enable_cga    endp
;
;------------------------------------------------------------------------------
;READ_CURSOR reads the cursor position directly from the CRT Controller.
;Exit:  AH,AL - row, column
;------------------------------------------------------------------------------
read_cursor   proc near
              mov dx,addr_6845              ;get CRTC Address Register port
              mov al,14                     ;specify register number
              out dx,al
              inc dx                        ;point DX to Data Register
              in al,dx                      ;read high byte of cursor address
              mov ah,al                     ;save it in AH
              dec dx                        ;point DX back to Address Register
              mov al,15                     ;specify next register number
              out dx,al
              inc dx                        ;point DX to Data Register
              in al,dx                      ;read low byte of address
              and ax,07FFh                  ;strip page bits from address
              mov bl,80                     ;divide by 80
              div bl
              xchg ah,al                    ;swap AH and AL
              ret
read_cursor   endp
;
;------------------------------------------------------------------------------
;OPEN_WINDOW writes the new window to video memory.
;------------------------------------------------------------------------------
open_window   proc near
              mov es,video_segment          ;point ES to video memory
              mov di,video_offset           ;point DI to start of window
              mov al,218                    ;get first character code
              mov ah,border_attr            ;and first attribute byte
              stosw                         ;write
              mov cx,56                     ;do the next 56 characters
              mov al,196
              rep stosw
              mov al,191                    ;finish the first line
              stosw
              add di,44                     ;advance DI to next line
              mov cx,9                      ;9 identical lines next
open1:        push cx                       ;save line counter
              mov al,179                    ;do first character
              push ax                       ;save character/attribute
              stosw
              mov cx,56                     ;do the next 56 characters
              mov al,32
              mov ah,text_attr
              rep stosw
              pop ax                        ;retrieve character/attribute pair
              stosw                         ;finish the line
              add di,44                     ;advance DI to next line
              pop cx                        ;retrieve line count
              loop open1                    ;loop until all 9 are done
              mov al,192                    ;first character on last line
              stosw
              mov cx,56                     ;do the next 56
              mov al,196
              rep stosw
              mov al,217                    ;finish the last line
              stosw
              ret
open_window   endp
;
;------------------------------------------------------------------------------
;FILL_WINDOW writes a set of key definitions to the open window.
;Entry:  DS:SI - key definition table address
;------------------------------------------------------------------------------
fill_window   proc near
              mov bh,video_page             ;retrieve video page number
              lea di,fill_parms             ;point DI to parameter table
              mov cx,7                      ;7 lines to write
fill1:        push cx                       ;save counter
              add si,word ptr [di]          ;adjust table index
              mov dx,[di+2]                 ;set starting cursor position
              mov cx,[di+4]                 ;set number of characters
              call writeln                  ;write one line
              add di,6                      ;advance parameter table index
              pop cx                        ;retrieve count
              loop fill1                    ;loop until done
              ret
fill_window   endp
;
;------------------------------------------------------------------------------
;WRITELN writes one line of key equivalents to the open window.
;Entry:  DS:SI - character string address
;        BH    - video page
;        CX    - number of characters
;        DH,DL - starting row and column
;------------------------------------------------------------------------------
writeln       proc near
              push cx                       ;save character counter
              mov ah,2                      ;position the cursor
              int 10h
              lodsb                         ;get one character
              mov ah,10                     ;print it
              mov cx,1
              int 10h
              add dl,3                      ;advance cursor position
              pop cx                        ;retrieve count
              loop writeln                  ;loop until done
              ret
writeln       endp
;
;------------------------------------------------------------------------------
;INITIALIZE prepares the body of the program for residency.
;------------------------------------------------------------------------------
;
SET_DISPLAY_TYPE    PROC NEAR   ; CHECKS DISPLAY TYPE FOR WINDOWING EACH TIME
                                ; IT IS USED, NOT JUST AT INITIALIZATION.
;
        MOV     AH,15
        INT     10H
        CMP     AL,7
        JNZ     INIT2
        MOV     ADAPTER,0
        MOV     VIDEO_SEGMENT,0B000H
        MOV     BORDER_ATTR,70H
        MOV     TEXT_ATTR,07H
        MOV     CURSOR_MODE,0C0DH
        JMP     INIT3
INIT2:  MOV     ADAPTER,2
        MOV     VIDEO_SEGMENT,0B800H
        MOV     BORDER_ATTR,2FH
        MOV     TEXT_ATTR,1BH
        MOV     CURSOR_MODE,0607H
        MOV     AX,0C000H
        MOV     ES,AX
        MOV     DI,1EH
        LEA     SI,IBM
        MOV     CX,3
        CLD
        REPE    CMPSB
        JZ      INIT3
        DEC     ADAPTER
INIT3:  MOV     AX,40H
        MOV     ES,AX
        MOV     DI,63H
        MOV     DX,ES:[DI]
        MOV     ADDR_6845,DX
        MOV     CX,CURSOR_MODE
        MOV     AH,1
        INT     10H
        RET
;
SET_DISPLAY_TYPE        ENDP
;
;
;initialize    proc near
;
;See if the display adapter is an EGA.
;
;              mov ax,0C000h                 ;set ES to EGA BIOS segment
;              mov es,ax
;              mov di,1Eh                    ;point DI to signature location
;              lea si,ibm                    ;point SI to 'IBM' text
;              mov cx,3                      ;three bytes to compare
;              cld                           ;clear DF
;              repe cmpsb                    ;check three bytes
;              je init1                      ;branch if signature found
;
;Determine whether adapter is a CGA or an MDA.
;
;              dec adapter                   ;decrement assumed value
;              mov ah,15                     ;get current video mode
;              int 10h
;              cmp al,7                      ;is it mode 7?
;              jne init1                     ;no, then it's a CGA
;
;The display adapter is an MDA.  Modify video attributes.
;
;              dec adapter                   ;set ADAPTER value for MDA
;              sub video_segment, 800h       ;modify video segment value
;              mov border_attr,70h           ;modify border attribute
;              mov text_attr,07h             ;modify text attribute
;              mov cursor_mode,0C0Dh         ;modify default cursor shape
;
;Reset the cursor to its default shape.
;
;init1:        mov cx,cursor_mode            ;set scan lines in CX
;              mov ah,1                      ;video function - set cursor
;              int 10h                       ;reset cursor
;
;Get and save the address of the CRT Controller.
;
;              mov ax,40h                    ;point ES to BIOS data area
;              mov es,ax
;              mov di,63h                    ;point DI to address word
;              mov dx,es:[di]                ;get CRTC address
;              mov addr_6845,dx              ;save it
;
INITIALIZE      PROC    NEAR
;
;Save the old interrupt 9 vector and replace it with a new one.
;
              mov ax,3509h                  ;get current interrupt 9 vector
              int 21h
              mov old9h_vector,bx           ;save it
              mov old9h_vector[2],es
              mov ah,25h                    ;then point it to NEW9H routine
              lea dx,new9h
              int 21h
;
;Terminate, leaving additional room for screen buffering.
;
              mov dx,offset initialize+1276 ;set DX for exit
              int 27h                       ;terminate-but-stay-resident
initialize    endp
;
code          ends
              end begin
