;MOUSECURsor for Microsoft Mouse drivers - 9/87, upgraded 9/89
;Based on Jeff Prosise's MOUSEKEY.
;------------------------------------------------------------
; Microsoft MOUSE.COM or MOUSE.SYS, or a compatible driver must be installed.
; Works with the Mouse Systems MSMOUSE driver, but takes less total RAM.
; MOUSER works with ANY PROGRAM that accepts the cursor keys.
; Mouse speed and buttons can be programmed.
; The cursor keys continue to work as usual.  Programs with mouse drivers work
; independently of MOUSER.
; -------------------------------------------------------------------------- 
;			PARAMETERS
; MOUSER Hn Vn Lnn Mnn Rnn ?
;	? = help (also appears if invalid command line params are entered)
;	Hn, Vn  n=1 to 9 - sets Horizontal or Vertical cursor speed
;	Lx, Mx, Rx - x=character or 2-digit decimal ASCII value:
;		Left, Middle, or Right button values
;		Digit values above 31 are treated as 'extended' keys.
;	Params are all optional, in any order, with any delimiters.
; -------------------------------------------------------------------------- 
;			ABOUT TSR AND RELOADING

; Only one copy of MOUSER ever loads; after that, only the parameters
; are updated whenever the program is run.
; Some programs disable MOUSER; you can put MOUSER in the application's
; .BAT startup file, after the application program, to turn the mouse back on.
; Programs (like ACAD) with internal mouse drivers are commonly guilty of this.
; -------------------------------------------------------------------------- 

cr		equ	0Dh
lf		equ	0Ah

code	segment byte public 'code'
	assume cs:code, ds:code, es:code

	org	100h

Start:	jmp	Install

ResFinder	db	'MOUSER CODE'	;Used to determine if MOUSER is resident
vdelay		db	8		;vertical divider (set by Vn)
hdelay		db	8		;horizontal divider (set by Hn)
lkey		dw	0Dh		;keycode for left button (set by Lnn)
rkey		dw	1Bh		;keycode for right button (set by Rnn)
mkey		dw	5200h		;keycode for middle button (set by Mnn)

vcount		dw	0		;vertical mickeys since last update
hcount		dw	0		;horizontal
UpDnPointer	dw	0
LeRiPointer	dw	0		;keycode below is at 011Eh in code
keycode		db	4Dh,4Bh,50h,48h	;codes for rt/left/dn/up cursor keys

;------------------------------------------------------------------------------
;This routine gets a FAR CALL from the Microsoft driver when the mouse is
; moved or a button is pressed.

mouse:	push	ds
	mov	dx,cs
	mov	ds,dx
	mov	dx,lkey
	test	ax,2			;bit 1, left button pressed ?
	jnz	PutAndRet		;yes, jump
	mov	dx,rkey
	test	ax,8			;bit 3, right button ?
	jnz	PutAndRet
	mov	dx,mkey
	test 	ax,32			;bit 5, middle button ?
	jz	Move			;no, must be mouse move
PutAndRet:
	mov	ax,dx
	call	PutCode	
MouseRet:
	pop	ds
	retf

; - - - - - - - - - - - - - - - - - - - - - - - -
;Move the cursor.
;hdelay & vdelay are input as Hn Vn params, 1-slow to 8-fast, and converted
;for decrementing to divide by 128-slow to 1-fast.  It skips that number of
; mickeys (mouse increments) before responding.
Move:	mov	ax,0Bh			;read mouse motion counters
	int	33h			; CX=hor, DX=vert count
	mov	LeRiPointer,0		;point to Right keycode
	mov	UpDnPointer,2		;point to Down keycode
;accumulate motion, use totals.  No total exceeds +/- 32K.
	add	hcount,cx
	jno	NoHorOflo
	jns	HorPos
	mov	hcount,-32768
HorPos:
	mov	hcount,32767
NoHorOflo:
	add	vcount,dx
	jno	NoVertOflo
	jns	VertPos
	mov	vcount,-32768
VertPos:
	mov	vcount,32767
NoVertOflo:
	cmp	vcount,0		;vertical count positive?
	jge	VDirSet
	inc	UpDnPointer		;point to Up code
VDirSet:
	cmp	hcount,0		;horizontal count positive?
	jge	DivideEm
	inc	LeRiPointer		;point to Left code
; Now the pointers point to the proper key code for the accumulated motion.
; The counts still are signed.
;Ideally, this would alternate hor & vert codes for diagonal moves.
DivideEm:
	mov	ax,vcount
	cwd				;extend AX to DX:AX
	mov	cl,vdelay
	xor	ch,ch
	idiv	cx
	mov	vcount,dx		;signed remainder
	mov	cx,ax			;CX has no. of codes to insert, signed
	or	cx,cx
	jz	DoHor
	jns	VertNotNeg
	neg	cx
VertNotNeg:
	mov	bx,UpDnPointer
	mov	ah,ds:[bx+keycode]
	call	MovePut
;now do the same for horiz motion
DoHor:	mov	ax,hcount
	cwd
	mov	cl,hdelay
	xor	ch,ch
	idiv	cx
	mov	hcount,dx		;signed remainder
	mov	cx,ax
	or	cx,cx			;filter out zero to insert
	jnz	HorSome
	jmp	MouseRet

HorSome:
	jns	HorNotNeg
	neg	cx
HorNotNeg:
	mov	bx,LeRiPointer
	mov	ah,ds:[bx+keycode]	;get keycode from table
	call	MovePut
	jmp	MouseRet

MovePut:
	xor	al,al			;zero AL for extended keycode
PutLoop:
	call	PutCode
	loop	PutLoop			;sets CX=1 if buffer full, so will exit
	ret

; - - - - - - - - - - - - - - - - - - - - - - - -
;Put the keycode in AX into the keyboard buffer.
PutCode:
	push	ds
	push	bx
	mov	bx,40h			;point DS to BIOS data area
	mov	ds,bx
	cli				;disable interrupts
	mov	bx,ds:[1Ch]		;buffer tail
	mov	dx,bx
	add	dx,2			;calculate next buffer position
	cmp	dx,ds:[82h]		;did we overshoot the buffer end?
	jnz	insert1			;no, continue
	mov	dx,ds:[80h]		;yes, wrap around to buffer start
insert1:
	cmp	dx,ds:[1Ah]		;buffer head - is the buffer full?
	jnz	DoInsert		;no, do it
	mov	cx,1			;to exit calling loop
	jmp	InsDone

DoInsert:
	mov	ds:[bx],ax
	mov	bx,dx			;advance &
	mov	ds:[1Ch],bx		; store new pointer
InsDone:
	sti
	pop	bx
	pop	ds
	ret

;- - - - - - - end of resident portion - - - - - - -

Banner	db	cr,lf,'MOUSECUR 10/87 by Paul Noeldner, Madison, WI'
	db	' - - - hacked by J. E. Arkay 9/89'
	db	cr,lf,'$'
helpmsg	db	'Cursor enabler for Microsoft Mouse drivers.'
	db	cr,lf,lf
	db	'You can set the speed for easy pointing and the buttons for common keys.'
	db	cr,lf
	db	' Put it in .BAT files, to set it up for your applications.'
jnkhelp	db	cr,lf,lf
	db	'Example:',cr,lf
	db	' MOUSECUR H5 V5 L13 R27 M82   shows default parameters.',cr,lf
	db ' MOUSECUR H1 V2 M/            Slower cursor (for 123-style menus)'
	db	cr,lf
	db ' MOUSECUR V7 L73 M81 R27      Faster with PGUP/PGDN (for browsing)'
	db	cr,lf,lf
	db '    Hn, Vn      Horizontal, Vertical speed 1-8 (default 5 if not entered)'
	db	cr,lf
	db '    Lx, Mx, Rx  Button characters or decimal ASCII key codes'
	db	cr,lf
	db '    All params are optional, in any order, with any delimiter.'
	db	cr,lf
	db '    ASCII values over 31 decimal are taken to be extended keys.'
	db	cr,lf,lf
	db ' Commonly used keys (see a BASIC manual for more):'
	db	cr,lf
	db '03 - CTRL-C   09 - TAB     13 - RETURN   27 - ESC'
	db	cr,lf
	db '71 - HOME     73 - PGUP    78 - GRAY +   79 - END      81 - PGDN'
	db	cr,lf
	db '82 - INSERT   83 - DELETE        59 THRU 68 - F1 THRU F10'
	db	cr,lf,lf,'$'

NoDriverMsg  	db	cr,lf,' DRIVER MISSING: Install MOUSE.SYS, MOUSE.COM,'
		db	' MSMOUSE.COM, etc.',cr,lf,lf,'$'

junkmsg		db	cr,lf,' INVALID PARAMETER - CHECK THIS SCREEN',7,'$'

loadmsg		db	'Mouse Cursor Installed',cr,lf,lf,'$'

AdjustedMsg	db	'Mouse Parameters Adjusted ',cr,lf,lf,'$'

endparm 	dw	81h		;offset of end of params
posted		db	'N'		;is MOUSER already loaded ?
DigitFlag	db	'N'		;found a digit in command line

Install:
	mov	dx,OFFSET Banner
	mov	ah,9
	int	21h
	mov	ax,0		;check Microsoft driver is installed and reset.
	int	33h
	or	ax,ax
	jnz	DrvrOK			;AX=0FFFFh if installed OK
	mov	dx,OFFSET NoDriverMsg
	jmp	MsgExit

DrvrOK:	call	parms			;process command line params
	call	Find			;if MOUSECUR is resident, update params
	cmp	posted,'Y'
	jnz	TSR			;if not, install this copy
	mov	dx,OFFSET AdjustedMsg
	jmp	MsgExit

TSR:	mov	ah,9
	mov	dx,OFFSET loadmsg
	int	21h
	mov	ax,0Ch			;activate Microsoft driver
	mov	cx,101011b  ;'call mask'- call if mouse moved, or button press
	mov	dx,OFFSET mouse		;point ES:DX to our routine
	int	33h			;pass address to mouse driver
;de-allocate the Environment Block, we don't need it
; NOTE: This sometimes leaves a uselessly small unused Block
	push	es
	xor	ax,ax
	xchg	ax,word ptr CS:2Ch	;Environment Block addr in the PSP
	or	ax,ax
	jz	GoRes			;skip it if there is no Env
	mov	es,ax
	mov	ah,49h			;free allocated RAM
	int	21h
GoRes:	pop	es
	mov	dx,offset Banner	;bytes to stay resident
	add	dx,15			;allow for fractional para
	mov	cl,4
	shr	dx,cl			;paras to stay resident
	mov	ax,3100h		;go TSR
	int	21h
; - - - - - - - - - - - - - - - - - - -

;Process command line params (if any)
parms:	mov	si,80h		;point at input param length in PSP
	mov	ah,0
	mov	al,[si]		;length of params
	add	ax,80h		;compute end of params
	mov	endparm,ax	;remember it
	inc	si		;skip initial space in input params

parmloop:
	inc	si
	cmp	si,endparm	;at end of params ?
        jle	parse		;if not, process it
	ret			;if so, done with setup

parse:	mov	al,[si]		;get next character
    	cmp	al,' '		;skip blanks
	jz	parmloop
	cmp	al,'/'		;skip slashes
	jz	parmloop
	cmp	al,','		;skip commas
	jz	parmloop
help:	cmp	al,'?'		;show help?
	jnz	case
	mov	dx,OFFSET helpmsg
MsgExit:
	mov	ah,9
	int	21h
	mov	ax,4C01h	;exit w/Errorlevel = 1
	int	21h

case: 	cmp al,91		;upper case?
	jl  upper
      	sub al,32		;convert lower to upper case   

upper:	call parmcheck		;see if H, V, L, R 
	jmp parmloop		;continue parsing

;Check for Horizontal and Vertical Speed, Left/Right/Both button control values
parmcheck:
	cmp	al,'H'		;horizontal speed param ?
	jnz	parmv
	call	digedit		;next byte to AL
        cmp	DigitFlag,'Y'
        jnz	junk		;error if not
	cmp	al,8
	ja	junk
	call	CvtSpeed
	mov	hdelay,ah
	ret			;back to parsing params

parmv:	cmp	al,'V'		;vertical speed param ?
	jnz	parml
	call	digedit		;next byte to AL
	cmp	DigitFlag,'Y'
	jnz	junk		;error if not
	cmp	al,8
	ja	junk
	call	CvtSpeed
	mov	vdelay,ah
	ret

CvtSpeed:
	mov	ah,80h
	sub	al,1		;DEC AL will not set the C flag !!
	jc	CvtRet		;if speed input was 0, use speed 1
	mov	cl,al
	shr	ah,cl		;each number is a factor of 2
CvtRet:	ret

parml:	cmp	al,'L'		;left button param ?
	jnz	parmr
	call	dighex		;get 2 ASCII bytes to AX
	mov	lkey,ax		;left button key code
	ret

parmr:	cmp	al,'R'		;right button param ?
	jnz	parmb
	call	dighex		;get 2 ASCII bytes to AX
	mov	rkey,ax		;right button key code
	ret

parmb:	cmp	al,'M'		;middle button param ?
	jnz	junk		;error if not
	call	dighex		;get 2 ASCII bytes to AX
	mov	mkey,ax		;both buttons key code
	ret

junk:	mov	dx,OFFSET junkmsg
	mov	ah,9
	int	21h
	mov	dx,OFFSET jnkhelp	;help, error message for invalid parms
	jmp	MsgExit

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Check for other copies of this program in memory by walking MCB's.
;See HOTBOOT3 for how to avoid the copy in the disk buffer with shorter code.
;If MOUSER is already resident, params are updated in that copy of the program.
Find:	push	ax
	push	bx
	push	cx
	push	dx
	push	es
	cld				;compare UPward
	mov	ah,52h			;get DOS list of lists
	int	21h			
	mov	bx,es:[bx-2]		;starting MCB seg address
	inc	bx			;for first DEC below
FindLoop:
	dec	bx
	mov	es,bx			;point to the MCB
	add	bx,es:[3]
	inc	bx			;length + 1 points next MCB
	inc	bx			;and 1 more points next block
	mov	es,bx
	mov	ax,cs
	cmp	bx,ax			;BX and ES up to present CS ?
	jae	NoCopies
	mov	si,offset ResFinder	;point DS:SI to our signature
	mov	di,si			;look the same place in both segs
	mov	cx,11			;length of 'MOUSER CODE'
	repz	cmpsb			;DS:[SI] - ES:[DI] and inc SI & DI
	jnz	FindLoop		;if not same, check next block
;Post params in current resident MOUSER
	mov	posted,'Y'		;flag we've done this
	mov	al,vdelay		;copy from this loaded copy
 	mov	es:vdelay,al		; to resident copy
	mov	al,hdelay
	mov	es:hdelay,al
	mov	ax,lkey
	mov	es:lkey,ax
	mov	ax,rkey
	mov	es:rkey,ax
	mov	ax,mkey
	mov	es:mkey,ax
;Re-activate the Microsoft driver  
	mov	ax,0Ch			;set driver active
	mov	cx,00101011b	;'call mask', move or button press (not release)
	mov	dx,offset mouse		;ES:DX points installed Mouse routine
	int	33h
NoCopies:
	pop	es
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;ASCII digits to hex routine
dighex:	call	digedit		;get next byte into AL
        cmp	DigitFlag,'Y'
	jz	digcon
	ret			;otherwise got literal in AX

digcon:	mov	bx,ax		;save hi digit
	call	digedit		;get next byte
	cmp	DigitFlag,'Y'
	jz	ItsOK
	jmp	junk		;error if not
ItsOK:	xchg	ax,bx		;hi byte to AX, lo byte to BX
	mov	cx,10
	mul	cx		;AX=hi byte times 10
	add	ax,bx		;now AX=value
        cmp	al,' '		;is number < 20h ?
	jl	setok		;yes, set as is
	mov	ah,al		;make higher nos the high byte, and
	xor	al,al		; null the low byte for extended keycode
setok:	ret

;Get value of ASCII digit or literal into AL with AH=0
digedit:
	mov	DigitFlag,'N'	;assume literal, not digit
	inc	si			
	mov	al,[si]		;get next byte
	mov	ah,al		;save literal value
	sub	al,30h		;convert digit to hex
	jl	liter		;if < 0, is literal
	cmp	al,9
	jg	liter		;if not digit, is literal
	mov	DigitFlag,'Y'
	xor	ah,ah
	ret

liter:	mov	al,ah		;use literal character
	xor	ah,ah
	ret			;back to param scan

code	ends
        end	Start
