        NAME    msurb1
; File MSURB1.ASM for the DEC Rainbow
; Keyboard translator, by Joe R. Doupnik, Dec 1986
;  with contributions from David L. Knoell.
; edit history:
; Last edit 1 Jan 1987
; 1 Jan 1987 version 2.30

        include mssdef.h

        public  keybd, dfkey, shkey, msuinit

; some definitions
; Rainbow level 1 console definitions

fnkey   equ     100H            ; function key flag
shfkey  equ     200H            ; shift key
ctlkey  equ     400H            ; control key
cplk    equ     800H            ; caps lock key
firmwr  equ     18H             ; Bios interrupt

maxkeys equ     200                     ; maximum number of key definitions
maxstng equ     100                     ; maximum number of multi-char strings
stbuflen equ    1000                    ; length of string buffer (bytes)

verb    equ     8000h                   ; dirlist flag: use verb action table
strng   equ     4000h                   ; dirlist flag: use string action table
scan    equ     100h                    ; keycode flag: code is scan not ascii
braceop equ     7bh                     ; opening curly brace
bracecl equ     7dh                     ; closing curly brace

datas   segment public 'datas'
        extrn taklev:byte, comand:byte
        extrn shkadr:word, stkadr:word, trans:byte, kbdflg:byte
                                                ; system dependent references

;;;     System Independent local storage

tranbuf db      132 dup (?)             ; 132 byte translator work buffer
crlf    db      cr,lf,'$'
dfhelp1 db    cr,lf,' Enter key',27h,'s identification as a character',cr,lf
        db      '  or as its numerical equivalent \{b##} of ascii',cr,lf
        db      '  or as its scan code \{b##}'
        db      cr,lf,'  or as SCAN followed by its scan code',cr,lf
        db      '    where b is O for octal, X for hex, or D for decimal'
        db      ' (default).',cr,lf,'    Braces {} are optional.'
        db      cr,lf,'    Follow the identification with the new definition.'
        db      cr,lf,' or CLEAR to restore initial key settings.$'
dfaskky db      cr,lf,' Push key to be defined: $'
dfaskdf db      cr,lf,' Enter new definition: $'
verbbad db      cr,lf,' No such verb',cr,lf,'$'
strbad  db      cr,lf,' Not enough space for new string',cr,lf,'$'
keyfull db      cr,lf,' No more space to define keys',cr,lf,'$'
dfkopps db      cr,lf,' Opps! That is Kermit',27h,'s Escape Char.'
        db      ' Translation is not permitted.',cr,lf,'$'
shkmsg1 db      cr,lf,'Push key to be shown (? shows all): $'
shkmsg2 db      ' decimal is defined as',cr,lf,'$'
shkmsg3 db      cr,lf,'... more, push any key to continue ...$'
kwarnmsg db     cr,lf,' Notice: this form of Set Key is obsolete$'

ascmsg  db      ' Ascii char: $'
scanmsg db      ' Scan Code $'
strngmsg db     ' String: $'
verbmsg db      ' Verb: $'
noxmsg  db      ' Self, no translation.$'
fremsg  db      cr,lf,' Free space: $'
kyfrdef db      ' key and $'
stfrdef db      ' string definitions, $'
stfrspc db      ' string characters.',cr,lf,'$'
                                        ; translation tables
keylist dw      maxkeys dup (?)         ; 16 bit keycodes, paralled by dirlist
dirlist dw      maxkeys dup (?)         ; director {v+s} + {index | new char}
sptable dw      maxstng dup (?)         ; list of asciiz string offsets
stbuf   dw      stbuflen dup (?)        ; buffer for strings
strmax  dw      stbuf                   ; first free byte in stbuf
listptr dw      ?                       ; item number for keylist and dirlist
nkeys   dw      ?                       ; number of actively defined keys
keycode dw      ?                       ; ascii/scan code for key
kbtemp  dw      ?                       ; scratch storage for translator
brace   db      ?                       ; brace detected flag byte
oldform db      ?                       ; old form Set Key, if non-zero
verblen dw      ?                       ; length of user's verb (work temp)
kwcnt   dw      ?                       ; number of keywords (work temp)
twelve  dw      12d

;;;     End System Independent Data Area

;;;     System Dependent Data Area
;       edit dfhelp2 to include nice list of verbs for this system.
dfhelp2 db      cr,lf,' Enter either \{Kverb}  for a Kermit action verb',cr,lf
        db      ' or a replacement string  (single byte binary numbers are'
        db      ' \{b##})',cr,lf,' or nothing at all to undefine a key.'
        db      cr,lf,' Braces {} are optional, and strings maybe enclosed in'
        db      ' them.',cr,lf,' Strings may not begin with the character'
        db      ' combinations of  \k  or  \{k',cr,lf
        db      '    (start with a { brace instead).',cr,lf,lf
        db      ' Verbs are as follows:',cr,lf
        db      ' uparr, dnarr, lfarr, rtarr, kpminus, kpcoma, kpdot, kpenter,'
        db      cr,lf
        db      ' gold (same as pf1), pf1, pf2, pf3, pf4, kp0, ... kp9,'
        db      cr,lf
        db      ' upscn, dnscn, homscn, endscn, upone, dnone'
        db      cr,lf
        db      ' toggle_prn, dump, prtscr, logon, logof, break, lbreak'
        db      cr,lf
        db      ' hangup, null, DOS, help, status, exit'
        db      cr,lf,'$'
        ; Aliaskey: keys having aliases - same ascii code but more than one
        ; scan code, as on auxillary keypads. Use just scan codes with these.
        ; Alternative use: force ascii keys to report out just scan codes.
        ; Table format: high byte = scan code, low byte = ascii code.
        ; Contents are machine dependent.
aliaskey dw     (55*scan)+'*'           ; keypad asterisk [hi=scan, lo=ascii]

aliaslen equ    ($-aliaskey) shr 1      ; number of words in aliaskey table

kverbs  db      42                      ; number of table entries below
        mkeyw   'uparr',uparrw          ; max of 100 entries.
        mkeyw   'dnarr',dnarrw          ; independent of ordering and case!
        mkeyw   'lfarr',lfarr           ; mkeyw 'name',procedure entry point
        mkeyw   'rtarr',rtarr
        mkeyw   'gold',pf1
        mkeyw   'pf1',pf1
        mkeyw   'pf2',pf2
        mkeyw   'pf3',pf3
        mkeyw   'pf4',pf4
        mkeyw   'kp0',kp0
        mkeyw   'kp1',kp1
        mkeyw   'kp2',kp2
        mkeyw   'kp3',kp3
        mkeyw   'kp4',kp4
        mkeyw   'kp5',kp5
        mkeyw   'kp6',kp6
        mkeyw   'kp7',kp7
        mkeyw   'kp8',kp8
        mkeyw   'kp9',kp9
        mkeyw   'kpminus',kpminus
        mkeyw   'kpcoma',kpcoma
        mkeyw   'kpenter',kpenter
        mkeyw   'kpdot',kpdot
        mkeyw   'dnscn',prvscr
        mkeyw   'upscn',nxtscr
        mkeyw   'endscn',nxtbot
        mkeyw   'homscn',nxttop
        mkeyw   'upone',nxtlin
        mkeyw   'dnone',prvlin
        mkeyw   'toggle_prn',trnprs
        mkeyw   'prtscr',prtscn
        mkeyw   'dump',dumpscr
;;      mkeyw   'modeline',trnmod
        mkeyw   'break',sendbr
        mkeyw   'lbreak',sendbl
        mkeyw   'logon',klogon
        mkeyw   'logoff',klogof
        mkeyw   'hangup',chang
        mkeyw   'null',snull
        mkeyw   'DOS',cdos
        mkeyw   'help',cquery
        mkeyw   'status',cstatus
        mkeyw   'exit',cquit

                                ; Initialization data.
kbdinlst equ    this byte       ; Kermit initialization time keyboard setup
        mkeyw   '\kgold',scan+89 ;    mkeyw 'definition',keytype*256+keycode
        mkeyw   '\kpf1',scan+89
        mkeyw   '\kpf2',scan+92
        mkeyw   '\kpf3',scan+95
        mkeyw   '\kpf4',scan+98
        mkeyw   '\kkp0',scan+47
        mkeyw   '\kkp1',scan+50
        mkeyw   '\kkp2',scan+53
        mkeyw   '\kkp3',scan+56
        mkeyw   '\kkp4',scan+59
        mkeyw   '\kkp5',scan+62
        mkeyw   '\kkp6',scan+65
        mkeyw   '\kkp7',scan+68
        mkeyw   '\kkp8',scan+71
        mkeyw   '\kkp9',scan+74
        mkeyw   '\kkpenter',scan+86
        mkeyw   '\kkpcoma',scan+80
        mkeyw   '\kkpminus',scan+77
        mkeyw   '\kkpdot',scan+83
        mkeyw   '\kuparr',scan+39       ; VT100 cursor control keys
        mkeyw   '\kdnarr',scan+41
        mkeyw   '\klfarr',scan+45
        mkeyw   '\krtarr',scan+43
        mkeyw   '\kupscn',scan+37       ; Nxtkey  Kermit screen roll back keys
        mkeyw   '\kdnscn',scan+35       ; Prvkey
        mkeyw   '\khomscn',scan+shfkey+35 ; Home  Shift Prvkey
        mkeyw   '\kendscn',scan+shfkey+37 ; End   Shift Nxtkey
        mkeyw   '\kupone',scan+ctlkey+37  ; one line scrolls Ctrl Nxtkey
        mkeyw   '\kdnone',scan+ctlkey+35  ;                  Ctrl Prvkey
        mkeyw   '\kdos',scan+13         ; DOS is Main Screen key
        mkeyw   '\kprtscr',scan+3       ; Print screen, Print Screen key
        mkeyw   '\ktoggle_prn',scan+ctlkey+3 ; Kermit toggle prn scn,^Prt Scn
        mkeyw   '\kdump',scan+1         ; Kermit Dump Screen  DO key
        mkeyw   '\kbreak',scan+101      ; Send a Break, Break key
        mkeyw   '\klbreak',scan+shfkey+101 ; Send Long Break, Shift Break key
        mkeyw   '\khelp',scan+0         ; Help on HELP key
        mkeyw   '\kexit',scan+15        ; Exit Kermit with EXIT key
        dw      0               ; end of table marker

datas   ends

;                       Documentation
;Translating a key:
;   The translator is called to obtain keyboard input; it sends characters to
; the serial port through standard controlled echo procedures or invokes
; named procedures. It returns carry clear when its operation is completed
; for normal actions and carry set when Connect mode must be exited. When
; Connect mode is exited the just read char should be passed in Kbdflg
; to msster.asm for invoking actions such as Status, send a break,
; quit connect mode; system dependent procedure Term is responsible for this.
;
;  Principal procedures are -
;       msuinit         Initializes keyboard translator in this file when
;                       Kermit first begins. Installs dfkey and shkey as the
;                       procedures used for Set Key and Show Key. Sys Indep.
;                       Called from msx or msy init procs. System Independent.
;       keybd           Performs the translation, outputs chars to the serial
;                       port or invokes a Kermit action routine. Sys Indep.
;       dfkey           Defines a key's translation. Reads command line
;                       via Kermit's command parser comnd. System Independent.
;       shkey           Shows translation of a key. Requests user to push
;                       selected key. System Independent.
;
;       kbdinit         optional. Initializes the translation tables when
;                       Kermit starts up. Called by msuinit. System Dependent.
;       getkey          Performs the keyboard read and returns results in
;                       a standardized system independent format. Sys Depend.
;       postkey         called by active translator after obtaining a keycode.
;                       Used to provide extra local actions (keyclick) only
;                       in Connect mode (not during Set/Show key commands).
;                       Called by keybd. System dependent.
; Supporting system independent procedures are -
; shkfre (show string free space), tstkeyw (finds user's keyword in the verb
; table), insertst (insert string in buffer), remstr (delete string in buffer).
;
;   System dependent procedure Getkey reads a keycode (usually via a Bios
; call). On IBM compatible machines this yields <ah=scan code, al=ascii>
; for ordinary keys, or <ah=scan code, al=0> for special keys such as F1,
; or <ah=0, al=###> when Alt### is used.
; For any system, the canonical output form is the key's code in Keycode.
; Place the ascii code (or scan code if none) in byte Keycode and ancillary
; info (shift states plus marker bit for scan codes) in byte Keycode + 1.
;
;   Table Aliaskey is a list of scan code/ascii codes for keys which appear
; more than once on a keyboard. This list is examined to distinguish such
; aliased keys (those on an auxillary keypad) from an ordinary ascii key,
; and the aliased key is then referenced by its scan code rather than by
; the ordinary ascii code. Aliaskey is machine and keyboard dependent.
;
;    Procedure Keybd calls Getkey for the Keycode, checks list of translatable
; keys Keylist, and then either sends an ascii string (one or more characters)
; or invokes a Kermit action verb. List Dirlist indicates what kind of
; translation to do. Keybd is system independent but may contain system
; dependent special actions such as echoing keyclicks. Keybd calls system
; dependent procedure Postkey just after calling getkey so local actions
; such as keyclicks can be activated only during Connect mode operations.
;
;    Keylist is a packed but unordered list of 16 bit keycodes which need
; translation. The lower order byte holds a key code (ascii char or scan code)
; while the high byte holds a scan code marker bit (0 if ascii code in low
; byte) plus any ancillary keyboard information such as Control/Shift/Alt/Meta
; keys being held down; these are of use in Show Key presentations.
;    Dirlist parallels Keylist to provide the kind of translation, verb or
; string, in the two highest bits with the other bits holding either
; a single new replacement character or the item number in lists of verbs
; or strings. If neither verb nor strng type bits are set in a dirlist
; word then the translation is a single new character held in the lower
; eight bits of that dirlist word.
;
;    The number of key translations is assembly constant Maxkeys (def 128).
;    The maximum number of strings is assembly constant Maxstngs (def 64).
;    The maximum number of verbs is 256 and is set by building table Kverbs.
;
;   For verbs, use the Item number from the Director table Dirlist to select
; a procedure offset from the structured list Kverbs and jump to that offset.
; Most verb procedures return carry clear to stay within Connect mode.
; Verbs requiring exiting Connect mode return carry set and may set byte
; Kbdflg to a char code which will be read by msster.asm for activating a
; transient Kermit action such as send a break (Kbdflg = 'b').
; Kbdflg is stored in msster.asm (as zero initially, meaning ignore it).
; Action verb procedures are normally located in a system dependent file.
;
;   For multi-char strings, use Item number from Director table Dirlist to
; select a pointer to a string. The list of string pointers is Sptable
; (string pointer table) which holds the offset in the data segment of the
; asciiz strings stored tip-to-tail in buffer Stbuf.  Use Chrout to send each
; string character and finally return from Keybd with carry clear.
;
;   For single character replacements obtain the new character from the lower
; order byte of Director table Dirlist. If the character is Kermit's present
; escape character return from Keybd carry set to leave connect mode.
; Otherwise, send the character via Chrout and return from Keybd carry clear.

; Keylist table format:
;    7 bits   1 bit   8 bits
; +----------+----+------------+ scan bit = 1 if key's code is non-ascii
; | aux info |scan| key's code | aux info = system dependent, used only to
; +----------+----+------------+            help identify key
;
; Dirlist table format            v s   meaning
;   1   1      14 bits            0 0   copy out one byte translation
; +---+---+--------------------+  1 0   copy out multi-char string number Item
; | v | s | item # or new char |  0 1   do action verb number Item
; +---+---+--------------------+  1 1   (not used)
;
; Table kverbs is organized by macro mkeyw as -
;       kverbs  db      number of table entries
;       (each entry is in the form below:)
;               db      number of bytes in verbname
;               db      'verbname'              variable length
;               db      '$'                     for printing
;               dw      value                   offset of procedure
;
;
;   Dfkey defines a key to be itself (undefines it) or a single replacement
; character or a character string or a Kermit action verb. Dfkey requires
; a command line so that it may be invoked by Take files but can be forced
; to prompt an interactive user to push a key. Syntax is discussed below.
; Note that redefined keys have their old definitions cleared so that
; old string space is reclaimed automatically.
;
;   Shkey displays a key's definition and the user is asked to push the
; selected key. The free space for strings is always shown afterward. See
; below for syntax.
;
;   Kbdinit is an optional routine called when Kermit starts up. It fills in
; the translation tables with desirable default values to save having to
; use long mskermit.ini files. The default values are stored in a structured
; table similar to (but not the same as) Dfkey's command lines; the keycode
; values are preset by hand to 16 bit numbers.

;Defining a key:
; Command is SET KEY <key ident><whitespace><definition>
;
; <key ident> is
;               a single ordinary ascii char or
;               the numerical equivalent of an ascii char or
;               a Scan Code written as a number or
;               keyword SCAN followed by a number.
;               ?       Displays help message.
;       Numbers and Binary codes are of the form
;               \123    a decimal number
;               \o456   an octal number         base letters o, d, x can be
;               \d213   a decimal number        upper or lower case
;               \x0d    a hex number
;               \{b###}  braces around above material following slash.
;
; <whitespace> is one or more spaces and or tabs.
;
; <definition> is
;       missing altogether which "undefines" a key.
;       \Kverb          for a Kermit action verb; upper or lower case K is ok
;       \{Kverb}        ditto. Verb is the name of an action verb.
;       text            a string with allowed embedded whitespace and embedded
;                       binary chars as above. This kind of string may not
;                       commence with sequences \K or \{K; use braces below.
;       {text}          string confined to material within but excluding
;                       the braces. Note, where the number of opening braces
;                       exceeds the number of closing braces the end of line
;                       terminates the string: {ab{}{{c}d ==> ab{}{{c}d
;                       but  {ab}{{c}d ==> ab.
;       ?               Displays help message and lists all action verbs.
;
;       If Set Key is given interactively, as opposed to within a Take
;       file, the system will prompt for inputs if none is on the command
;       line. The response to Push key to be defined cannot be edited.
;
;       Text which reduces to a single replacement character is put into a
;       table separate from the multi-character strings (maxstng of these).
;       A key may be translated into any single 8 bit code.
;
;       Comments can follow a Kermit action verb or a braced string; no
;       semicolon is required since all are stripped out by the Take file
;       reader before the defining text is seen by SET KEY.
;
;       The current Kermit escape character cannot be translated without
;       subtrafuge.
;
;       Examples:
;               Set Key q z
;                               makes key q send character z
;               Set Key \7 \27[0m
;                               makes key Control G send the four byte
;                               string  ESC [ 0 m
;               Set Key q
;                               undefines key q so it sends itself (q) again.
;               Set Key \2349 \kexit
;                               defines IBM Alt-X to invoke the leave connect
;                               mode verb "exit" (Kermit's escape-char ^] C).
;               Set Key \x0c Login \{x0d}myname\{x0d}mypass\x0d
;                               defines Control L to send the string
;                               Login <cr>myname<cr>mypass<cr>
;
; Alternative Set Key syntax for backward compatibility with previous versions
;       The same forms as above except the key identification number must
;       be decimal and must Not have a leading backslash. Example:
;       Set Key Scan 59 This is the F1 key
;
;       If the definition is omitted it may be placed on the following line;
;       if that line is also empty the key is undefined (defined as Self).
;       A warning message about obsolete syntax will be given followed by
;       the key's modern numerical value and new definition. Only "special"
;       keys (those not producing ascii codes) are compatible with this
;       translator.
;
;Showing a key:
; Command is SHOW KEY <cr>
; System prompts user to press a key and shows the definition plus the
; free space for strings. Query response results in showing all definitions.
;                       End Documentation

code    segment public 'code'
                ; system independent external items
        extrn   comnd:near, prompt:near                 ; in msscmd
        extrn   isfile:near, strlen:near, strcpy:near, prtasz:near ; in mssfil
        extrn   cnvlin:near, katoi:near, decout:near    ; in mssset
                ; system dependent external items
        extrn   beep:near, prtchr:near                  ; in msxrb
                ; these are system dependent action verbs, in msxrb
        extrn   uparrw:near, dnarrw:near, rtarr:near, lfarr:near
        extrn   pf1:near, pf2:near, pf3:near, pf4:near, kp0:near, kp1:near
        extrn   kp2:near, kp3:near, kp4:near, kp5:near, kp6:near, kp7:near
        extrn   kp8:near, kp9:near, kpminus:near, kpcoma:near, kpenter:near
        extrn   kpdot:near
        extrn   chrout:near, cstatus:near, cquit:near, cquery:near
        extrn   prvlin:near, nxtlin:near, nxtbot:near
        extrn   nxttop:near, prvscr:near, nxtscr:near, trnprs:near
        extrn   sendbr:near, sendbl:near, snull:near, dumpscr:near
        extrn   chang:near, klogon:near, klogof:near, cdos:near
        extrn   prtscn:near     ;;; trnmod:near

        assume  cs:code, ds:datas, es:datas

; Begin system independent Keyboard Translator code

; MSUINIT performs Kermit startup initialization for this file.
; Note, shkadr and stkadr are pointers tested by Set/Show Key calls. If they
; are not initialized here then the older Set/Show Key procedures are called.
MSUINIT PROC    NEAR                    ; call from msx/msy init code
        call    kbdinit                 ; optional: init translator tables
        mov     shkadr,offset shkey     ; declare keyboard translator present
        mov     stkadr,offset dfkey     ; via Show and Set Key proc addresses
        ret
MSUINIT ENDP

; Call Keybd to read a keyboard char (just returns carry clear if none) and
; 1) send the replacement string (or original char if not translated)
;    out the serial port, or
; 2) execute a Kermit action verb.
; Returns carry set if Connect mode is to be exited, else carry clear.
; Modifies registers ax and bx.
KEYBD   PROC    NEAR                    ; active translator
        call    getkey                  ; read keyboard
        jnc     keybd1                  ; nc = data available
        jmp     keybdx                  ; else just return carry clear
keybd1: call    postkey                 ; call system dependent post processor
        cmp     nkeys,0                 ; is number of keys defined = 0?
        jz      keybd3                  ; z = none defined
        push    di                      ; search keylist for this keycode
        push    cx                      ; save some registers
        push    es
        mov     di,offset keylist       ; list of defined keycode words
        mov     ax,keycode              ; present keycode
        mov     cx,nkeys                ; number of words to examine
        push    ds
        pop     es                      ; make es:di point to datas segment
        cld
        repne   scasw                   ; find keycode in list
        pop     es                      ; restore regs
        pop     cx
        je      keybd1b                 ; e = found, work with present di
        pop     di                      ; restore original di
        test    keycode,scan            ; is this a scan code?
        jz      keybd3                  ; z = no, it's ascii, use al as char
        call    beep                    ; say key is a dead one
        clc
        ret                             ; and exit with no action

keybd1b:sub     di,2                    ; correct for auto increment
        sub     di,offset keylist       ; subtract start of list ==> listptr
        mov     ax,dirlist[di]          ; ax = contents of director word
        pop     di                      ; restore original di
                                        ; dispatch on Director code
        test    ax,verb                 ; verb only?
        jnz     keyvb                   ; e = yes
        test    ax,strng                ; multi-char string only?
        jnz     keyst                   ; e = yes, else single char & no xlat.
                                        ;
                                        ; do single CHAR output (char in al)
keybd3: cmp     al,trans.escchr         ; Kermit's escape char?
        je      keybd3a                 ; e = yes, handle separately
        call    chrout                  ; transmit the char
        clc                             ; return success
        ret
keybd3a:stc                             ; set carry for jump to Quit
        ret

keyvb:  and     ax,not(verb+strng)      ; VERB (ax=index, remove type bits)
        mov     bx,offset kverbs        ; start of verb table
        cmp     al,byte ptr [bx]        ; index > number of entries?
        jae     keybdx                  ; ae = illegal, indices start at 0
        inc     bx                      ; bx points to first entry
        push    cx                      ; save reg
        mov     cx,ax                   ; save the index in cx
        inc     cx                      ; counter, indices start at 0
keyvb1: mov     al,byte ptr [bx]        ; cnt value
        xor     ah,ah
        add     ax,4                    ; skip text and '?' and value word
        add     bx,ax                   ; look at next slot
        loop    keyvb1                  ; walk to correct slot
        sub     bx,2                    ; backup to value field
keyvb2: pop     cx                      ; restore reg
        mov     bx,[bx]                 ; get value field of this slot
        cmp     bx,0                    ; jump address defined?
        je      keybdx                  ; e = no, skip the action
        jmp     bx                      ; perform the function

keyst:  and     ax,not(verb+strng)      ; STRING (ax=index, remove type bits)
        shl     ax,1                    ; convert to word index
        push    si                      ; save working reg
        mov     si,ax                   ; word subscript in table
        mov     si,sptable[si]          ; memory offset of selected string
        cmp     si,0                    ; is there a string pointer present?
        je      keyst3                  ; e = no, skip operation
keyst2: cld                             ; scan forward
        lodsb                           ; get new string char into al
        cmp     al,0                    ; at terminator?
        je      keyst3                  ; e = yes, quit now
        push    si                      ; save si and cx around call
        push    cx
        call    chrout                  ; send out the char in al
        pop     cx                      ; recover regs
        pop     si
        jmp     short keyst2            ; loop through whole string
keyst3: pop     si                      ; restore reg

keybdx: clc                             ; return success
        ret
KEYBD   ENDP

; SET KEY - define a key   (procedure dfkey)
; SET KEY <key ident><whitespace><new meaning>
; Call from Kermit level. Returns as ret if failure or as rskp if success.
;
DFKEY   PROC    NEAR                    ; define a key as a verb or a string
        mov     keycode,0               ; clear keycode
        mov     oldform,0               ; say no old form Set Key yet
        mov     dx,offset tranbuf       ; our work space
        mov     word ptr tranbuf,0      ; insert terminator
        mov     bx,offset dfhelp1       ; first help message
        mov     ah,cmfile               ; parse a word
        call    comnd                   ; get key code or original ascii char
         nop
         nop
         nop
        or      ah,ah                   ; any text given?
        jnz     dfkey12                 ; nz = yes, so don't consider prompts
                                        ; interactive key request
        cmp     taklev,0                ; in a Take file?
        je      dfkey10                 ; e = no, prompt for keystroke
        jmp     dfkey0                  ;  else say bad syntax
dfkey10:mov     ah,prstr
        mov     dx,offset dfaskky       ; ask for key to be pressed
        int     dos
dfkey11:call    getkey                  ; read key ident from keyboard
        jc      dfkey11                 ; c = no response, wait for keystroke
        mov     ah,prstr                ; display cr/lf
        mov     dx,offset crlf
        int     dos
        call    shkey0                  ; show current definition (in SHKEY)
        jmp     dfkey1e                 ; prompt for and process definition

dfkey12:                                ; Look for word SCAN and ignore it
        mov     dx,word ptr tranbuf     ; get first two characters
        or      dx,2020h                ; map upper to lower case
        cmp     dx,'cs'                 ; first two letters of word "scan"?
        je      dfkey                   ; e = yes, skip the word
        cmp     dx,'lc'                 ; first two letters of word "clear"?
        je      dfkeyc                  ; e = yes, reinit keyboard
        cmp     ah,1                    ; number of characters received
        ja      dfkey1                  ; a = more than one, decode
        mov     ah,byte ptr tranbuf     ; get the single char
        mov     byte ptr keycode,ah     ; store as ascii keycode
        jmp     dfkey1b                 ; go get definition

dfkey0: mov     dx,offset dfhelp1       ; say bad definition command
        mov     ah,prstr
        int     dos
        jmp     rskp

dfkeyc:                                 ; CLEAR key defs, restore startup defs
        mov     cx,maxkeys              ; size of keycode tables
        push    es                      ; save register
        push    ds
        pop     es                      ; make es point to datas segment
        mov     ax,0                    ; null, value to be stored
        mov     di,offset dirlist       ; director table
        cld
        rep     stosw                   ; clear it
        mov     cx,maxkeys
        mov     di,offset keylist       ; keycode table
        rep     stosw                   ; clear it
        pop     es                      ; recover register
        mov     strmax,offset stbuf     ; clear string buffer
        mov     stbuf,0                 ;
        mov     bx,offset sptable
        mov     word ptr [bx],0         ; clear table of string pointers
        mov     nkeys,0                 ; clear number of defined keys
        call    msuinit                 ; restore startup definitions
        jmp     rskp
                                        ; Multi-char key identification
dfkey1: mov     si,offset tranbuf       ; point to key ident text
        cmp     byte ptr [si],'0'       ; is first character numeric?
        jb      dfkey1a                 ; b = no
        cmp     byte ptr [si],'9'       ; in numbers?
        ja      dfkey1a                 ; a = no
        mov     keycode,scan            ; setup keycode for scan value
        mov     dx,si                   ; get length of string in cx
        call    strlen
        push    ds
        pop     es                      ; make es point to datas segment
        push    si
        add     si,cx                   ; point at string terminator
        mov     di,si
        inc     di                      ; place to store string (1 byte later)
        inc     cx                      ; include null terminator
        std                             ; work backward
        rep     movsb                   ; move string one place later
        cld
        pop     si
        mov     byte ptr [si],'\'       ; make ascii digits into \nnn form
        mov     oldform,0ffh            ; set old form flag
        mov     dx,offset kwarnmsg      ; tell user this is old form
        mov     ah,prstr
        int     dos
dfkey1a:call    katoi                   ; convert ascii number to binary in ax
        jc      dfkey0                  ; c = no number converted
        or      keycode,ax              ; store in keycode

dfkey1b:                                ; Get Definition proper
        test    oldform,0ffh            ; old form Set Key active?
        jz      dfkey1f                 ; z = no
        mov     bx,offset tranbuf       ; get new definition on main cmd line
        mov     word ptr [bx],0         ; insert terminator
        mov     dx,offset dfhelp2       ; help for definition of key
        mov     ah,cmtxt                ; read rest of line into tranbuf
        call    comnd
         nop                            ; allow null definitions
         nop
         nop
        or      ah,ah                   ; char count zero?
        jz      dfkey1e                 ; z = zero, prompt for definition
        jmp     dfkey1g                 ; process definition

dfkey1e:mov     dx,offset dfaskdf       ; prompt for definition string
        call    prompt                  ; Kermit prompt routine
        mov     comand.cmccnt,1         ; make parser happy with null responses
dfkey1f:mov     bx,offset tranbuf       ; get new definition
        mov     word ptr [bx],0         ; insert terminator
        mov     dx,offset dfhelp2       ; help for definition of key
        mov     ah,cmtxt                ; read rest of line into tranbuf
        call    comnd
         jmp r                          ; exit now on ^C from user
         nop
dfkey1g:xchg    ah,al                   ; put byte count in al
        xor     ah,ah                   ; clear high byte
        mov     kbtemp,ax               ; and save count in kbtemp
        mov     ah,cmcfm                ; get a confirm
        call    comnd
         jmp    r                       ; none so declare parse error
         nop                            ; round out to three bytes
        mov     cx,kbtemp               ; string length
        jcxz    dfkey2                  ; z = empty string
        push    si
        push    di
        mov     si,offset tranbuf       ; strip leading white space
dfkey1c:cld                             ; work forward
        lodsb                           ; read string char into al
        dec     cx                      ; number of chars left to read
        cmp     al,' '                  ; a space?
        je      dfkey1c                 ; e = yes, keep going
        cmp     al,tab                  ; tab?
        je      dfkey1c                 ; e = yes, keep going
        dec     si                      ; offset inc si in lodsb
        add     cx,2                    ; include terminator, offset dec above
        jcxz    dfkey1d                 ; z = nothing to move
        mov     di,offset tranbuf       ; destination is start of buffer
        push    ds
        pop     es
        cld
        rep     movsb                   ; copy text part of string
dfkey1d:pop     di
        pop     si

dfkey2:                                 ; Examine translation
        mov     al,trans.escchr         ; current escape char (port dependent)
        cmp     al,byte ptr keycode     ; is this Kermit's escape char?
        jne     dfkey2a                 ; ne = no
        test    keycode,scan            ; see if scan code
        jnz     dfkey2a                 ; nz = scan, so not ascii esc char
        mov     dx,offset dfkopps       ; Opps! msg
        mov     ah,prstr                ; complain and don't redefine
        int     dos
        jmp     rskp

dfkey2a:push    di                      ; get a director code for this key
        push    cx
        mov     di,offset keylist       ; list of keycodes
        mov     cx,nkeys                ; number currently defined
        mov     ax,keycode              ; present keycode
        jcxz    dfkey2b                 ; cx = 0 means none defined yet
        cld
        push    ds
        pop     es
        repne   scasw                   ; is current keycode in the list?
        jne     dfkey2b                 ; ne = not in list
        sub     di,2                    ; correct for auto increment
        sub     di,offset keylist
        mov     listptr,di              ; list pointer for existing definition
        pop     cx
        pop     di
        jmp     dfkey3                  ; go process definition

dfkey2b:pop     cx                      ; key not currently defined so
        pop     di                      ;  make a new director entry for it
        mov     bx,nkeys                ; number of keys previously defined
        cmp     bx,maxkeys              ; enough space?
        jae     dfkey2c                 ; ae = no, complain
        shl     bx,1                    ; count words
        mov     listptr,bx              ; index into word list
        mov     ax,keycode              ; get key's code
        mov     keylist[bx],ax          ; store it in list of keycodes
        mov     dirlist[bx],0           ; clear the new director entry
        inc     nkeys                   ; new number of keys
        jmp     dfkey3                  ; go process definition

dfkey2c:mov     dx,offset keyfull       ; say key space is full already
        mov     ah,prstr
        int     dos
        jmp     rskp                    ; tell parser we are happy
; listptr has element number in keylist or dirlist; keycode has key's code.

; Parse new definition. First look for Kermit verbs as a line beginning
; as \K or \{K. Otherwise, consider the line to be a string.
; In any case, update the Director table for the new definition.

dfkey3: mov     brace,0                 ; assume not using braces
        mov     si,offset tranbuf       ; start of definition text
        cmp     byte ptr [si],'\'       ; starts with escape char?
        jne     dfkey5                  ; ne = no, so we have a string
        inc     si                      ; skip the backslash
        cmp     byte ptr [si],braceop   ; starts with \{?
        jne     dfkey3a                 ; ne = no
        inc     si                      ; skip the opening brace
        mov     brace,bracecl           ; expect closing brace
dfkey3a:cmp     byte ptr [si],'K'       ; starts with \{K or \K?
        je      dfkey3b                 ; e = yes
        cmp     byte ptr [si],'k'       ; starts as \{k or \k?
        jne     dfkey5                  ; ne = no, then it's a string
dfkey3b:inc     si                      ; yes, skip the K too
                                        ; Kermit action VERBS
        push    si                      ; save verb name start address
dfkey4a:cld
        lodsb                           ; scan til closing brace or w/s or end
        cmp     al,0                    ; premature end?
        je      dfkey4b                 ; e = yes, accept without brace
        cmp     al,brace                ; closing brace?
        je      dfkey4b                 ; e = yes
        cmp     al,spc                  ; white space or control char?
        ja      short dfkey4a           ; a = no, so not at end yet
dfkey4b:mov     byte ptr[si-1],0        ; insert null terminator
        pop     si                      ; recover start address
        call    tstkeyw                 ; find keyword, kw # returned in kbtemp
        jc      dfkey4d                 ; c = no keyword found, complain
        call    remstr                  ; clear old string, if string
        mov     ax,kbtemp               ; save keyword number
        and     ax,not(verb+strng)      ; clear verb / string field
        or      ax,verb                 ; set verb ident
        mov     si,listptr
        mov     dirlist[si],ax          ; store info in Director table
        jmp     dfkey7                  ; show results and return success

dfkey4d:mov     dx,offset verbbad       ; say no such verb
        mov     ah,prstr
        int     dos
        jmp     rskp

; Here we are left with the definition string; si points to its start, and
; kbtemp holds its length (number of bytes). Null termination. If the string
; begins with an opening brace it terminates on a matching closing brace
; or the end of line, whichever occurs first.
; Null length strings mean define key as Self.
                                        ; STRING definitions
dfkey5: mov     si,offset tranbuf       ; start of string text
        cmp     byte ptr [si],braceop   ; opening brace?
        jne     dfkey5f                 ; ne = no, ignore brace-matching code
        mov     dl,braceop              ; opening brace (we count them up)
        mov     dh,bracecl              ; closing brace (we count them down)
        mov     kbtemp,0                ; we are at brace level 0
dfkey5b:cld                             ; search forward
        lodsb                           ; read a string char
        cmp     al,0                    ; at end of string?
        je      dfkey5d                 ; e = yes, we are done
        cmp     al,dl                   ; an opening brace?
        jne     dfkey5c                 ; ne = no
        inc     kbtemp                  ; yes, increment brace level
        jmp     short dfkey5b           ;  and continue scanning
dfkey5c:cmp     al,dh                   ; closing brace?
        jne     dfkey5b                 ; ne = no, continue scanning
        dec     kbtemp                  ; yes, decrement brace level
        cmp     kbtemp,0                ; at level 0?
        jne     dfkey5b                 ; ne = no, #opening > #closing braces
        mov     byte ptr [si-1],0       ; plant terminator on closing brace
dfkey5d:mov     si,offset tranbuf+1     ; skip opening brace

dfkey5f:call    remstr                  ; first, clear old string, if any
        mov     di,offset tranbuf       ; si=source, di=dest, convert in-place
        call    cnvlin                  ; convert numbers, cx gets line length
        mov     si,offset tranbuf       ; provide address of new string
        cmp     cx,1                    ; just zero or one byte to do?
        jbe     dfkey6                  ; e = yes, do as a char
        call    insertst                ; insert new string, returns reg cx.
        jc      dfkey5h                 ; c = could not do insert
        mov     si,listptr              ; cx has type and string number
        mov     dirlist[si],cx          ; update Director table from insertst
        jmp     dfkey7                  ; show results and return success

dfkey5h:mov     dx,offset strbad        ; display complaint
        mov     ah,prstr
        int     dos
dfkeyx: jmp     rskp

                ; define SINGLE CHAR replacement or CLEAR a key definition.
                ; cx has char count 1 (normal) or 0 (to undefine the key).
dfkey6: jcxz    dfkey6c                 ; z = cx= 0, clear definition
        mov     al,byte ptr [si]        ; get first byte from definition
        xor     ah,ah                   ; set the type bits to Char
        mov     si,listptr
        mov     dirlist[si],ax          ; store type and key's new code
        jmp     dfkey7                  ; return success

dfkey6c:push    si                      ; clear a definition,
        push    di                      ; listptr points to current def
        mov     si,listptr              ; starting address to clear
        add     si,offset dirlist
        mov     di,si                   ; destination
        add     si,2                    ; source is next word
        mov     cx,nkeys                ; current number of keys defined
        add     cx,cx                   ; double for listptr being words
        sub     cx,listptr              ; cx = number of words to move
        shr     cx,1                    ; convert to actual number of moves
        jcxz    dfkey6d                 ; z = none, just remove last word
        push    es
        push    ds
        pop     es                      ; make es:di point to datas segment
        cld
        push    cx                      ; save cx
        rep     movsw                   ; move down higher list items
        pop     cx
        mov     si,listptr              ; do keylist too, same way
        add     si,offset keylist
        mov     di,si
        add     si,2
        rep     movsw
        pop     es
dfkey6d:mov     si,nkeys                ; clear old highest list element
        shl     si,1                    ; address words
        mov     dirlist[si],0           ; null the element
        mov     keylist[si],0           ; null the element
        dec     nkeys                   ; say one less key defined now
        pop     di                      ; restore saved registers
        pop     si

dfkey7: cmp     taklev,0                ; Finish up. In a Take file?
        jne     dfkey7a                 ; ne = yes, don't echo results
        mov     ah,prstr                ; display cr/lf
        mov     dx,offset crlf
        int     dos
        call    shkey0                  ; show new definition (in SHKEY)
        call    shkfre                  ; show free string space
dfkey7a:jmp     rskp                    ; return success
DFKEY   ENDP

; SHOW KEY <cr> command. Call from Kermit level. Vectored here by SHOW
; command. Replaces obsolete procedure in msx---.
; Prompts for a key and shows that key's (or all if ? entered) keycode,
; definition, and the key definition free space remaining.

SHKEY   PROC    NEAR                    ; Show key's definition command
        mov     ah,cmcfm                ; get a confirm
        call    comnd
         nop                            ; ignore any additional text
         nop
         nop
        push    bx
        mov     dx,offset shkmsg1       ; ask for original key
        mov     ah,prstr
        int     dos
shky0:  call    getkey                  ; read keyboard, output to keycode
        jc      shky0                   ; wait for a key (c = nothing there)
        cmp     byte ptr keycode,'?'    ; query for all keys?
        jne     shky0a                  ; ne = no, not a query
        test    keycode,scan            ; is this a scan code, vs ascii query?
        jz      shky0c                  ; z = no Scan, so it is a query

shky0a: mov     ah,prstr                ; show single key. Setup display
        mov     dx,offset crlf
        int     dos
        call    shkey0                  ; show just one key
shky0b: call    shkfre                  ; show free string space
        jmp     shkeyx                  ; exit

shky0c: mov     cx,nkeys                ; Show all keys. nkeys = number defined
        jcxz    shky0b                  ; z = none to show
        mov     si,offset keylist       ; list of definitions
        push    si                      ; save pointer
shky1:  pop     si                      ; recover pointer
        cld
        lodsw                           ; get a keycode
        push    si                      ; save pointer
        push    cx                      ; save counter
        mov     keycode,ax              ; save new keycode
        mov     ah,prstr
        mov     dx,offset crlf
        int     dos
        call    shkey0                  ; show this keycode

        pop     cx                      ; pause between screens, recover cntr
        push    cx                      ; save it again
        dec     cx                      ; number yet to be shown
        jcxz    shky1b                  ; z = have now shown all of them
        mov     ax,nkeys                ; number of defined keys
        sub     ax,cx                   ; minus number yet to be displayed
        xor     dx,dx                   ; clear extended numerator
        div     twelve                  ; two lines per definition display
        or      dx,dx                   ; remainder zero (12 defs shown)?
        jnz     shky1b                  ; nz = no, not yet so keep going
        mov     ah,prstr
        mov     dx,offset shkmsg3       ; "push any key to continue" msg
        int     dos
shky1a: call    getkey                  ; get any key
        jc      shky1a                  ; c = nothing at keyboard yet, wait
shky1b: pop     cx                      ; resume loop
        loop    shky1
        pop     si                      ; clean stack
        call    shkfre                  ; show free string space
        jmp     shkeyx                  ; exit

                ; show key worker routine, called from above
                                        ; SHKEY0 called by DFKEY just above
SHKEY0: test    keycode,scan            ; scan code?
        jz      shkey1                  ; z = no, regular ascii

                                        ; SCAN codes
        mov     dx,offset scanmsg       ; say Scan Code:
        mov     ah,prstr
        int     dos
        mov     ah,conout
        mov     dl,'\'                  ; add backslash before number
        int     dos
        mov     ax,keycode              ; get key's code again
        call    decout                  ; display 16 bit decimal keycode
        jmp     shkey2                  ; go get definition

shkey1: mov     dx,offset ascmsg        ; say ASCII CHAR
        mov     ah,prstr
        int     dos
        mov     dl,byte ptr keycode     ; get ascii code (al part of input)
        mov     ah,conout
        cmp     dl,spc                  ; control code?
        jae     shkey1a                 ; ae = no
        push    dx                      ; save char
        mov     dl,5eh                  ; show caret first
        int     dos
        pop     dx
        add     dl,'A'-1                ; ascii bias
shkey1a:cmp     dl,del                  ; DEL?
        jne     shkey1b                 ; ne = no
        mov     dl,'D'                  ; spell out DEL
        int     dos
        mov     dl,'E'
        int     dos
        mov     dl,'L'
shkey1b:int     dos
        mov     dl,spc                  ; add a couple of spaces
        int     dos
        int     dos
        mov     dl,'\'                  ; add backslash before number
        int     dos
        mov     ax,keycode              ; show 16 bit keycode in decimal
        call    decout                  ; and go get definiton

                                        ; Display defintion
shkey2: mov     dx,offset shkmsg2       ; intermediate part of reply
        mov     ah,prstr                ; " is defined as "
        int     dos
        push    di                      ; get a director code for this key
        push    cx
        mov     di,offset keylist       ; list of keycodes
        mov     cx,nkeys                ; number currently defined
        jcxz    shkey2a                 ; z = none
        mov     ax,keycode              ; present keycode
        push    ds
        pop     es                      ; use datas segment for es:di
        cld
        repne   scasw                   ; is current keycode in the list?
        jne     shkey2a                 ; ne = not in list
        sub     di,2                    ; correct for auto increment
        sub     di,offset keylist
        mov     listptr,di              ; list pointer for existing definition
        pop     cx
        pop     di
        jmp     shkey3                  ; go process definition

shkey2a:pop     cx
        pop     di
        mov     dx,offset noxmsg        ; say Self (no translation)
        mov     ah,prstr
        int     dos
        ret                             ; return to main show key loop

shkey3:                                 ; translations, get kind of.
        mov     si,listptr
        test    dirlist[si],verb        ; defined as verb?
        jnz     shkey6                  ; nz = yes, go do that one
        test    dirlist[si],strng       ; defined as string?
        jz      shkey3a                 ; z = no
        jmp     shkey8                  ; yes, do string display
shkey3a:
        mov     dx,offset ascmsg        ; CHAR. say 'Ascii char:'
        mov     ah,prstr
        int     dos
        mov     ax,dirlist [si]         ; get type and char
        mov     dl,al                   ; put char here for display
        push    ax                      ; save here too
        mov     ah,conout
        cmp     dl,spc                  ; control code?
        jae     shkey4                  ; ae = no
        push    dx
        mov     dl,5eh                  ; show caret
        int     dos
        pop     dx
        add     dl,'A'-1                ; add ascii bias
shkey4: cmp     dl,del                  ; DEL?
        jne     shkey4a                 ; ne = no
        mov     dl,'D'                  ; spell out DEL
        int     dos
        mov     dl,'E'
        int     dos
        mov     dl,'L'
shkey4a:int     dos
        mov     dl,spc                  ; add a couple of spaces
        mov     ah,conout
        int     dos
        int     dos
        mov     dl,'\'                  ; add backslash before number
        int     dos
        pop     ax                      ; recover char
        xor     ah,ah                   ; clear high byte
        call    decout                  ; show decimal value
        ret                             ; return to main show key loop

shkey6: mov     ah,prstr                ; VERB
        mov     dx,offset verbmsg       ; say 'verb'
        int     dos
        mov     si,listptr              ; get verb index from director
        mov     dx,dirlist[si]
        and     dx,not(verb+strng)      ; remove type bits, leaves verb number
        mov     bx,offset kverbs        ; table of verbs & actions
        mov     al,byte ptr [bx]        ; number of keywords
        xor     ah,ah
        dec     ax
        mov     kwcnt,ax                ; save number of last one here
        cmp     dx,ax                   ; asking for more than we have?
        ja      shkeyx                  ; a = yes, exit bad
        inc     bx                      ; point to first slot
        mov     cx,0                    ; current slot number
shkey6b:cmp     cx,dx                   ; this slot?
        je      shkey6c                 ; e = yes, print the text part
        ja      shkeyx                  ; a = beyond, exit bad
        mov     al,byte ptr [bx]        ; get cnt (keyword length)
        xor     ah,ah
        add     ax,4                    ; skip over '$' and two byte value
        add     bx,ax                   ; bx = start of next keyword slot
        inc     cx                      ; current keyword number
        jmp     short shkey6b           ; try another
shkey6c:inc     bx                      ; look at text field
        mov     dx,bx                   ; offset for printing
        mov     ah,prstr
        int     dos
        mov     ah,conout
        mov     dl,spc                  ; add a couple of spaces
        int     dos
        int     dos
        mov     dl,'\'                  ; show verb name as \Kverb
        int     dos
        mov     dl,'K'
        int     dos
        mov     ah,prstr
        mov     dx,bx                   ; show name part again
        int     dos
        ret                             ; return to main show key loop

shkey8: mov     ah,prstr                ; STRING
        mov     dx,offset strngmsg      ; say String:
        int     dos
        mov     si,listptr              ; get index from director
        mov     bx,dirlist[si]
        and     bx,not(verb+strng)      ; remove type bits
        shl     bx,1                    ; index words
        mov     dx,sptable[bx]          ; table of string offsets
        call    strlen                  ; get length in cx
        mov     si,dx                   ; starting offset in si
        mov     ah,conout
shkey8a:cld
        lodsb                           ; get a byte
        cmp     al,spc                  ; control code?
        jae     shkey8b                 ; ae = no
        push    ax
        mov     dl,5eh                  ; show caret first
        int     dos
        pop     ax
        add     al,40h                  ; convert to printable for display
shkey8b:mov     dl,al
        int     dos                     ; display it
        loop    shkey8a                 ; do another
        ret                             ; return to main show key loop

shkeyx: pop     bx                      ; restore reg
        jmp     rskp                    ; return success
SHKEY   ENDP

;;;     keyboard translator local support procedures, system independent

; Tstkeyw checks text word pointed to by si against table of keywords (pointed
; to by kverbs, made by mkeyw macro); returns in bx either action value or 0.
; Returns in kbtemp the number of the keyword and carry clear, or if failure
; returns kbtemp zero and carry set.
; Keyword structure is:         db      cnt     (length of string 'word')
;                               db      'word'  (keyword string)
;                               db      '$'     (printing terminator)
;                               dw      value   (value returned in bx)
; Make these with macro mkeyw such as   mkeyw 'test',15   with the list of
; such keywords headed by a byte giving the number of keywords in the list.
tstkeyw proc    near
        push    ax
        push    cx
        push    si
        mov     verblen,0               ; verblen will hold verb length
        push    si                      ; save user's verb pointer
tstkw1: cld
        lodsb                           ; get a verb character
        cmp     al,spc                  ; verbs are all non-spaces and above
        jbe     tstkw2                  ; be = done (space or control char)
        inc     verblen                 ; count verb length
        jmp     short tstkw1            ; printable char, look for more
tstkw2: pop     si                      ; pointer to verb
        mov     bx,offset kverbs        ; table of Kermit verb keywords
        mov     al,byte ptr [bx]        ; number of keywords
        xor     ah,ah
        mov     kwcnt,ax                ; save number of keywords here
        inc     bx                      ; point bx to first slot
        mov     kbtemp,0                ; remember which keyword

tstkw3:                                 ; match table keyword and text word
        mov     cx,verblen              ; length of user's verb
        cmp     byte ptr [bx],cl        ; compare length vs table keyword
        jne     tstkw4                  ; ne = not equal lengths, try another
        push    si                      ; lengths match, how about spelling?
        push    bx
        inc     bx                      ; point at start of keyword
tstkw3a:mov     ah,byte ptr [bx]        ; keyword char
        mov     al,byte ptr [si]        ; text char
        cmp     ah,'A'
        jb      tstkw3b                 ; b = control chars
        cmp     ah,'Z'
        ja      tstkw3b                 ; a = not upper case alpha
        add     ah,'a'-'A'              ; convert upper case to lower case
tstkw3b:cmp     al,'A'
        jb      tstkw3c
        cmp     al,'Z'
        ja      tstkw3c
        add     al,'a'-'A'              ; convert upper case to lower case
tstkw3c:cmp     al,ah                   ; test characters
        jne     tstkw3d                 ; ne = no match
        inc     si                      ; move to next char
        inc     bx
        loop    tstkw3a                 ; loop through entire length
tstkw3d:pop     bx
        pop     si
        jcxz    tstkw5                  ; z: cx = 0, exit with match;
                                        ;  else select next keyword
tstkw4: inc     kbtemp                  ; number of keyword to test next
        mov     cx,kbtemp
        cmp     cx,kwcnt                ; all done? Recall kbtemp starts at 0
        jae     tstkwx                  ;ae = exhausted search, unsuccessfully
        mov     al,byte ptr [bx]        ; cnt (keyword length from macro)
        xor     ah,ah
        add     ax,4                    ; skip over '$' and two byte value
        add     bx,ax                   ; bx = start of next keyword slot
        jmp     tstkw3                  ; do another comparison

tstkw5:                                 ; get action pointer
        mov     al,byte ptr [bx]        ; cnt (keyword length from macro)
        xor     ah,ah
        add     ax,2                    ; skip over '$'
        add     bx,ax                   ; now bx points to dispatch value
        mov     bx,[bx]                 ; bx holds dispatch value
        clc                             ; carry clear for success
        jmp     short tstkwxx           ; exit
        ret
tstkwx: xor     bx,bx                   ; exit when no match
        mov     kbtemp,bx               ; make verb number be zero too
        stc                             ; carry set for failure
tstkwxx:pop     si
        pop     cx
        pop     ax
        ret
tstkeyw endp

; Insert asciiz string pointed to by si into string buffer stbuf.
; Success: returns offset of first free byte (strmax) in string buffer stbuf,
; cx = type and Index of new string, and carry clear.
; Failure = carry set.
insertst proc   near
        push    bx
        push    dx
        push    di
        push    kbtemp          ; save this variable too
        mov     bx,offset sptable ; table of string offsets
        mov     kbtemp,0        ; slot number
        mov     cx,maxstng      ; number of entries, find an empty slot
insert1:cmp     word ptr[bx],0  ; slot empty?
        je      insert2         ; e = yes
        inc     kbtemp          ; remember slot number
        add     bx,2            ; look at next slot
        loop    insert1         ; keep looking
        jmp     short insert4   ; get here if no empty slots
insert2:                        ; see if stbuf has sufficient space
        mov     dx,si           ; si points to text of new string
        call    strlen          ; length of new string to cx
        inc     cx              ; count the terminator
        mov     di,strmax       ; offset of first free byte in stbuf
        add     di,cx           ; di = address where this string would end
        cmp     di,offset stbuf+stbuflen ; beyond end of buffer?
        jae     insert4         ; ae = yes, not enough room
        mov     di,strmax       ; point to first free slot in stbuf
        mov     [bx],di         ; fill slot with address offset of buffer
        call    strcpy          ; copy the new string (si) to the buffer (di)
        add     strmax,cx       ; 1 + length of new string = new free byte
        mov     cx,kbtemp       ; return new slot number with Director Index
        and     cx,not(strng+verb) ; clear type bits
        or      cx,strng        ; say type is multi-char string
        clc                     ; say success
        jmp     short insertx   ; exit
insert4:stc                     ; say no-can-do
insertx:pop     kbtemp
        pop     di
        pop     dx
        pop     bx
        ret
insertst endp

; Remove (delete) string. Enter with listptr preset for director entry.
; Acts only on existing multi-char strings; recovers freed space.
; All registers preserved.
remstr  proc    near
        push    si
        mov     si,listptr              ; list pointer
        test    dirlist[si],strng       ; multi-char string?
        pop     si
        jnz     remst1                  ; nz = a multi-char string
        ret                             ; else do nothing
remst1: push    ax
        push    bx
        push    cx
        push    dx
        push    si
        mov     si,listptr
        mov     ax,dirlist[si]          ; Director table entry
        and     ax,not(strng+verb) ; clear type bits, leave string's pointer
        mov     dirlist[si],0           ; clear Director table entry
        shl     ax,1                    ; index words not bytes
        mov     si,offset sptable       ; list of string offsets in stbuf
        add     si,ax                   ; plus index = current slot
        mov     dx,[si]                 ; get offset of string to be deleted
        call    strlen                  ; get length of subject string
        inc     cx                      ; count terminator, cx has whole length
        sub     strmax,cx       ; count space to be freed (adj end-of-buf ptr)
        mov     word ptr [si],0 ; clear sptable of subject string address
        push    cx                      ; save length of purged string
        push    di                      ; save di
        push    es                      ; save es
        push    ds
        pop     es              ; setup es:di to be ds:offset of string
        mov     di,dx           ; destination = start address of purged string
        mov     si,dx           ; source = start address of purged string
        add     si,cx           ;  plus string length of purged string.
        mov     cx,offset stbuf+stbuflen ; 1 + address of buffer end
        sub     cx,si                   ; 1 + number of bytes to move
        dec     cx                      ; number of bytes to move
        jcxz    remst2                  ; z = none
        cld                             ; direction is forward
        rep     movsb                   ; move down preserved strings
remst2: pop     es                      ; restore regs
        pop     di
        pop     ax              ; recover length of purged string (was in cx)
        mov     bx,offset sptable       ; string pointer table
        mov     cx,maxstng              ; max mumber of entries
remst4: cmp     [bx],dx         ; does this entry occur before purged string?
        jbe     remst5          ; be = before or equal, so leave it alone
        sub     [bx],ax         ; recompute address (remove old string space)
remst5: add     bx,2                    ; look at next list entry
        loop    remst4                  ; do all entries in sptable
        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
remstr  endp

shkfre  proc    near                    ; show free key & string defs & space
        push    ax                      ; preserves all registers.
        push    bx
        push    cx
        push    dx
        push    kbtemp
        mov     dx,offset fremsg
        mov     ah,prstr
        int     dos
        mov     ax,maxkeys              ; max number of key defs
        sub     ax,nkeys                ; number currently used
        call    decout                  ; show the value
        mov     ah,prstr
        mov     dx,offset kyfrdef       ; give key defs msg
        int     dos
        mov     bx,offset sptable       ; table of string pointers
        mov     cx,maxstng              ; number of pointers
        mov     kbtemp,0                ; number free
shkfr1: cmp     word ptr [bx],0         ; slot empty?
        jne     shkfr2                  ; ne = no
        inc     kbtemp                  ; count free defs
shkfr2: add     bx,2                    ; look at next slot
        loop    shkfr1                  ; do all of them
        mov     ax,kbtemp               ; number of free defs
        call    decout                  ; display
        mov     dx,offset stfrdef       ; say free string defs
        mov     ah,prstr
        int     dos
        mov     ax,offset stbuf+stbuflen ; 1 + last byte in stbuf
        sub     ax,strmax               ; offset of last free byte in stbuf
        call    decout
        mov     dx,offset stfrspc       ; give free space part of msg
        mov     ah,prstr
        int     dos
        pop     kbtemp
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
shkfre  endp
; Initialize the keyboard tables at Kermit startup time. Optional procedure.
; Requires kbdinlst to be configured with mkeyw macro in the form
;       mkeyw   'definition',keytype*256+keycode
; keytype is 0 for scan codes and non-zero for ascii.
; Returns normally.
kbdinit proc    near                    ; read keyword kbdinlst and setup
        push    ds                      ;  initial keyboard assignments.
        pop     es                      ; set es:di to datas segment
        mov     taklev,1                ; pretend that we are in Take file
        mov     si,offset kbdinlst      ; start of list of definitions
kbdini1:mov     cl,byte ptr [si]        ; cnt field (keyword length of macro)
        xor     ch,ch
        jcxz    kbdinix                 ; z = null cnt field = end of list
        inc     si                      ; look at text field
        mov     di,offset tranbuf       ; where defkey expects text
        cld
        rep     movsb                   ; copy cx chars to tranbuf
        mov     byte ptr [di],0         ; insert null terminator
        inc     si                      ; skip '$' field
        mov     ax,word ptr [si]        ; get value field
        mov     keycode,ax              ; set key ident value
        push    si
        call    dfkey2                  ; put dfkey to work
         nop
         nop
         nop
        pop     si
        add     si,2                    ; point to next entry
        jmp     kbdini1                 ; keep working
kbdinix:mov     taklev,0                ; reset Take file level
        ret
kbdinit endp
;;;     End of System Independent Procedures

;;;     Begin System Dependent Procedures

; Read keyboard. System dependent.
; Return carry set if nothing at keyboard.
; If char present return carry clear with key's code in Keycode.
; If key is ascii put that in the low byte of Keycode and clear bit Scan in
; the high byte; otherwise, put the scan code in the lower byte and set bit
; Scan in the high byte.
; Bit Scan is set if key is not an ascii code.
; Modifies register ax.
; Read keyboard. System dependent.
; Return carry set if nothing at keyboard.
; If char present return carry clear, key's code in Keycode, and key's
; type in Keytype (0 for scan codes, non-zero for ascii) in Keycode.
; Modifies register ax.
getkey  proc    near
        mov     kbdflg,0                ; char passed to msster.asm, clear it
        push    bx                      ; firmwr corrupts all but cs,ds,ss,sp
        push    cx
        push    dx
        push    si
        push    di
getky1:
        mov     di,6                    ; get level 1 (Bios) character
        push    es
        int     firmwr
        pop     es
        cmp     cl,0ffh                 ; level 1 character available?
        je      getky2                  ; e = yes
        cmp     cl,1                    ; is level 2 sequence is in progress?
        jne     getkyz                  ; ne = no, nothing available
        mov     di,2                    ; get level 2 character
        push    es
        int     firmwr
        pop     es
        cmp     cl,0ffh                 ; did we really get one?
        jne     getkyz                  ; ne = no, something strange happened
        jmp     getky1                  ; else skip and keep trying.
getky2: and     ax,not cplk             ; ignore caps lock key
        test    ax,fnkey                ; function-type key?
        jz      getky2a                 ; z = no, assume ascii key
        and     ax,not fnkey            ; strip function key bit
        or      ax,scan                 ; set scan bit
        jmp     short getky3
getky2a:cmp     al,','                  ; main comma key?
        jne     getky2b                 ; ne = no
        test    ax,shfkey               ; shifted?
        jz      getky2b                 ; z = no, leave as a comma
        or      ax,scan                 ; declare to be a function key
        jmp     getky3
getky2b:cmp     al,'.'                  ; main period key?
        jne     getky2c                 ; ne = no
        test    ax,shfkey               ; shifted?
        jz      getky2c                 ; z = no, leave as a period
        or      ax,scan                 ; declare to be a function key
        jmp     getky3
getky2c:and     ax,not(shfkey+ctlkey) ; ignore shift and control key on alphas
getky3: mov     keycode,ax              ; return key's code (usually ascii)
        pop     di
        pop     si
        pop     dx
        pop     cx
        pop     bx
        clc                             ; carry clear = got a char
        ret
getkyz: pop     di
        pop     si
        pop     dx
        pop     cx
        pop     bx
        stc                             ; carry set = no char avaiable
        ret
getkey  endp


; Do any local processing after reading a key during active translation
; Avoids same actions if a key is being defined or shown.
postkey proc    near
        ret
postkey endp

; Jumping to this location is like retskp.  It assumes the instruction
;   after the call is a jmp addr.

RSKP    PROC    NEAR
        pop bp
        add bp,3
        push bp
        ret
RSKP    ENDP

; Jumping here is the same as a ret.

R       PROC    NEAR
        ret
R       ENDP

code    ends
        end
