;* MOUSE - Module of mouse functions. To use it, include the MOUSE.FI and
;* MOUSE.FD files in your program. The following functions are public:
;*
;*   MOUSEINIT      - Initialize mouse
;*   GETMOUSEEVENT  - Get information about most recent mouse event
;*   SETPTRVIS      - Set visibility of pointer to HIDE or SHOW
;*   SETPTRPOS      - Set position of pointer
;*   GETPTRPOS      - Get pointer position and button status
;*
;* The following structure is defined:
;*
;*   EVENT      -   Defines x, y, and mouse status of a mouse event
;*   BTN_STS    -   Defines x, y, and button status of a mouse button
;*
;* This file illustrates the most important principles of writing
;* assembler routines for 32-bit FORTRAN. These include:
;*
;*   - Use .386 and .MODEL flat to set up the appropriate instruction
;*     set and segmentation model.
;*
;*   - Declare FORTRAN routines with STDCALL attribute. Declare C
;*     routines with the C attribute. You can do this globally with
;*     .MODEL or individually on each function.
;*
;*   - If your routine uses EBX, ESI, or EDI, save the registers at
;*     routine entry and restore at exit. You will seldom need to touch
;*     segment registers, but if you do, you must save and restore.
;*     Do not modify FS or GS.
;*
;*   - You can call most DOS and BIOS interrupts. The list of supported
;*     interrupts and functions and the method of calling them is similar
;*     to that of the DOS Protected Mode Interface (DPMI). A specification
;*     for this interface is available from Intel. In summary, functions
;*     that do not pass or receive addresses are used exactly the same
;*     in 32-bit as in 16-bit. Functions that take segmented addresses
;*     in 16-bit take flat addresses in extended registers in 32-bit.
;*     For example, a function that passes an address in DS:DX in 16-bit
;*     passes an address in EDX in 32-bit.

	.386

	; Allow fInit, fWait, and WAIT as names instead of instructions
	OPTION  NOKEYWORD: <fInit fWait WAIT>

	.MODEL  flat, stdcall

;* Internal information used by various mouse functions.
MOUINFO         STRUCT

   fExist       DWORD   1
   fInit        DWORD   0
   fGraph       DWORD   0
   xVirtual     SWORD   0
   yVirtual     SWORD   0
   xActual      SWORD   0
   yActual      SWORD   0
   xLast        SWORD   0
   yLast        SWORD   0
   bitBtnLast   DWORD   0
   cBtn         DWORD   0

MOUINFO         ENDS

;* Mouse event structure
EVENT           STRUCT  4
  x             SWORD   0
  y             SWORD   0
  bitBtn        DWORD   0
EVENT           ENDS

;* Mouse button states structure
BTN_STS         STRUCT  4
  x             SWORD   0
  y             SWORD   0
  Btn           SWORD   0
  BtnState      SWORD   0
BTN_STS         ENDS

;* Structure for getvideoconfig from FGRAPH.FD
VIDEOCONFIG     STRUCT  4
  numxpixels    SWORD   0       ; number of pixels on X axis
  numypixels    SWORD   0       ; number of pixels on Y axis
  numtextcols   SWORD   0       ; number of text columns available
  numtextrows   SWORD   0       ; number of text rows available
  numcolors     SWORD   0       ; number of actual colors
  bitsperpixel  SWORD   0       ; number of bits per pixel
  numvideopages SWORD   0       ; number of available video pages
  mode          SWORD   0       ; current video mode
  adapter       SWORD   0       ; active display adapter
  monitor       SWORD   0       ; active display monitor
  memory        SWORD   0       ; adapter video memory in K bytes
VIDEOCONFIG     ENDS

;* Prototype for C function
_getvideoconfig PROTO   C vc:PTR VIDEOCONFIG

;* Constants for keyboard function
NO_WAIT         EQU     0
WAIT            EQU     1
CLEAR_WAIT      EQU     2

	.DATA

;* Mouse information variable with mouse exist initialized to 1
mi      MOUINFO <1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>

	.CODE

;* MOUSEINIT - Initialize mouse and turns on mouse pointer. Initializes
;* all internal variables used by other mouse functions. This function
;* should be called whenever a new video mode is set, since internal
;* variables are mode-dependent.
;*
;* Params: none
;*
;* Return: 0 if no mouse available, otherwise number of buttons available

MOUSEINIT PROC USES ebx

	LOCAL vc:VIDEOCONFIG
	LOCAL pMode:PTR CHAR

	; Call graphics function to get configuration information
	INVOKE  _getvideoconfig,
		ADDR vc

	mov     mi.fInit, 1     ; We have called initialize
	sub     eax, eax        ; Mouse function 0, reset mouse
	mov     ebx, eax
	mov     mi.cBtn, eax    ; Assume no mouse buttons
	int     33h
	mov     mi.fExist, eax  ; Set existence flag for future calls
	or      ax, ax          ; If AX = 0, there is no mouse
	jz      nomouse
	mov     mi.cBtn, ebx    ; Save number of mouse buttons for return
nomouse:

	.IF !mi.fExist
	    sub eax, eax        ; Return 0 if no mouse
	    ret
	.ENDIF


	; Set graphics flag.
	.IF vc.numxpixels
	    mov     mi.fGraph, 1
	    mov     ax, vc.numypixels
	    dec     ax
	    mov     mi.yActual, ax
	    mov     ax, vc.numxpixels
	    dec     ax
	    mov     mi.xActual, ax
	.ELSE
	    mov mi.fGraph, 0
	.ENDIF

	; The mouse works on a virtual screen of 640 x pixels by
	; (8 * textrows) vertical pixels. By default, it assumes
	; 640 x 200 for 25-line mode. You must call function 8 to
	; adjust for other screen sizes.

	mov     mi.xVirtual, 639
	.IF mi.fGraph
	    ; mi.yVirtual = vc.numypixels - 1;
	    mov     ax, vc.numypixels
	    dec     ax
	    mov     mi.yVirtual, ax
	.ELSE
	    ; mi.yVirtual = (vc.numtextrows SHL 3) - 1;
	    mov     ax, vc.numtextrows
	    shl     ax, 3
	    dec     ax
	    mov     mi.yVirtual, ax
	.ENDIF

	sub     ebx, ebx        ; Clear high word
	mov     eax, 8          ; Set minimum and maximum vertical
	sub     ecx, ecx        ; Minimum is 0
	mov     dx, mi.yVirtual ; Maximum is 8 * rows (or rows SHL 3)
	int     33h             ; Adjust for 25, 30, 43, 50, or 60 lines

	mov     ax, 1           ; Turn on mouse pointer
	int     33h

	mov     ax, 3           ; Get initial position and button status
	int     33h
	mov     mi.xLast, cx    ; Save internally
	mov     mi.yLast, dx
	mov     mi.bitBtnLast, ebx

	mov     eax, mi.cBtn    ; Return the number of mouse buttons

	ret

MOUSEINIT ENDP


;* GETMOUSEEVENT - Check to see if there has been a change in the state since
;* the last call to mouse event. If event occurred, update event structure.
;*
;* Changes detected are: new mouse position and changes in the state of one
;* of the buttons (if a button had been depressed at the time getmouseevent
;* was called and is releasd at the next call, the button state changed).
;*
;* Note: It will NOT detect a button click that happend between calls to
;* getmouseevent; use GetButtonPress instead.
;*
;* Params: pEvent - Pointer to event structure
;*
;* Return: 1 if event, 0 if no event; and may put current position in pEvent

GETMOUSEEVENT PROC USES ebx,
	pEvent:PTR EVENT

	LOCAL rtn:DWORD

	; Make sure that mouse is initialized and exists.
	.IF !mi.fInit
	    INVOKE      MOUSEINIT
	.ENDIF

	.IF !mi.fExist
	    sub    eax, eax     ; Return 0
	    ret
	.ENDIF

	sub     ebx, ebx        ; Clear high word
	mov     ax, 3           ; Get Mouse position and button status
	int     33h
	sub     eax, eax        ; Assume no event

	cmp     cx, mi.xLast    ; Has column changed?
	jne     isevent
	cmp     dx, mi.yLast    ; Has row changed?
	jne     isevent
	cmp     ebx, mi.bitBtnLast  ; Has button changed?
	je      noevent
isevent:
	mov     eax, 1          ; If something changed, event occurred
	mov     mi.xLast, cx    ; Update internal variables
	mov     mi.yLast, dx
	mov     mi.bitBtnLast, ebx
noevent:
	mov     rtn, eax         ; Set return value

	; If event, put adjusted values in structure.
	.IF eax

	    ; If graphics mode, adjust virtual mouse position to actual
	    ; screen coordinates.

	    .IF mi.fGraph

		; pEvent.x = (mi.xLast * mi.xActual) / mi.xVirtual;
		sub     edx, edx
		movzx   eax, mi.xLast
		movzx   ecx, mi.xActual
		mul     ecx

		movzx   ecx, mi.xVirtual
		div     ecx

		mov     ecx, pEvent
		mov     (EVENT PTR [ecx]).x, ax

		; pEvent.y = (mi.yLast * mi.yActual) / mi.yVirtual;

		movzx   eax, mi.yLast
		movzx   ecx, mi.yActual
		mul     ecx

		movzx   ecx, mi.yVirtual
		div     ecx

		mov     ecx, pEvent
		mov     [ecx].EVENT.y, ax

	    ; If text mode, adjust virtual mouse position to 1-based
	    ; row/column.
	    .ELSE

		; pEvent.x = (mi.xLast SHR 3) + 1;
		movzx   eax, mi.xLast
		shr     eax, 3
		inc     eax

		mov     ecx, pEvent
		mov     [ecx].EVENT.x, ax

		; pEvent.y = (mi.yLast SHR 3) + 1;

		movzx   eax, mi.yLast
		shr     eax, 3
		inc     eax

		mov     ecx, pEvent
		mov     [ecx].EVENT.y, ax

	    .ENDIF

	    ; pEvent.bitBtn = mi.bitBtnLast;

	    mov     eax, mi.bitBtnLast
	    mov     [ecx].EVENT.bitBtn, eax

	.ENDIF
	mov     eax, rtn
	ret

GETMOUSEEVENT ENDP



;* GETPTRPOS - Get mouse pointer position and button status regardless of
;* whether there was an event.
;*
;* Params: pEvent - Pointer to event structure
;*
;* Return: 0 if no mouse, otherwise 1 (ignored by FORTRAN)

GETPTRPOS PROC USES ebx,
	pEvent:PTR EVENT

	; Make sure that mouse is initialized and exists.
	.IF !mi.fInit
	    INVOKE MOUSEINIT
	.ENDIF

	.IF !mi.fExist
	    sub     eax, eax
	    ret
	.ENDIF

	sub     ebx, ebx        ; Clear upper word
	mov     ax, 3           ; Get Mouse position and button status
	int     33h
	mov     eax, pEvent
	mov     (EVENT PTR [eax]).x, cx
	mov     (EVENT PTR [eax]).y, dx
	mov     (EVENT PTR [eax]).bitBtn, ebx

	; If graphics mode, adjust virtual mouse position to actual
	; screen coordinates.
	.IF mi.fGraph

	    ; pEvent.x = (pEvent.x * mi.xActual) / mi.xVirtual;

	    sub     edx, edx

	    mov     ecx, pEvent
	    movzx   ecx, (EVENT PTR [ecx]).x
	    movzx   eax, mi.xActual
	    mul     ecx

	    movzx   ecx, mi.xVirtual
	    div     ecx

	    mov     ecx, pEvent
	    mov     (EVENT PTR [ecx]).x, ax

	    ; pEvent.y = (pEvent.y * mi.yActual) / mi.yVirtual;

	    mov     ecx, pEvent
	    movzx   ecx, (EVENT PTR [ecx]).y
	    movzx   eax, mi.yActual
	    mul     ecx

	    movzx   ecx, mi.yVirtual
	    div     ecx

	    mov     ecx, pEvent
	    mov     [ecx].EVENT.y, ax


	; If text mode, adjust virtual mouse position to 1-based
	; row/column.
	.ELSE

	    ; pEvent.x = pEvent.x SHR 3
	    ; pEvent.x = pEvent.x + 1

	    mov     ecx, pEvent
	    movzx   eax, (EVENT PTR pEvent[ecx]).x
	    shr     eax, 3
	    inc     eax
	    mov     (EVENT PTR pEvent[ecx]).x, ax

	    ; pEvent.y = pEvent.y SHR 3
	    ; pEvent.y = pEvent.y + 1

	    movzx   eax, (EVENT PTR pEvent[ecx]).y
	    shr     eax, 3
	    inc     eax
	    mov     (EVENT PTR pEvent[ecx]).y, ax

	.ENDIF

	mov     eax, 1

	ret

GETPTRPOS ENDP



;* SETPTRPOS - Set mouse pointer position.
;*
;* Params: x - column position in text modes, actual x coordinate in graphics
;*         y - row position in text modes, actual y coordinate in graphics
;*
;* Return: 0 if no mouse, otherwise 1 (ignored by FORTRAN)

SETPTRPOS PROC,
	x:DWORD,
	y:DWORD

	; Make sure that mouse is initialized and exists.
	.IF !mi.fInit
	    INVOKE MOUSEINIT
	.ENDIF

	.IF !mi.fExist
	    sub    eax, eax
	    ret
	.ENDIF

	; If graphics, adjust actual coordinates to virtual coordinates.
	.IF mi.fGraph

	    ; x = (x * mi.xActual) / mi.xVirtual;
	    sub     edx, edx
	    movzx   eax, mi.xActual
	    mul     x
	    movzx   ecx, mi.xVirtual
	    div     ecx
	    mov     x, eax

	    ; y = (y * mi.yActual) / mi.yVirtual;
	    movzx   eax, mi.yActual
	    mul     y
	    movzx   ecx, mi.yVirtual
	    div     ecx
	    mov     y, eax

	; If text, adjust row/column to 0-based virtual coordinates.
	.ELSE

	    ; x = x - 1
	    ; x = x SHL 3
	    dec    x
	    shl    x, 3

	    ; y = y - 1
	    ; y = x SHL 3
	    dec    y
	    shl    y, 3

	.ENDIF

	mov     ax, 4               ; Set mouse position
	mov     ecx, x
	mov     edx, y
	int     33h

	mov     eax, 1
	ret

SETPTRPOS ENDP



;* GETBUTTONPRESS - Get button press status and mouse pointer position at the
;* time the button was last pressed. 
;*
;* Params: btns - Pointer to button states structure
;*         btns.Btn  - select button: left 0, right 1, middle 2 (if any)
;*
;* Return: 0 if no mouse, otherwise number of button presses for selected button
;*             since last call to GETBUTTONPRESS
;*         btns.x,btns.y  - is the position of the last button press
;*         btns.BtnState

GETBUTTONPRESS PROC USES ebx,
	btns:PTR BTN_STS

	LOCAL rtn:DWORD

	; Make sure that mouse is initialized and exists.
	.IF !mi.fInit
	    INVOKE MOUSEINIT
	.ENDIF

	.IF !mi.fExist
	    sub     eax, eax
	    ret
	.ENDIF

	mov     eax, btns
	movzx   ebx, [eax].BTN_STS.Btn ;select button
	mov     ax, 5            ; Get selected button's press status and position
	int     33h
	mov     rtn, ebx         ; Set return value (number of times button pressed)
	mov     ebx, btns
	mov     [ebx].BTN_STS.x, cx   ; position of last button press
	mov     [ebx].BTN_STS.y, dx
	mov     [ebx].BTN_STS.BtnState, ax
	mov     [ebx].BTN_STS.y, dx

	; If graphics mode, adjust virtual mouse position to actual
	; screen coordinates.
	.IF mi.fGraph

	    ; btns.x = (btns.x * mi.xActual) / mi.xVirtual;

	    sub     edx, edx

	    mov     ecx, btns
	    movzx   ecx, [ecx].BTN_STS.x
	    movzx   eax, mi.xActual
	    mul     ecx

	    movzx   ecx, mi.xVirtual
	    div     ecx

	    mov     ecx, btns
	    mov     [ecx].BTN_STS.x, ax

	    ; btns.y = (btns.y * mi.yActual) / mi.yVirtual;

	    mov     ecx, btns
	    movzx   ecx, [ecx].BTN_STS.y
	    movzx   eax, mi.yActual
	    mul     ecx

	    movzx   ecx, mi.yVirtual
	    div     ecx

	    mov     ecx, btns
	    mov     [ecx].BTN_STS.y, ax


	; If text mode, adjust virtual mouse position to 1-based
	; row/column.
	.ELSE

	    ; btns.x = btns.x SHR 3
	    ; btns.x = btns.x + 1

	    mov     ecx, btns
	    movzx   eax, [ecx].BTN_STS.x
	    shr     eax, 3
	    inc     eax
	    mov     [ecx].BTN_STS.x, ax

	    ; btns.y = btns.y SHR 3
	    ; btns.y = btns.y + 1

	    movzx   eax, [ecx].BTN_STS.y
	    shr     eax, 3
	    inc     eax
	    mov     [ecx].BTN_STS.y, ax

	.ENDIF

	mov     eax, rtn

	ret

GETBUTTONPRESS ENDP



;* GETBUTTONRELEASE - Get button release status and mouse pointer position at the
;* time the button was last released. 
;*
;* Params: btns - Pointer to button states structure
;*         btns.Btn  - select button: left 0, right 1, middle 2 (if any)
;*
;* Return: 0 if no mouse, otherwise number of button releases for selected button
;*             since last call to GETBUTTONRELEASE
;*         btns.x,btns.y  - is the position of the last button release
;*         btns.BtnState

GETBUTTONRELEASE PROC USES ebx,
	btns:PTR BTN_STS

	LOCAL rtn:DWORD

	; Make sure that mouse is initialized and exists.
	.IF !mi.fInit
	    INVOKE MOUSEINIT
	.ENDIF

	.IF !mi.fExist
	    sub     eax, eax
	    ret
	.ENDIF

	mov     eax, btns
	movzx   ebx, [eax].BTN_STS.Btn ;select button
	mov     ax, 6            ; Get selected button's release status and position
	int     33h
	mov     rtn, ebx         ; Set return value (number of times button released)
	mov     ebx, btns
	mov     [ebx].BTN_STS.x, cx   ; position of last button release
	mov     [ebx].BTN_STS.y, dx
	mov     [ebx].BTN_STS.BtnState, ax
	mov     [ebx].BTN_STS.y, dx

	; If graphics mode, adjust virtual mouse position to actual
	; screen coordinates.
	.IF mi.fGraph

	    ; btns.x = (btns.x * mi.xActual) / mi.xVirtual;

	    sub     edx, edx

	    mov     ecx, btns
	    movzx   ecx, [ecx].BTN_STS.x
	    movzx   eax, mi.xActual
	    mul     ecx

	    movzx   ecx, mi.xVirtual
	    div     ecx

	    mov     ecx, btns
	    mov     [ecx].BTN_STS.x, ax

	    ; btns.y = (btns.y * mi.yActual) / mi.yVirtual;

	    mov     ecx, btns
	    movzx   ecx, [ecx].BTN_STS.y
	    movzx   eax, mi.yActual
	    mul     ecx

	    movzx   ecx, mi.yVirtual
	    div     ecx

	    mov     ecx, btns
	    mov     [ecx].BTN_STS.y, ax


	; If text mode, adjust virtual mouse position to 1-based
	; row/column.
	.ELSE

	    ; btns.x = btns.x SHR 3
	    ; btns.x = btns.x + 1

	    mov     ecx, btns
	    movzx   eax, [ecx].BTN_STS.x
	    shr     eax, 3
	    inc     eax
	    mov     [ecx].BTN_STS.x, ax

	    ; btns.y = btns.y SHR 3
	    ; btns.y = btns.y + 1

	    movzx   eax, [ecx].BTN_STS.y
	    shr     eax, 3
	    inc     eax
	    mov     [ecx].BTN_STS.y, ax

	.ENDIF

	mov     eax, rtn

	ret

GETBUTTONRELEASE ENDP



;* SETPTRVIS - Set pointer visibility.
;*
;* Params: state - SHOW or HIDE
;*
;* Return: 0 if no mouse or invalid range, otherwise 1 (ignored by FORTRAN)

SETPTRVIS PROC,
	pv:DWORD

	; Make sure that mouse is initialized and exists.
	.IF !mi.fInit
	    INVOKE MOUSEINIT
	.ENDIF

	.IF !mi.fExist
	    sub    eax, eax
	    ret
	.ENDIF

	mov     eax, pv         ; Show or hide mouse pointer
	cmp     eax, 1
	jb      EExit
	cmp     eax, 2
	ja      EExit
	int     33h
	mov     eax, 1
Exit:
	ret
EExit:
	sub     eax, eax
	jmp     Exit

SETPTRVIS ENDP


        END

