588 lines
21 KiB
Plaintext
588 lines
21 KiB
Plaintext
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.
|
|
----------------------------------------------------------------------
|
|
|