Programming the Microsoft Mouse
                     -------------------------------

                      "A Mouse! What A Great Idea!!"

                                 -W. Disney


          Written for the PC-GPE by bri (accbpf@vaxc.hofstra.edu)
               and Mark Feldman (pcgpe@geocities.com)


Disclaimer
----------

We assume no responsibility whatsoever for any effect that this file, the
information contained therein or the use thereof has on you, your sanity,
computer, spouse, children, pets or anything else related to you or your
existance. No warranty is provided nor implied with this information.

Introduction
------------

Programming the mouse is one of the easiest tasks you'll ever have to
undertake. Mouse drivers hook themselves into interrupt 33h, so you
can just call driver-functions the same way you would BIOS functions.

Basics
------

The first step is to initialize the mouse driver. You do this by setting
the ax register to 0 and calling interrupt 33h. This will return a
non-zero number in ax if the driver is installed(which usually means if
the mouse is installed. From here on in, anything I say about the mouse
actually refers to the driver).

To display the mouse cursor on the screen, set ax to 1 before calling int 33h.
In text-mode you should get what appears to be a block-like text cursor, and
in any graphics mode you should get the arrow by default(although we'll see
how to change this later). The driver detects what mode you're in and draws
the appropriate cursor.

To hide the mouse cursor, you set the ax register to 2 and call the interrupt.
Showing and hiding the mouse cursor is something you'll probably have to do
often when you draw images to the screen. Believe you me, having a mouse move
across something you're drawing can really wreck you're display. To get around
this, hide the mouse cursor, draw what's necessary, and re-display the mouse
cursor.

Please note, mouse drivers often keep a 'display counter' that reads 0
if the cursor is displayed and less then 0 if its hidden. Consecutive
calls to hide the mouse cursor will decrement the counter and make it
more and more negative-in which case it will take more calls to display
the mouse cursor to get the counter to 0. Once the counter is 0, calls
to display the mouse cursor have no effect on the counter. To read the
state of the counter, you can call function 2Ah, and the counter is
returned in the ax register. I'll touch on function 2Ah a bit more later.

Last but not least, we should be able to figure out if any buttons are
pressed, if so which ones, and where the mouse is.  This is easy, just
set ax to 3 and call int 33h-the horizontal coordinate is returned
in the cx register, the vertical coordinate in the dx register and
bx has the button status. In bx, each bit reads 1 if a corresponding
button is pressed-for example bit 0 is 1 if the left button is pressed,
bit 1 is 1 if the right button is pressed, and bit 2 is 1 if the
center button is pressed.

As for the coordinates-be careful, as a lot of mouse drivers use a "virtual
screen" of 640x200 pixels-which mean if you're screen resolution isn't this-
you'll have to do some converting.


SETTING THE CURSOR SHAPE
------------------------

When you use your mouse in a graphics mode, by default, you're stuck
with the shape of the mouse cursor as an arrow. This is fine most of the
time, but it can get boring after a while. Don't fret!!! You can alter the
shape to accomodate your needs!

The graphics mode cursor image is a 16-by-16 pixel area on the screen
that is defined by a 64 byte buffer passed to int 33h, function 9. The first
32 bytes contain the cursor mask-the appearance of the cursor on the screen.
The second 32 bytes is the screen mask-it defines the appearance of the
screen image under the cursor. In this 64 byte buffer, each bit corresponds
to 1 pixel, i.e. the first two bytes in both of the masks corresponds to the
16 pixels that make up the top row of the cursor.

When you're designing the cursor mask, each bit is 1 if it is displayed,
and 0 if it is not. On the screen mask, bits that are 1 are transparent.

The mouse driver takes the screen mask, and the cursor mask and shoves
them together, coming up with the following:


Screen Mask Bit is      Cursor Mask Bit is      Resulting Screen Bit is
------------------      ------------------      -----------------------
        0                        0                         0
        0                        1                         1
	1                        0                     Bit is Not Changed
	1                        1                     Bit is Inverted

To set the shape you call Function 9h with ES holding the segment of your
buffer containing the masks, and DX containing the offset of the buffer
containing the cursor mask.

One other important thing to note: your cursor has a hot spot=the point
on your mouse cursor that is where the cursor is actually pointing.
Usually this is 1, 1(that is 1 pixel from the top of the cursor and
1 from the right). But when you change the image of your mouse cursor
you might need to change the hot spot. You can do this by setting CX
and DX of Function 9 to the horizontal and vertical offset of the hot
spot.

ODDS AND ENDS
-------------

Well, there are a few odds and ends here that you might find useful.

You can set limits on where you want to allow the mouse cursor to roam.
Function 7 & 8(AX= 7 & AX = 8) set the horizontal and vertical limits
of the mouse cursor respectively. In both cases you input cx as the minimum
coordinate in pixels, and dx as the maximum coordinate in pixels.

You can take the mouse and move it to a certain position on the screen
from inside your program. That's function 4(ax=4). You specify the
horizontal coordinate in CX, and the vertical coordinate in DX. If you
specify a coordinate outside a range you've set using functions 7 & 8,
the mouse driver will most likely put the cursor at the very edge of
that boundary.

Lastly, you can set the amount of distance you're actual mouse moves
to get the cursor on the screen to move. Mouse movement is measured
in mickeys(I'm not joking here!) where each mickey is approximately
1/200 of an inch. To adjust this use function 0Fh. CX should contain
the number of horizontal mickeys, and dx the number of vertical ones.
The numbers in CX and DX actually refer to the amount of mickeys needed
to move the mouse cursor a total of 8 pixels. By default, CX is 8, while
DX is 16. You can set a range of 1(hyper-active energetic mouse) to
32,767(unbelievably sluggish and lazy).


SLAM DUNKS AND LOW CEILINGS
----------------------------
(With special thanks to Feldman the Great for this section)

Yes, like the subject header, something else that has never mixed very well
together was mouse and SVGA programming. The reason, of course is that your
mouse driver is what takes care of the updating of the image of the mouse
cursor in graphics mode.

You see, in the beginning, SVGA was created. This was widely regarded
as a bad idea.

Sure it looked cool, and there were more pretty colors than you could shake
a kaleidoscope at, but there was no standard. (This was before VESA, and sadly
even today many mouse drivers don't use VESA) This left all SVGA chip
makers to deviously make chips however they wanted, depending on what
kind of mood they were in, and what they had had for lunch. As most
SVGA chip designers rarely ate the same thing at lunchtime, you wound up
with a googleplex full of SVGA cards that all were 110% incompatible
with each other.

Remember what I said about your mouse driver handling your mouse cursor.
Now mouse drivers had to know how to handle every single SVGA card, so
they could draw the cursors correctly in SVGA mode. Mouse driver
maufacturers got around this problem in a rather novel way: they ignored
SVGA.

Because of this, most mouse drivers throw up their hands in disgust when
confronted with the ugly head of SVGA and simply provide you with no mouse
cursor at all. Likewise, if you're programming for Mode X, you're likely to
run into the same trouble. You CAN get around this, however. How? you ask
with baited breath. Simply install your own mouse handler(I'm using the word
'simply' rather loosely here). What this will do, is cause the mouse driver
to call one of your functions-and then you're responsible for updating the
graphics cursor image on the screen.

Basically, you call Function 0Ch. CX contains the event mask: on what
conditions the mouse driver will call your function. The mask is listed
below:
		Bit   If set
		 0    Mouse Cursor Movement
		 1    Left Button Pressed
		 2    Left Button Released
		 3    Right Button Pressed
		 4    Right Button Released
		 5    Center Button Pressed
		 6    Center Button Released

The ES register holds the segment of your mouse code that the driver
should call, and DX holds the offset. (As an aside, I'd recommend
doing the mouse handler itself in assembly, as getting it to work
in C or Pascal is an uphill struggle at best).

When the mouse driver calls your function, AX will contain the event
flag that you set earlier. BX will contain the button status: 0 if
the left button is pressed, 1 if the right button is pressed, and 2
if the center button is pressed. CX and DX contain the horizontal and
vertical position of the mouse cursor respectively.

To disable an installed handler, simply call function 0Ch with an
event mask of 0, or call Function 0h.

Well that about wraps it up...if you have any questions at all,
please feel free to contact me (bri) at accbpf@vaxc.hofstra.edu and I'll
do my best to answer them.

Quick Reference Guide to Interrupt 33h
--------------------------------------

FUNCTION: AX = 0h
	Description: Determines whether a mouse is available and if it is
		     initializes the mouse driver.
	Returns    : AX = Non-Zero (If Mouse is installed, 0 if not)
		     BX = Number of Mouse Buttons
FUNCTION: AX = 1h
	Description: Increments the mouse cursor display counter.
	Returns    : Nothing

FUNCTION: AX = 2h
	Description: Decrements the mouse cursor display counter.
	Returns    : Nothing

FUNCTION: AX = 3h
	Description: Returns the current mouse position and button status.
	Returns	   : BX = Buttons status:
			  Bit 0: Left Button
			  Bit 1: Right Button
			  Bit 2: Center Button
		     CX = Horizontal Coordinate
		     DX = Vertical Coordinate

FUNCTION: AX = 4h
	Description: Moves the mouse cursor to a certain position on the screen.
	Call  With : CX = Horizontal Coordinate
		     DX = Vertical Coordinate
	Returns    : Nothing

FUNCTION: AX = 5h
	Description: Reports on the status and numbers of presses for a
		     button.
	Call with  : BX = Button to Check
  			  0 = Left Button
			  1 = Right Button
		          2 = Center Button
	Returns    : AX = Button Status
		          Bit 0 = Left Button
			  Bit 1 = Right Button
			  Bit 2 = Center Button
		     BX = Button Press Counter
 		     CX = Horizontal Coordinate of Last Button Press
		     DX = Vertical Coordinate of Last Button Press

FUNCTION: AX = 6h
	Description: Gets the button release information.
	Call With  : BX = Button to Query
		       0 = Left Button
		       1 = Right Button
		       2 = Center Button
	Returns    : AX = Button Status(1 if pressed)
		       Bit 0 = Left Button
		       Bit 1 = Right Button
		       Bit 2 = Center Button
		     BX = Button Release counter
		     CX = Horizontal Coordinate of last button release
		     DX = Vertical Coordinate of last button release

FUNCTION: AX = 7h
        Description: Sets the horizontal limits for the mouse cursor.
	Calls With : CX = Minimum horizontal mouse coordinate.
	             DX = Maximum horizontal mouse coordinate.

FUNCTION: AX = 8h
	Description: Sets the vertical limits for the mouse cursor.
        Call With  : CX = Minimum vertical mouse coordinate.
		     DX = Maximum vertical mouse coordinate.
	Returns    : Nothing

FUNCTION: AX = 9h
	Description: Defines the shape of the graphics mode cursor.
	Call With  : BX = Hoorizontal hot spot offset
	  	     CX = Vertical hot spot offset
		     ES = Segment of buffer containing cursor mask
		     DX = Offset of buffer containing cusror mask

	Returns    : Nothing

FUNCTION: AX = 0Ah
        Description: Definesthe shape of the text mode cursor
	Call With  : BX = Cursor Type
		         0 = Software Cursor
			 1 = Hardware cursor
		     if BX = 0 then
		        CX = Screen Mask value
		        DX = Cursor Mask Value
		     else
 			CX = Starting Scan Line For Cursor
			DX = Ending Scan Line For Cursor

FUNCTION: AX = 0Bh
	Description: Returns the net mouse movement since the last call
		     to this function(or since the mouse was initialized).
	Returns    : CX = Horizontal mouse movement(in Mickeys)
		     DX = Vertical mouse movement(in Mickeys)

FUNCTION: AX = 0Ch
	Description: Sets the user defined mouse handler.
	Call With  : CX = Event Mask

		Bit   If set
		 0    Mouse Cursor Movement
		 1    Left Button Pressed
		 2    Left Button Released
		 3    Right Button Pressed
		 4    Right Button Released
		 5    Center Button Pressed
		 6    Center Button Released

		     ES = Segment of your mouse handler code
		     DX = Offset of your mouse handler code
	Returns    : AX = Event Mask
		     BX = Button Status(1 if pressed)
		       Bit 0 = Left Button
		       Bit 1 = Right Button
		       Bit 2 = Center Button
                     CX = Horizontal Coordinate
		     DX = Vertical Coordinate

FUNCTION: AX = 2Ah
	Description: Returns display counter state, and current hot spot
	Returns    : BX = Horizontal offset of hot spot
		     CX = Vertical offset of hot spot



A complete list of mouse function calls can be found in the file GMOUSE.TXT,
the file contains calls for both Microsoft (2 button) and Genius (3 button)
modes.

Writing Custom Handlers
-----------------------

Most mouse drivers do not support SVGA modes, so you must write custom
handlers if you want mouse support for these modes.

Rather than writing an entire mouse driver, you can write a simple handler
routine to take care of the graphics and tell the mouse driver to call it
whenever the mouse does anything. This function is descibed in the GMOUSE.DOC
file, but this demo Pascal program shows the general idea. It sets mode 13h,
resets the mouse and waits for a key to be pressed. Whenever you do anything
to the mouse (moving it or pressing a button) the handler will get called
and it will draw a pixel on the screen. The color of the pixel depends on
which buttons are being pressed.

Uses Crt, Dos;

{$F+}
{ called with bl = buttons, cx = x * 2, dx = y }
procedure Handler; far; assembler;
asm

  { This mouse "handler" just draws a pixel at the current mouse pos }
  pusha
  push es                  ; pusha doesn't save es
  mov ax, $A000
  mov es, ax
  shr cx, 1
  xchg dh, dl
  mov di, dx
  shr dx, 2
  add di, dx
  add di, cx
  mov al, bl
  inc al
  stosb
  pop es
  popa
end;
{$F-}

begin
  asm

    { Set graphics mode 13h }
    mov ax, $13
    int $10

    { Initialize mouse driver }
    xor ax, ax
    int $33

    { Install custom handler }
    mov ax, SEG Handler
    mov es, ax
    mov dx, OFS Handler
    mov ax, 12
    mov cx, $1F
    int $33

    { Wait for a key press }
    xor ah, ah
    int $16

    { Back to text mode }
    mov ax, 3
    int $10
  end;
end.




Alternatively you may wish to write your own interrupt handler to process
mouse events as they happen. When a mouse event occurs, 3 interrupts are
generated and the bytes are available via the COM port.

                  ----------------------------
                  |       Interrupt    Port  |
                  ----------------------------
                  | COM1      $0C       $3F8 |
                  | COM2      $0B       $3F8 |
                  ----------------------------

The three bytes sent are formatted as follows:


               1st byte        2nd byte         3rd byte
          -----------------   ---------------   ---------------
          |-|1|?|?|X|X|Y|Y| - |0|X|X|X|X|X|X|   |0|Y|Y|Y|Y|Y|Y|
          -----------------   ---------------   ---------------
               | | \ / \ /           |                |
               | |  |   |            |                |
               | |  |   ------------- ----------      |
               | |  ----------       |        \ \     |
               | |          \_\_ _ _ _ _ _ _   \_\_ _ _ _ _ _ _  
               | |          |_|_|_|_|_|_|_|_|  |_|_|_|_|_|_|_|_|
               | |             X increment        Y increment
 Left Button --  |
Right Button ---- 


The X and Y increment values are in 2's compliment signed char format. (BTW
thanks go to Adam Seychell for posting this info to comp.os.msdos.programmer).


A simple Borland Pascal 7.0 mouse handler follows. First we declare a few
things we'll need:



----------------------------------------------------------------------
Uses Crt, Dos;

{$F+}

const COM1INTR = $0C;
      COM1PORT = $3F8;

var bytenum : word;
    combytes : array[0..2] of byte;
    x, y : longint;
    button1, button2 : boolean;
    MouseHandler : procedure;
----------------------------------------------------------------------

The bytenum variable is used to keep track of which byte is expected next
(ie 0, 1 or 2). The combytes variable is simply an array to keep track of
bytes received so far. The mouse position will be stored in the x and y
variables (note that this example will not perfrom any range checking).
button1 and button2 will be used to store the states of each of the buttons.
MouseHandler will be used to store the normal mouse driver event handler.
We'll also need it to reset everything once we are finished.

Here's the actual handler:

----------------------------------------------------------------------
procedure MyMouseHandler; Interrupt;
var dx, dy : integer;
var inbyte : byte;
begin

  { Get the port byte }
  inbyte := Port[COM1PORT];

  { Make sure we are properly "synched" }
  if (inbyte and 64) = 64 then bytenum := 0;

  { Store the byte and adjust bytenum }
  combytes[bytenum] := inbyte;
  inc(bytenum);

  { Have we received all 3 bytes? }
  if bytenum = 3 then
    begin

      { Yes, so process them }
      dx := (combytes[0] and 3) shl 6 + combytes[1];
      dy := (combytes[0] and 12) shl 4 + combytes[2];
      if dx >= 128 then dx := dx - 256;
      if dy >= 128 then dy := dy - 256;
      x := x + dx;
      y := y + dy;
      button1 := (combytes[0] And 32) <> 0;
      button2 := (combytes[0] And 16) <> 0;

      { And start on first byte again }
      bytenum := 0;
    end;

  { Acknowledge the interrupt }
  Port[$20] := $20;
end;
----------------------------------------------------------------------

Once again pretty simple stuff. We just read the byte from the com1 port and
figure out if it's time to do anything yet. If bit 6 is set to 1 then we
know that it's meant to be the first byte of the 3, so we reset our
bytenum variable to zero (in a perfect world bytes would always come in 3's
and we would never need to check, but it never hurts to be careful).

When 3 bytes have been received we simple decode them according to the
diagram above and update the appropriate variables accordingly.

The 'Port[$20] := $20;' command just lets the interrupt controller know we
have processed the interrupt so it can send us the next one when it wants to.

Note that the above "handler" does nothing more than keep track of the
current mouse position and button states. If we were writing a proper mouse
driver for an SVGA game we would also have to write custom cursor routines.
I'll leave that bit to you!

To actually install our mouse driver we'll have to set up all the variables,
save the address of the current mouse handler and install our own. We'll
also need call the existing mouse driver to set up the COM1 port to make
sure it sends us the mouse bytes as it receives them. We could do this
ourselves, but why make life harder than it already is?

----------------------------------------------------------------------
procedure InitMyDriver;
begin

  { Initialize the normal mouse handler }
  asm
    mov ax, 0
    int $33
  end;

  { Initialize some of the variables we'll be using }
  bytenum := 0;
  x := 0;
  y := 0;
  button1 := false;
  button2 := false;

  { Save the current mouse handler and set up our own }
  GetIntVec(COM1INTR, @MouseHandler);
  SetIntVec(COM1INTR, Addr(MyMouseHandler));
end;
----------------------------------------------------------------------


And finally when our program is finished it'll need to clean up after
itself and return control back to the normal mouse driver:

----------------------------------------------------------------------
procedure CleanUpMyDriver;
begin
  SetIntVec(COM1INTR, @MouseHandler);
end;
----------------------------------------------------------------------


This little bit of source will test the above code. It does nothing more
than repeatedly write the mouse position and button states to the screen
until a keyboard key is pressed:

----------------------------------------------------------------------
begin
  ClrScr;
  InitMyDriver;
  while not keypressed do
    WriteLn(x : 5, y : 5, button1 : 7, button2 : 7);
  CleanUpMyDriver;
end.
----------------------------------------------------------------------
1