Metropoli BBS
VIEWER: key.asm MODE: TEXT (ASCII)
include qlib.inc
include conio.inc
include dos.inc
include stdio.inc

;debug equ 0

; =========================================================================
;   Keyboard Driver     Version 1.32
; =========================================================================
;  The keyboard driver sets up IRQ handlers and allows better access to the
;  keyboard which BIOS just can not do.
;  You can:
;    - getch(), getche(), getchw(), ungetch()
;    - block certain keys from being directed RMODE (which will disable
;      ctrl+break and ctrl+alt+del
;    - setup lights on the kbd
;    - block the LOCK keys from going to RMODE
;  There is also 3 sets of tables maintained by the handler which you can
;  peek into anytime to see which keys are currently held in.
;  There are many keys on the keyboard which are a double of another (like
;  the enter key on the keypad), all these keys are called grey keys and
;  are preceded by an 0e0h code from the kbd.  When these keys are held in
;  table#2 is used and if it is not a grey key then table#1 is used.
;  Table#0 is basically table#1 OR table#2.
;  So if you don't care which CTRL the user
;  is holding in just look in table#0.  These tables are each 128 bytes
;  and each byte is either 1 (held in) or 0 (!held in).  Each byte corresponds
;  to that keys scan code. (ie: key_tab[1] is the ESC key)
; =========================================================================

; NEW : ver 1.32
;  - now inited thru the _INIT_ segment
;  - GS: references were removed since we are in FLAT mode!

; NEW : ver 1.31
;  - added _getch() which returns (100h + scan code) if it was a 0 + scan code
;    key that was pressed.

; NEW : ver 1.3
;  - total overhaul, now is idendical to ANSI C (which sucks) but now
;    supports multi-language kbds which is very important
;  - added ungetch() - NOTE : only one key can be unget at a time

; NEW : ver 1.2
;  - all IRQs are sent to RMODE (and therefore all LOCKS are handled by BIOS)
;  - some keys may be blocked from going to RMODE to prevent Ctrl+brk and
;    ctrl+alt+del (key_block())
;  - can also prevent LOCK keys from going to RMODE  (key_block_locks())
;  - 64 byte buffer added
;  - can set lights (also updates BIOS's flags)

; NEW : ver 1.1
;  - key_init is now called on startup  (within c0.asm)

_INIT_ segment word public use32 'INITDATA'
  db 0
  db 0  ;Kernel init
  dd key_init
_INIT_ ends

.data
align 4
  key_tab db 128 dup (0)   ;Table #0  Current keys held in  (1 OR 2)
  key_tab1 db 128 dup (0)  ;Table #1  (keys held in w/o preceding 0e0h)
  key_tab2 db 128 dup (0)  ;Table #2  (keys held in w/ preceding 0e0h)

  e0h db 0  ;indicates if the last key typed started with an 0e0h
            ;or an 0e1h (ver 1.2)
            ;this is true on all keypad keys, the right ALT,CTRL
            ;and other things... When this is true key_tab2 is used when
            ;keys are pressed/released (instead of key_tab1)
align 4
  old_int df ?  ;old PMODE IRQ handler

  ack db 0      ;ACK that command was sent
  resend db 0   ;resend last command/data
  sending_data db 0  ;flag : currently sending data to kbd
  block_e0 db 0    ;when set all keys starting with 0e0h are not sent to
                     ;RMODE.
                     ; (plus CTRL just in case - to prevent CTRL+ALT+DEL)
                     ; This includes break,del,ins,home,end,pgu,pgd,arrow keys,
                     ;  right ctrl,right alt,enter(on keypad only)
                     ; Great to disable Ctrl+break and Ctrl+alt+del.
                     ;  Use key_block(x) to do this. (1=YES  0=NO)

  paws db 0          ;Pause key sequence is coming (e1 1d 45 e1 9d c5)
  block_locks db 0   ;when set all LOCK keys are not sent to RMODE
  ungetch_flg db 0   ;set if a key is waiting for getch using ungetch
  ungetch_char db 0  ;char waiting when using ungetch

.code
; waits till kbd is not busy
kb_wait proc private
  ;don't use edx!!
  mov ecx,10000h
@@:
  in al,64h
  test al,2
  jz @f
  dec ecx
  jnz @b
@@:
  ret
kb_wait endp

align 4
;send data to kbd
key_send proc private,data:byte
  mov edx,3   ;# of reties
retry:
  call kb_wait
  mov ack,0
  mov resend,0
  mov al,data
  out 60h,al
  mov ecx,40000h
@@:
  push edx        ;
  callp delay,1   ;waste some time (1ms)
  pop edx         ;
  .if ack
    xor eax,eax
    ret
  .endif
  .if resend
    dec edx
    jnz retry
    mov eax,1    ;did not send
    ret
  .endif
  dec ecx
  jnz @b
  mov eax,1    ;did not send
  ret
key_send endp

;this empty's the kbd input/output buffers  (scraps everything)
empty_8042 proc private
  call _delay
  in al,64h
  test al,1
  jz no_output
  call _delay
  in al,60h
  jmp empty_8042
no_output:
  test al,2
  jnz empty_8042
  ret
empty_8042 endp

;sets the lights on the kbd and updates the BIOS flags
key_lights proc,a:byte
  pushad   ;FIX v2.06 : was not preserving regs
  callp empty_8042
  mov sending_data,1    ;we are sending data!
  callp key_send,0edh     ;send set lights command
  .if eax    ;successful?
    mov sending_data,0  ;nope!
bad:
    popad
    mov eax,ERROR
    ret
  .endif
  callp key_send,a        ;send data  (bits 0-2 are the lights)
  mov sending_data,0      ;done sending data
  .if eax
    jmp bad
  .endif

;update BIOS flags
  .if a & 1  ;SCROOL LOCK bit
    or byte ptr ds:[417h],(1h SHL 4)
  .else
    and byte ptr ds:[417h],0ffh XOR (1h SHL 4)
  .endif
  .if a & 4  ;CAPS
    or byte ptr ds:[417h],(1 SHL 6)
  .else
    and byte ptr ds:[417h],0ffh XOR (1h SHL 6)
  .endif
  .if a & 2  ;NUM LOCK
    or byte ptr ds:[417h],(1 SHL 5)
  .else
    and byte ptr ds:[417h],0ffh XOR (1h SHL 5)
  .endif
  popad
  xor eax,eax
  ret
key_lights endp

_delay proc private
  jmp $+2
  ret
_delay endp

align 4
;PMODE IRQ handler
key_pmirq:
  call key_irqhand
  sti
  iretd

align 4
;this is the main IRQ handler
key_irqhand proc private
  push ds
  push es
  push gs
  pushad
  mov ds,cs:seldata
  mov es,seldata
  mov gs,selzero
  call kb_wait
  xor eax,eax
  in al,60h  ;get data from kbd
  .if paws   ;if the paws sequence is coming then just ignore the data
    dec paws
    .if block_e0   ;if 0e0h is disabled then just IRET
      jmp key_done
    .endif
    jmp key_iend     ;else redirect to RMODE
  .endif
  .if al==0fah   ;ACK (means the kbd successfully recieved the command/data)
    .if sending_data  ;if we are sending data to the kbd we need the ACK
      mov ack,1
      jmp key_done
    .endif
    jmp key_iend      ;else send to RMODE
  .endif
  .if al==0feh  ;resend  (means kbd did not get command/data)
    .if sending_data
      mov resend,1
      jmp key_done
    .endif
    jmp key_iend
  .endif
  .if sending_data    ;ignore everything else while sending data
    jmp key_done
  .endif
  .if al==0e0h     ;it's the 0e0h prefix  (next key is a grey key)
    mov e0h,1           ;set flag
    .if block_e0        ;block e0h from going to RMODE?
      jmp key_done      ;yes
    .endif
    jmp gotoRMODE       ;no
  .endif
  .if al==0e1h          ;this is the 1st byte of the PAUSE sequence
    mov paws,5        ;ignore next 5 codes
    mov e0h,1         ;the PAUSE key is considered to have a 0e0h prefix
    jmp key_iend      ;so that if you disable 0e0h this will also not goto RMODE
  .endif
  .if (e0h)   ;detect wachy keys
    .if al==37h  ;Print Screen make (e0 2a e0 37)
      mov al,7eh  ;this is the new scan code (look in toascii for it's ASCII)
      mov e0h,0
    .elseif al==0b7h  ;print screen break (e0 b7 e0 aa)
      mov e0h,0
      mov al,7eh+80h
    .elseif al==46h   ;Ctrl+brk code (e0 46 e0 c6)  (ignored)
      jmp key_iend    ;
    .elseif al==0aah  ;
      jmp key_iend    ;
    .elseif al==2ah   ;
      jmp key_iend    ;
    .elseif al==0c6h  ;
      jmp key_iend    ;  other stuff I must ignore
    .elseif al==036h  ;
      jmp key_iend    ;
    .elseif al==0b6h  ;
      jmp key_iend    
    .endif
  .else
    .if al==54h
      mov al,7eh
    .elseif al==0d4h
      mov al,7eh+80h
    .endif
  .endif
  mov ebx,offset key_tab
  add ebx,eax
;when bit 7 of al =1 then al=(scan code of key released + 128)
;                 =0 then al=(scan code of key pressed)
  test al,128
  jz @f
;key released
  sub ebx,128  ;remove 80h from al
  .if e0h  ;was last byte 0e0h?
    add ebx,256    ;goto tab2
    mov byte ptr[ebx],0
    sub ebx,128    ;goto tab1
    .if !byte ptr[ebx]
      sub ebx,128  ;goto tab
      mov byte ptr[ebx],0
    .endif
  .else
    add ebx,128    ;goto tab1
    mov byte ptr[ebx],0
    add ebx,128    ;goto tab2
    .if !byte ptr[ebx]
      sub ebx,256  ;goto tab
      mov byte ptr[ebx],0
    .endif
  .endif
  jmp key_iend
@@:
;key pressed
  mov byte ptr[ebx],1
  .if e0h
    add ebx,256   ;goto tab2
    mov byte ptr[ebx],1
  .else
    add ebx,128   ;goto tab1
    mov byte ptr[ebx],1
  .endif
key_iend:  
  .if (block_e0) && (e0h)
    mov e0h,0
    jmp key_done  ;do not redirect to RMODE
  .endif
  mov e0h,0
  .if (block_e0) && (al==1dh)   ;stop CTRL from going to RMODE
    jmp key_done   ;do not redirect to RMODE
  .endif
  .if block_locks
    .if (al==3ah) || (al==45h) || (al==46h)  ;disable LOCK keys
      jmp key_done ;do not redirect to RMODE
    .endif
  .endif
gotoRMODE:
;call old int #9
  pushfd
  call old_int
  jmp @f
key_done:
  mov al,20h
  out 20h,al
@@:
  popad
  pop gs
  pop es
  pop ds
  ret
key_irqhand endp

key_init proc private
ifdef
  callp printf,"KBD INIT:"
endif
  callp getint,9
  mov wptr[old_int+4],ax
  mov dptr[old_int],edx
  mov cx,cs
  callp setint,9,cx,offset key_pmirq
ifdef debug
  callp printf,"Complete!\n"
endif
  ret
key_init endp

align 4
getchar proc
  jmp _getch_  ;it's the EXACT same
getchar endp

getch proc
  ;waits till a key is pressed
_getch_::
  .if ungetch_flg
    xor eax,eax
    mov al,ungetch_char
    mov ungetch_flg,0
    ret
  .endif
  mov ax,700h
  int 21h
  movzx eax,al
  ret
getch endp

_getch proc
  call getch
  .if !al
    call getch
    inc ah  ;100h
  .endif
  ret
_getch endp

dat db ?

getche proc
  call getch
  push ax
  mov dat,al
  invoke write,1,offset dat,1
  pop ax
  ret
getche endp

kbhit proc
  .if ungetch_flg
    mov eax,1
    ret
  .endif
  mov ah,0bh
  int 21h
  movzx eax,al
  ret
kbhit endp

key_block proc,x:byte      ;Block some keys from going to RMODE
  mov al,x                   ;this will prevent PAUSE,Ctrl^brk,Ctrl^alt^del
  .if al                     ;plus much more
    mov al,1
  .else
    mov al,0
  .endif
  mov block_e0,al
  xor eax,eax
  ret
key_block endp

;if you want to set the lights are stop the user from changing them then:
;  key_block_locks(1)
;  key_lights(x)   ;set lights
key_block_locks proc,x:byte ;Block NUM/CAPS/SCROLL LOCKS from going to RMODE
  mov al,x
  .if al
    mov al,1
  .else
    mov al,0
  .endif
  mov block_locks,al
  and byte ptr ds:[418h],0ffh XOR (5 shl 4)     ;shut off keys in BIOS
  xor eax,eax
  ret
key_block_locks endp

ungetch proc,a:byte
  .if ungetch_flg
    mov eax,ERROR
    ret
  .endif
  mov al,a
  mov ungetch_char,al
  mov ungetch_flg,1
  xor eax,eax
  ret
ungetch endp

end

[ RETURN TO DIRECTORY ]