; maildvrs/asm - kjw/bqsd - version 2.00 - 01/83
;
; revised 02/23/83 - kjw
;
	SUBTTL	'<maildvrs/asm - keyboard/video drivers>'
;
	PAGE
;
;	read key from keyboard
;
INKEY	LD	A,CHARCUR	;character cursor
KEYSP	LD	(CURCHR),A	;save it
	CALL	CURADD		;fetch cursor address
;
KEY	PUSH	BC		;save 'em
	PUSH	DE		;save too
;
	IF	MOD2
	CALL	@VIDON		;select video
	ENDIF
;
	LD	E,(HL)		;fetch current char
	LD	D,'$'		;cursor char
CURCHR	EQU	$-1
;
	IF	MOD2
	CALL	@VIDOFF		;de-select video
	ENDIF
;
KEY1	LD	BC,DELAY	;flash delay count
;
	IF	MOD2
	CALL	@VIDON		;select video
	ENDIF
;
	LD	A,(HL)		;fetch current char
	CP	E		;cursor off?
	LD	A,E		;fetch cursor off
	JR	NZ,$+3		;turn on if off
	LD	A,D		;else turn off
	LD	(HL),A		;char to video
;
	IF	MOD2
	CALL	@VIDOFF		;video off
	ENDIF
;
KEY2	CALL	STROBE		;strobe keyboard
	OR	A		;have a key?
	JR	NZ,KEY3		;go if yes
;
;	see if time to change cursor
;
	DEC	BC		;less counter
	LD	A,B		;any more?
	OR	C		;BC any bits on?
	JR	NZ,KEY2		;strobe more if yes
	JR	KEY1		;reset count, flash it
;
;	have a key, return with char in A, E on video
;
KEY3	EQU	$
;
	IF	MOD2
	PUSH	AF		;save A
	CALL	@VIDON		;enable video
	ENDIF
;
	LD	(HL),E		;replace character
;
	IF	MOD2
	CALL	@VIDOFF		;de-select video
	POP	AF		;restore char
	ENDIF
;
	POP	DE		;unstack
	POP	BC		;count too
	CP	BREAK		;set flags
	SCF			;C = yes
	RET	Z		;go if break
	CP	CR		;else return with NC
	SCF			;and Z flag set if CR
	CCF
	RET			;NC and Z setting
;
;	strobe keyboard for single key, no wait
;
STROBE	KEYIN$			;fetch key
	RET			;Z = no key, NZ = A = key
;
;	fetch cursor video address into HL
;
CURADD	EQU	$
;
	IF	MOD1.OR.MOD3.OR.LDOSSVC
	LD	HL,(@CURSOR)	;fetch cursor address
	RET
	ENDIF
;
	IF	MOD2
	PUSH	IX		;save IX
	LD	A,1		;video DCB #
	CALL	@LOCDCB		;locate it
	LD	L,(IX+11)	;fetch LSB
	LD	H,(IX+12)	;fetch MSB
	POP	IX		;restore
	RET			;done
	ENDIF
;
	PAGE
;
;	fetch string from keyboard
;
GETSTR	LD	HL,STRING	;start input area
	PUSH	BC		;save string length
	PUSH	HL		;save string start
	CALL	FILCLR		;clear buffer
	LD	(HL),CR		;carriage return to end
	CALL	CURADD		;HL => video cursor
	POP	DE		;DE => buffer
	POP	BC		;B = max length
SPGET	LD	C,0		;init input length
	LD	A,CHARCUR	;cursor character
	LD	(CURCHR),A	;save it
	LD	A,(KEYFLG)	;get system flag
	RES	1,A		;set NO keys
	LD	(KEYFLG),A	;put it back
;
GETLP	CALL	KEY		;get a key
	JP	Z,GETSRET	;go exit BREAK/ENTER
;
ONEKEY	PUSH	IY		;save
;
	PUSH	HL		;pass HL => IY
	POP	IY		;IY => video
	LD	HL,GETRET	;return vector
	PUSH	HL		;to the stack
	LD	HL,TABLEK	;vector table
	CALL	GOTABLE		;any matching keys?
	JR	NEWKEY		;nope, new input key
;
GETRET	PUSH	IY		;pass video back to HL
	POP	HL		;restore video
	POP	IY		;restore pointer
	JR	GETLP		;get another key
;
;	new key, add to buffer and video
;
NEWKEY	CP	20H		;displayable?
	RET	C		;nope, ignore it
	CP	80H		;displayable?
	RET	NC		;nope, ignore it
	PUSH	AF		;save key
	LD	A,(KEYFLG)	;get system flag
	SET	1,A		;set KEY in buffer
	LD	(KEYFLG),A	;put it back
	POP	AF		;restore key
	JR	KEYRCM		;go common
;
KEYRDS	LD	A,' '		;else load blank
	JR	KEYRCM		;go common
;
KEYRHT	LD	A,(DE)		;fetch current
;
KEYRCM	EQU	$
	IF	MOD2
	PUSH	BC		;save
	LD	B,A		;pass char
	CALL	@VIDON		;enable video
	LD	A,B		;now fetch char
	ENDIF
;
	LD	(IY),A		;char to video
	LD	(DE),A		;char to buffer
;
	IF	MOD2
	CALL	@VIDOFF		;disable video
	POP	BC		;restore
	ENDIF
;
MOVRHT	LD	A,B		;get max length
	DEC	A		;less one
	CP	C		;at max before cursor?
	RET	Z		;yes, can't add it
;
	INC	IY		;bump video
	INC	DE		;bump buffer
	INC	C		;bump length
	OR	A		;clear carry
	RET			;done!
;
KEYLDS	LD	A,' '		;blank char
	JR	KEYLCM		;go common
;
KEYLEF	LD	A,(DE)		;read buffer
;
KEYLCM	EQU	$
;
	IF	MOD2
	PUSH	BC
	LD	B,A
	CALL	@VIDON		;enable video
	LD	A,B		;restore char
	ENDIF
;
	LD	(IY),A		;to video
	LD	(DE),A		;to buffer
;
	IF	MOD2
	CALL	@VIDOFF		;video off
	POP	BC		;restore
	ENDIF
;
MOVLFT	INC	C		;any length?
	DEC	C		;C=0?
	SCF			;C = yes
	RET	Z		;yes, done!
;
	DEC	C		;length -1
	DEC	IY		;video -1
	DEC	DE		;buffer -1
	OR	A		;clear carry
	RET			;done
;
;	delete mode
;
KEYDEL	LD	HL,BUFLFT	;buffer left
	JR	KEYINDE		;go common
;
;	insert mode
;
KEYINS	LD	HL,BUFRHT	;buffer right
;
KEYINDE	LD	A,B		;fetch max length
	DEC	A		;less 1 for posit
	SUB	C		;less current length
	RET	Z		;at end, can't insert
;
	LD	(KEYCAL1),HL	;save vector
	LD	(KEYCAL2),HL	;2 places
;
	PUSH	BC		;save count
	PUSH	DE		;save buffer
	LD	B,A		;B = counter
;
	IF	MOD2
	CALL	@VIDON		;enable video
	ENDIF
;
	PUSH	IY		;pass IY => HL
	POP	HL		;HL => video
	CALL	$
KEYCAL1	EQU	$-2
	EX	DE,HL		;HL => buffer
	CALL	$
KEYCAL2	EQU	$-2
;
	IF	MOD2
	CALL	@VIDOFF		;disable video
	ENDIF
;
	POP	DE		;unstack
	POP	BC		;this too
	RET
;
;	move buffer right for insert
;
BUFRHT	PUSH	DE		;save needed
	PUSH	BC		;save length
;
	LD	C,B		;C = length
	LD	B,0		;BC = length
	ADD	HL,BC		;HL => end
	LD	D,H		;pass to DE
	LD	E,L		;DE => end
	DEC	HL		;HL => end -1
	LDDR			;move block up
	LD	A,' '		;blank first char
	LD	(DE),A		;to buffer
;
	POP	BC		;restore length
	POP	DE		;unstack
	RET			;done!
;
;	move block down for delete
;
BUFLFT	PUSH	BC		;save
	PUSH	DE		;save
;
	LD	C,B		;pass length
	LD	B,0		;BC = length
	LD	D,H		;pass HL => DE
	LD	E,L		;HL/DE => start
	INC	HL		;HL = start +1
	LDIR			;move block up
	LD	A,' '		;load blank
	LD	(DE),A		;to buffer
;
	POP	DE		;unstack
	POP	BC
	RET			;buffer shifted
;
;	clear to end of frame
;
KEYESC	LD	A,B		;fetch max length
	SUB	C		;at max?
	RET	Z		;yes, nothing to clear!
;
	PUSH	BC		;save count
	PUSH	DE		;save buffer
	LD	B,A		;pass length
	LD	C,A		;twice
;
	IF	MOD2
	CALL	@VIDON		;enable video
	ENDIF
;
	PUSH	IY		;pass video to HL
	POP	HL		;HL => video
	CALL	FILCLR		;fill with blanks
	EX	DE,HL		;HL => buffer
	LD	B,C		;reset pointer
	CALL	FILCLR		;fill with blanks
;
	IF	MOD2
	CALL	@VIDOFF		;disable video
	ENDIF
;
	POP	DE		;unstack
	POP	BC
	RET
;
;	fill buffer
;
FILCLR	LD	(HL),' '	;load a blank
	INC	HL		;bump buffer
	DJNZ	FILCLR		;go for length
	RET			;done
;
GETSRET	LD	HL,STRING	;point to input
	LD	A,'$'		;get system flag
KEYFLG	EQU	$-1
	BIT	1,A		;Z = no input
	LD	A,(HL)		;get first char
	RET			;back to caller
;
	PAGE
;
;	display string to video
;
DISPLAY	LD	A,(HL)		;fetch string byte
	INC	HL		;bump pointer
	CP	ETX		;end of text?
	RET	Z		;yes, done!
	CP	SETCUR		;new cursor address?
	JR	Z,NEWADD	;yes, go!
	CALL	VOUT		;else display byte
	JR	DISPLAY		;go next char
;
;	set new cursor location
;
NEWADD	PUSH	BC		;save it
	LD	B,(HL)		;fetch ROW
	INC	HL		;bump pointer
	LD	C,(HL)		;fetch COLUMN
	INC	HL		;bump pointer
	CALL	PUTCUR		;place cursor there
	POP	BC		;restore BC
	JR	DISPLAY		;go next char
;
;	position cursor to BC (row/column)
;
PUTCUR	EQU	$
;
	IF	MOD1.OR.MOD3.OR.LDOSSVC
	PUSH	HL		;save from math
	LD	L,B		;fetch row
	LD	H,0		;HL = row
	ADD	HL,HL		;*2
	ADD	HL,HL		;*4
	ADD	HL,HL		;*8
	ADD	HL,HL		;*16
	ADD	HL,HL		;*32
	ADD	HL,HL		;*64
	LD	A,C		;fetch column
	ADD	A,L		;add offset
	LD	L,A		;HL = rows*64 + column
	LD	A,H		;fetch MSB
	ADD	A,@VIDEO<-8	;add msb video offset
	LD	H,A		;HL = video mem offset
	LD	(@CURSOR),HL	;update cursor location
	POP	HL		;unstack
	RET			;done!
	ENDIF
;
	IF	MOD2
	PUSH	DE		;save
	LD	D,0		;# chars to display
	LD	A,@VDGRAF	;SVC #
	RST	SVC		;position cursor
	POP	DE		;unstack
	RET			;done
	ENDIF
;
;	clear lower video
;
CLS	PUSH	BC		;save BC
	LD	BC,12<8+0	;row/column
	CALL	PUTCUR		;place cursor there
	POP	BC		;unstack
	LD	A,EOF		;clear to end of frame
;
;	send char to video
;
VOUT	VOUT$			;char to video
	RET			;done
;
;	vector table for key editing responses
;
TABLEK	DEFB	UP		;up arrow
	DEFW	KEYINS
	DEFB	DOWN		;down arrow
	DEFW	KEYDEL
	DEFB	LEFT		;left arrow
	DEFW	KEYLEF
	DEFB	RIGHT		;right arrow
	DEFW	KEYRHT
	DEFB	SLEFT		;shift left (backspace)
	DEFW	KEYLDS
	DEFB	SRIGHT		;shift right (tab)
	DEFW	KEYRDS
	DEFB	FCLEAR		;clear (F1)
	DEFW	KEYESC
	DEFB	ETBL		;end of table
;
;	check that current program
;	did not load into high memory
;
INTEG	PUSH	BC		;save BC
	PUSH	HL		;save HL
;
;	fetch current top memory
;
	TOPMEM$			;get topmem into HL
	LD	BC,PGMEND	;end of load code
	OR	A		;clear carry flag
	SBC	HL,BC		;compare TOPMEM <> PGMEND
	POP	HL		;unstack
	POP	BC
	RET	NC		;go if OK
;
;	high memory overlaid!  abort programs!
;
	LD	HL,MSGCOR	;memory corrupt
	CALL	DISPLAY		;display message
	CALL	INKEY		;wait for a key
;
	IF	MOD1.OR.MOD3.OR.LDOSSVC
	LD	A,(@ROM)	;read ROM
	CP	@ROM3		;mod III?
	JR	Z,BOOT3		;yes, go!
BOOT1	HALT			;reboot mod I
BOOT3	RST	0		;reboot mod III
	ENDIF
;
	IF	MOD2
BOOT2	LD	A,1		;set bit 0
	OUT	(0F9H),A	;enable boot ROM II
	RST	0		;go boot rom!
	ENDIF
	JR	$
;
MSGCOR	DEFB	SETCUR
	DEFB	00,00		;home cursor
	DEFB	EOF		;clear video
;
	DEFM	'$TOPMEM$ corrupted - (KEY) to re-boot'
	DEFB	ETX
;
;	locate FCB block
;
LOCFCB	PUSH	BC		;save
	AND	7		;force in range
	ADD	A,A		;*2
	LD	C,A		;C = offset
	LD	B,0		;BC = drive # time 2
	LD	IX,FCBTBL	;start of table
	ADD	IX,BC		;IX => entry vector
	LD	C,(IX+0)	;fetch LSB block
	LD	B,(IX+1)	;fetch MSB block
	PUSH	BC		;pass to IX
	POP	IX		;IX => fcb block
	POP	BC		;restore BC
	RET			;done, IX => block
;
;	convert binary BHL => (DE) in ascii
;
BINASC	PUSH	IX		;save IX
	PUSH	IY		;and IY
;
	PUSH	DE		;pass string pointer > IX
	POP	IX		;IX => string to load
	PUSH	DE		;leave string start
	LD	IY,TENTBL	;lookup table for 10's
;
WRL1	LD	A,'0'		;init ascii digit
	EX	AF,AF'		;save in prime
	LD	C,(IY+0)	;fetch MSB 10's place
	LD	D,(IY+1)	;fetch NSB
	LD	E,(IY+2)	;LSB
;
WRL2	CALL	SUBIT		;sub CDE from BHL
	JR	C,WRL3		;go if overflow
	EX	AF,AF'		;restore ascii digit
	INC	A		;bump ascii
	EX	AF,AF'		;re-save it
	JR	WRL2		;go next subtract
;
WRL3	CALL	ADDIT		;add last subtract back
	EX	AF,AF'		;fetch ascii digit
	LD	(IX),A		;to string
	INC	IX		;bump string pointer
	INC	IY		;bump table
	INC	IY		;3 byte entries
	INC	IY
	DEC	E		;check for end digit
	JR	NZ,WRL1		;go if more to do
;
;	remove leading zeroes
;
	POP	HL		;HL => string beginning
	POP	IY		;unstack
	POP	IX		;done
	LD	B,7		;# digits -1
	LD	A,'0'		;comparator
;
STRIP	CP	(HL)		;leading zero?
	RET	NZ		;done not
	LD	(HL),SPACE	;load space
;
	INC	HL		;bump string pointer
	DJNZ	STRIP		;continue stripping
	RET			;done
;
;	table defining decimal places for 24 binary bits
;
TENTBL	DEFB	098H,096H,080H	;10,000,000
	DEFB	00FH,042H,040H	;1,000,000
	DEFB	001H,086H,0A0H	;100,000
	DEFB	000H,027H,010H	;10,000
	DEFB	000H,003H,0E8H	;1,000
	DEFB	000H,000H,064H	;100
	DEFB	000H,000H,00AH	;10
	DEFB	000H,000H,001H	;1
;
;	common subroutine for all files
;
COMMON	LD	BC,DRIVES<8+0	;B=counter, C=drive
	LD	(COMSUB),HL	;save vector
;
COMMLP	PUSH	BC		;save current
	LD	A,C		;get current drive #
	CALL	LOCFCB		;locate FCB
	LD	E,(IX+15)	;fetch FCB pointer
	LD	D,(IX+16)
	LD	L,(IX+17)	;fetch buff pointer
	LD	H,(IX+18)
	CALL	$		;go sub
COMSUB	EQU	$-2
	POP	BC		;unstack
	INC	C		;bump drive
	DJNZ	COMMLP		;go for 8 drives
	RET			;done
;
;	branch to table entry
;
GOTABLE	INC	(HL)		;check for terminator
	DEC	(HL)		;00 = terminator
	RET	Z		;done, return
;
	CP	(HL)		;match?
	JR	Z,GOTBL		;go vector if yes
	INC	HL		;bump to next entry
	INC	HL		;3 bytes each
	INC	HL
	JR	GOTABLE		;check next slot
;
GOTBL	INC	HL		;bump table
	LD	A,(HL)		;fetch LSB
	INC	HL		;bump table
	LD	H,(HL)		;fetch MSB
	LD	L,A		;HL = vector
	EX	(SP),HL		;replace vector on stack
	RET			;go vector!
;
;	convert char to upper case
;
UCASE	CP	'a'		;already upper?
	RET	C		;go if yes
	CP	'z'+1		;out of alpha range?
	RET	NC		;go if yes
	AND	5FH		;make upper case
	RET			;A = upper case char
;
;	run program module
;
RUNPGM	PUSH	BC		;leave error vector
	LD	DE,STRING	;use input area
	LD	BC,32		;max length name
	PUSH	DE		;save FCB start
	LDIR			;move name into FCB
	POP	DE		;DE => fcb
	LD	HL,DBUFF	;dummy I/O buffer
;
;	open file to locate drive where resident
;
	OPEN$			;open the file
	RET	NZ		;go if error
;
;	close file to place ascii drive # into string
;
	CLOSE$			;close file
	RET	NZ		;go if error
;
;	run program, drive # in filespec
;
	RUN$			;run program
	RET			;go on error
;
