	NAME	msurb
; File MSURB1.ASM for the DEC Rainbow
	include	mssdef.h
;       Copyright (C) 1982,1991, Trustees of Columbia University in the
;       City of New York.  Permission is granted to any individual or
;       institution to use, copy, or redistribute this software as long as
;       it is not sold for profit and this copyright notice is retained.
; Keyboard translator, by Joe R. Doupnik, Dec 1986
;  with contributions from David L. Knoell.
; Edit history:
; 2 March 1991 version 3.10
; Last edit 3 Feb 1991
; 21-SEP-90   [rhw-1]	Fixed bug when using SET KEY, prompt string DFASKDF had
;			'$' terminator but needed NULL termination, ie. bug was
;			it displayed lots of junk after the prompt string.
;			Robert H. Weiner (robert%progplus.uucp@uunet.uu.net)
; 11 Nov 1989
; 1 July 1988 Version 2.31
; 1 Jan 1987 version 2.30


	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

data	segment	public 'data'
	extrn taklev:byte, comand:byte, flags:byte
	extrn shkadr:word, stkadr:word, trans:byte, kbdflg:byte, ttyact: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: ',0	; [rhw-1] made '$'->0
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,'$'
dfkoops	db	cr,lf,' Oops! 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 (0)		; 16 bit keycodes, paralled by dirlist
dirlist	dw	maxkeys dup (0)		; director {v+s} + {index | new char}
sptable	dw	maxstng dup (0)		; list of asciiz string offsets
stbuf	dw	stbuflen dup (0)	; buffer for strings
strmax	dw	stbuf			; first free byte in stbuf
listptr	dw	?			; item number for keylist and dirlist
nkeys	dw	0			; 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)
msutake	db	?			; if being run from take file or not
stringcnt dw	0			; qty of string chars to be processed
stringptr dw	0			; address of next string char
twelve	dw	12d
dosflg	db	0

;;;	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

data	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:data, es:nothing

; 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
	mov	ttyact,1		; doing single char output
	cmp	stringcnt,0		; any leftover string chars?
	je	keybd0			; e = no
	jmp	keyst2			; yes, finish string
keybd0:	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 data 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
	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
	xor	cx,cx			; init string length to null
	cmp	si,0			; is there a string pointer present?
	je	keyst1			; e = no, skip operation
	cld				; scan forward
	mov	cl,byte ptr [si]	; get string length byte
	inc	si
keyst1:	mov	stringcnt,cx
	mov	stringptr,si
	pop	si
	jcxz	keybdx			; z = null length

keyst2:	push	si
	mov	si,stringptr		; pointer to next string char
	cld
	lodsb				; get new string char into al
	pop	si
	dec	stringcnt		; string chars remaining
	inc	stringptr
	cmp	stringcnt,0		; end of the string?
	jne	keyst3			; ne = no
	mov	ttyact,0		; pretend not single char output
keyst3:	call	keysv			; scan for embedded verbs
	jc	keyst4			; c = not found, al has string char
	jmp	bx			; perform the verb (bx = address)
keyst4:	jmp	chrout			; send out the char in al

keybdx:	clc				; return success
	ret
KEYBD	ENDP

; Scan for keyboard verbs embedded in outgoing string. If found update
; string pointer and count to just beyond the verb and return action routine
; address in bx with carry clear. If failure return carry set and no change.

keysv	proc	near
	push	ax
	push	si
	push	di
	cmp	al,'\'			; escape?
	jne	keysv7			; ne = no
	mov	cx,stringcnt		; chars remaining
	mov	si,stringptr		; address of next char to read
	mov	brace,0			; assume not using braces
	cmp	byte ptr [si],braceop	; starts with \{?
	jne	keysv1			; ne = no
	inc	si			; skip the opening brace
	dec	cx
	mov	brace,bracecl		; expect closing brace
keysv1:	cmp	byte ptr [si],'K'	; starts with \{K or \K?
	je	keysv2			; e = yes
	cmp	byte ptr [si],'k'	; starts as \{k or \k?
	jne	keysv7			; ne = no, then it's a string
keysv2:	inc	si			; yes, skip the K too
	dec	cx
	mov	di,offset tranbuf	; copy verb name to this work buffer
	xor	ax,ax
	mov	[di],ax			; init the buffer to empty
keysv3:	cld
	jcxz	keysv4			; z = no more string chars
	lodsb				; scan til closing brace or w/s or end
	dec	cx
	cmp	al,brace		; closing brace?
	je	keysv4			; e = yes
	cmp	al,spc			; white space or control char?
	jbe	keysv3			; be = yes
	mov	[di],ax			; copy to tranbuf and terminate
	inc	di
	jmp	short keysv3
keysv4:	push	si			; save input reading position
	mov	si,offset tranbuf	; where verb starts (needs si)
	call	tstkeyw			; find keyword, bx = action routine
	pop	si
	jc	keysv7			; c = no such verb
	cmp	brace,0			; need to end on a brace?
	je	keysv6			; e = no
	dec	si			; break position
	inc	cx
	cld
keysv5:	jcxz	keysv6			; z = no more string characters
	lodsb				; read string char
	dec	cx
	cmp	al,brace		; the brace?
	jne	keysv5			; ne = no, repeat until it is found
keysv6:	mov	stringptr,si		; where we finished+1
	mov	stringcnt,cx		; new count of remaining chars
	pop	di
	pop	si			; original si, starting place
	pop	ax			; original ax
	clc
	ret
keysv7:	pop	di			; verb not found
	pop	si
	pop	ax
	stc
	ret
keysv	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,cmword		; parse a word
	call	comnd			; get key code or original ascii char
	mov	al,taklev		; reading from Take file
	mov	msutake,al		; save here
	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	dfkey15			; e = yes, reinit keyboard [2.31]
	cmp	dx,'fo'			; first two letters of "off"
	je	dfkey13			; e = yes, use DOS keyboard calls
	cmp	dx,'no'			; first two letters of "on"
	je	dfkey14			; e = yes, use standard kbd calls
	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

dfkey13:mov	dosflg,0ffh		; set DOS keyboard read flag
	jmp	short dfkey14a
dfkey14:mov	dosflg,0		; clear DOS keyboard read flag
dfkey14a:mov	ah,cmeol		; get end of line confirmation
	call	comnd
	ret

dfkey15:mov	ah,cmeol
	call	comnd			; confirm request before proceeding
	jnc	dfkeyc			; nc = success
	ret				; failure

dfkey0:	mov	dx,offset dfhelp1	; say bad definition command
	mov	ah,prstr
	int	dos
	stc				; failure
	ret

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 data 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
	mov	cx,maxstng
	mov	di,offset sptable	; string pointer table
	rep	stosw			; clear it
	pop	es			; recover register
	mov	strmax,offset stbuf	; clear string buffer, free space ptr
	mov	stbuf,0			; first element of buffer 
	mov	nkeys,0			; clear number of defined keys
	call	msuinit			; restore startup definitions
	clc				; success
	ret
					; 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 data 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,cmline		; read rest of line into tranbuf
	call	comnd			; allow null definitions
	or	ah,ah			; char count zero?
	jz	dfkey1e			; z = zero, prompt for definition
	jmp	dfkey1g			; process definition

dfkey1e:mov	ah,prstr
	mov	dx,offset crlf
	int	dos
	mov	dx,offset dfaskdf	; prompt for definition string
 	call	prompt			; Kermit prompt routine
	mov	comand.cmcr,1		; permit bare carriage returns
	mov	comand.cmwhite,1	; allow leading whitespace
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,cmline		; read rest of line into tranbuf
	call	comnd
	jc	dfkey1x			; exit now on ^C from user
	cmp	comand.cmcr,0		; prompting for definition?
	je	dfkey1g			; e = no, trim leading whitespace
	mov	comand.cmcr,0		; turn off allowance for bare c/r's
	jmp	dfkey2			; interactive, allow leading whitespace
dfkey1x:ret				; failure exit

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,cmeol		; get a confirm
	call	comnd
	jc	dfkey1x			; none so declare parse error
	mov	cx,kbtemp		; string length
	
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 dfkoops	; Oops! msg
	mov	ah,prstr		; complain and don't redefine
	int	dos
	stc				; failure
	ret

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
	stc				; failure
	ret

; 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
dfkey4:	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 dfkey4		; 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
	stc				; failure
	ret

; 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. Trailing whitespace removed
; before examining braces.
; Null length strings mean define key as Self.
					; STRING definitions
dfkey5:	call	remstr			; first, clear old string, if any
	mov	si,offset tranbuf	; si=source, di=dest, convert in-place
	mov	di,si
	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
	stc				; failure
	ret

		; 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 data 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:	mov	ah,msutake		; Finish up. In a Take file?
	or	ah,taklev		; or even directly
	cmp	ah,0
	je	dfkey7a			; e = no
	cmp	flags.takflg,0		; echo Take commands?
	je	dfkey7b			; e = no
dfkey7a: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
dfkey7b:clc				; return success
	ret
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,cmeol		; get a confirm
	call	comnd			; ignore any additional text
	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 data 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	si,sptable[bx]		; table of string offsets
	mov	cl,byte ptr [si]	; get string length byte
	xor	ch,ch
	inc	si			; point to string text
	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
	clc				; return success
	ret
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.
; Reg cx has string length upon entry.
; 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	si
	push	di
	push	kbtemp		; save this variable too
	mov	dx,cx		; save length of incoming string in dx
	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	cx,dx		; length of new string to cx
	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
	push	es
	push	ds
	pop	es		; point es:di to data segment
	cld
	mov	byte ptr [di],cl ; length of text for new string
	inc	di		; move to next storage slot
	rep	movsb		; copy string text
	pop	es
	mov	strmax,di	; offset of next 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	si
	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	bx,[si]			; get offset of string to be deleted
	mov	dx,bx			; save in dx for later
	mov	cl,byte ptr [bx]	; get length byte
	xor	ch,ch			; get length of subject string
	inc	cx			; length byte too, 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	si
	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	si
	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
	pop	si
	add	si,2			; point to next entry
	jmp	kbdini1			; keep working
kbdinix:dec	taklev			; 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
code	ends
	end

