;-----------------------------------------------------------------
; KWIKKEY.ASM  Version 1.4                                  5-28-86
; Copyright (c) 1985 by Dan Rollins
;
; This program speeds up the repeat action of the IBM PC and XT
; keyboard. After installation, a keystroke begins repeating about
; 1/4 second after the initial keystroke and the repeats occur at
; about twice the normal speed. These delay and rate parameters may
; be modified. The program uses the 55ms timer interrupt to augment
; the speed of the keyboard. The basic idea is to have the timer
; interrupt check to see if the key has been released. If not, then
; it stuffs a keystroke into the BIOS keyboard buffer. Notes: This
; program must be used with DOS 2.x or later.  It is a COM format
; program, so it must be processed by EXE2BIN.
;------------------------------------------------------------------
;=== program equates ===

REPT_DELAY    equ     5  ;number of 55ms intervals to skip before
                         ; the first repeat.  5 = 275ms = about
                         ; 1/4 second.   Use at least 2 to avoid
                         ; "key bounce"

REPT_RATE     equ     1  ;Select from:   0 = 29 repeats per second
                         ;               1 = 20 repeats per second
                         ;               2 = 16 repeats per second
                         ;               3 = 13 repeats per second
                         ;       4 or more = standard repeat rate

BIOS_DATA_SEG equ     40h  ;These addresses are listed
BUF_START     equ     1eh  ; in the Technical Reference manual
BUF_END       equ     3eh
BUF_HEAD_ADDR equ     1ah
BUF_TAIL_ADDR equ     1ch
ALT_NUM_BUF   equ     19h

FALSE         equ     0
TRUE          equ     1

;=========================

com_seg  segment
         assume  cs:com_seg, ds:com_seg
         org     100h                    ;must set up for COM file
kwikkey  proc    far
         jmp     set_up    ;get past the data and install the
                           ;interrupt handlers

;============= program data area ========
delay          db    REPT_DELAY  ;max ticks BEFORE STARTING to repeat
rate           db    REPT_RATE   ;maximum ticks BETWEEN repeats

inst_flag      dw    1234h   ;KWIKKEY signature when already installed

rep_ok         db    FALSE   ;flag turns off repeat while processing
last_key       dw    0       ;stores most recent keyboard scan code

init_delay     db    REPT_DELAY  ;remaining ticks before
                                                   ;starting to repeat
rate_delay     db    REPT_RATE   ;remaining tick between repeats

bios_kbd_int   label dword   ;DWORD so it can be used in a FAR call
bki_offset     dw    0       ; This is set to the addr of BIOS KB_INT
bki_segment    dw    0       ; at the time of installation of KWIKKEY

user_timer_int label  dword  ;used to preserve "forward chain" of user
uti_offset     dw    0       ;timer interrupt handlers.  Set in SET_UP
uti_segment    dw    0       ;procedure, this addr will normally point
                             ;to an IRET.


;---------------------------------------------------------------------
; KBD_INT
; This procedure intercepts keystrokes and sends control to normal
; BIOS KB_INT.  Its primary function is to set up for the repeat action
; that occurs in the TIMER_INT.  It checks each key that comes in, and
; resets a delay counter if it is a new keystroke.

kbd_int  proc    far
         mov     cs:rep_ok,FALSE       ;turn off repeats while
                                    ;processing
         push    ax                    ;save the registers
         push    si
         push    ds
         mov     ax,BIOS_DATA_SEG      ; set up to address
         mov     ds,ax                 ; BIOS data area
         mov     si,ds:[BUF_TAIL_ADDR] ;get addr of current
                                    ;buffer tail

        ;check special case: don't repeat ALT-numpad keystrokes

         cmp     byte ptr ds:[ALT_NUM_BUF],0 ;is one in progress?
         je      ki_10                       ; no, continue
         pushf                               ; yes, process
         call    cs:bios_kbd_int             ; the keystroke
         jmp     ki_exit                     ; exit with repeats off
ki_10:
         pushf                          ;simulate a normal interrupt
         call    cs:bios_kbd_int        ; and process the keystroke

         cmp     si,ds:[BUF_TAIL_ADDR]  ;did the tail move?
         jne     ki_20                  ; no, either a shift-key
                                        ; or a release
         mov     cs:last_key,0          ; insure no spurious repeats
         jmp     ki_exit                ; exit with repeats turned off
ki_20:                                  ; yes, process the keystroke
         mov     ax,[si]                ; get new scan code
         cmp     ax,cs:[last_key]       ; same as last time?
         mov     cs:last_key,ax         ; (save the key for next time)
         je      ki_30                  ;  yes, hardware repeat
                                        ;       just reset the rate
                                        ;  no, reset both timer delays
         mov     al,cs:delay            ;  get maximum tick count
         mov     cs:init_delay,al       ;  set delay before repeat
ki_30:
         mov     al,cs:rate             ;    get maximum tick value
         mov     cs:rate_delay,al       ;    set delay between repeats
         mov     cs:rep_ok,TRUE         ;  OK to continue repeating

ki_exit:
         pop     ds
         pop     si
         pop     ax
         iret                     ;also restores Flags of interruptee
kbd_int  endp


;---------------------------------------------------------------------
; TIMER_INT
; This procedure is executed 18.2 times per second
;
; It checks to see if a repeat is needed.  If so, it stuffs the scan
; code into the keyboard buffer, ready for the next keystroke request
;
timer_int proc far
         cmp     cs:[rep_ok],TRUE    ;are repeats blocked?
         jne     ti_exit             ;   yes, resume without repeat

         cmp     cs:init_delay,0   ;finished delaying before
                                ;first repeat?
         je      ti_10             ;   yes, check rate delay
         dec     cs:init_delay     ;   no, decrement timer
         jmp     ti_exit           ;       and resume without repeat

ti_10:
         cmp     cs:rate_delay,0   ;finished delaying between repeats?
         je      ti_20             ;   yes, do the repeat
         dec     cs:rate_delay     ;   no, decrement rate timer
         jmp     ti_exit           ;       and resume without repeat

;------- repeat the previous keystroke -----------

ti_20:
         push    ax                     ;save all registers used
         push    si
         push    ds

         mov     ax,BIOS_DATA_SEG  ;prepare to address BIOS data area
         mov     ds,ax
         mov     ax,ds:[BUF_TAIL_ADDR]  ;get current position
                                        ;in kbd buffer
         mov     si,ax                  ;we'll need this address later
         add     ax,2                   ;point to next position in buffer
         cmp     ax,BUF_END             ;past end of buffer?
         jne     ti_30                  ;  no, continue
         mov     ax,BUF_START           ;  yes, next position is the
                                        ;start
ti_30:
         cmp     ax,ds:[BUF_HEAD_ADDR]  ;if tail=head, buffer is full
         je      ti_40                  ;full, continue without repeat
         cli                            ;not full, dont allow break-in
         mov     ds:[BUF_TAIL_ADDR],ax  ;      update buffer position
         mov     ax,cs:[last_key]       ;      fetch key to repeat
         mov     [si],ax                ;      store key in buffer
         sti                            ;      interrupts ok now
ti_40:
         mov     al,cs:rate             ;get the max rate delay value
         mov     cs:rate_delay,al       ;don't repeat for a while
         pop     ds                     ;restore registers
         pop     si                     ; and exit
         pop     ax
ti_exit:
         jmp     cs:[user_timer_int]    ;continue with previously
                                        ;installed interrupt handler
timer_int endp

LAST_BYTE equ   offset $+1  ;this is the address passed to INT 27H
                            ; Notice that the code of the SET_UP
                            ; procedure does not need to be preserved

;---------------------------------------------------------------------
; SET_UP
; This routine is executed only once -- when the program is installed.
;
; It resets the vectors of the KBD_INT and the USER_TIMER_INT,
; pointing them to code within this program.  Note that the original
; vectors are saved and executed so all previously-installed interrupt
; handlers remain operational.

logo_msg db 201, 25 dup(205),187,0dh,0ah
         db 186,'    KWIKKEY  Ver. 1.4    ',186,0dh,0ah
         db 186,' (c) 1986 by Dan Rollins ',186,0dh,0ah
         db 200, 25 dup(205),188,0dh,0ah,'$'

err_msg  db 'Error: ',07,'KWIKKEY already installed',0dh,0ah,'$'

set_up   proc    near
         ;------ first, make sure KWIKKEY hasn't been installed ---
         mov     al,9
         mov     ah,35h              ;DOS GET_VECTOR service
         int     21h                 ; for interrupt 9

         cmp     es:inst_flag,1234h  ;has KWIKKEY been installed?
         jne     su_10               ; no, continue
         mov     dx,offset err_msg   ; yes, display
         mov     ah,9                ;       error
         int     21h                 ;        message
         int     20h                 ;and exit to DOS
su_10:
         mov     al,9           ;get original vector of keyboard int 9
         mov     ah,35h         ;DOS GET_VECTOR service
         int     21h
         mov     bki_segment,es ;save original address
         mov     bki_offset,bx  ; so we can resume normally
                                                  ; after intercept
         mov     dx,offset kbd_int
         mov     al,9           ;set vector for INT 9
         mov     ah,25h         ;DOS SET_VECTOR service
         int     21h

         mov     al,1ch         ;the user timer interrupt
         mov     ah,35h         ;DOS GET_VECTOR service
         int     21h
         mov     uti_segment,es ;save old address
         mov     uti_offset,bx  ; so we don't hog the timer interrupt

         mov     dx,offset timer_int
         mov     al,1ch        ;set vector to point to new TIMER_INT
         mov     ah,25h        ;DOS SET_VECTOR service
         int     21h

         ;------ display logo to indicate installation complete
         mov     dx,offset logo_msg
         mov     ah,9
         int     21h

         mov     cs:rep_ok,TRUE   ; it's OK to start repeat action

         ;------ exit to DOS, leaving the interrupt handlers resident
         mov     dx,LAST_BYTE
         int     27h
set_up   endp
kwikkey  endp
com_seg  ends
         end     kwikkey       ;must specify for COM-format file
