;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
