QBMouse Tutorial

Last Update March 8 2002

This tutorial explains how to use mouse in plain VGA modes. SVGA and Mode-X are out of it's scope. It covers QB 4.5 and QBasic 1.1
How to use mouse in your QB programs?
  1. The Easy way.Use QB4.5 and get a library that supports it , Future, DQB, CosmoX, Rellib, Zephyr etc
  2. The hard way: Do it yourself!
This tutorial covers the B) option. You will learn about:
-

So What's an interrupt?

An interrupt is a special instruction of the procesor that calls a function. There are two kinds of interrupts, hardware and software.
Hardware interrupts are triggered electrically by the hardware when it needs to ask for some attention of fthe processor. Keyboard, ports, disk, almost every device can trigger hardware interrupts. Let's forget about, we don't need them to read the mouse.
The Software Interrupts are the API of DOS, the way you have to access to the DOS functions. Printing to screen, accessing disk, running a program , almost every DOS function is done thru interrupt calls. When you PRINT to screen or GET something data from a file, QB is calling a DOS function by means of an interrupt.
So why you need to bother about interrupts to call mouse? Well, mouse is not a part of DOS, it's an add-in you must load in CONFIG.SYS (if you're in plain DOS) . It was not a common tool back in 1987, when QB4.5 appeared, so Microsoft did'nt implement it. But they left an open door, by allowing you to call interrupts and implement every DOS extension using them. To call an interrupt you need to load the processor registers with the apropiate values, call the interrupt, and at the return, read the results, if any from the processor's registers.

How is this done?

-First you need a good interrupt rreference , the most complete is Ralf brown's list . It's very hard to use because it includes interrupts used by programs and devices you will never see in your life. Maybe someone could point us to a more practical list. For the mouse interrupts, don't worry, I will be explaining the most important ones.

Enabling interrupt use in QB4.5

In QB4.5 there is a libray that does the hard work, how can we use it?:
-To avoid path errors you must firrst setup the all Options/Directories in the IDE to the path where the compiler is, there is installed the default lib and includes of QB.
-First you must ask for the librarry to be loaded, this is done by modifying the way you open QB. this is done by adding /l at the end of the calling command line (in a bat or shortcut) you use to call qb. /l without a filename loads the default library of qb: qb.qlb
myqbpath\qb   mysource /l 
This is the also parameter must you use to enable CALL ABSOLUTE
If you are already using a library, you have a problem, QB allows to load only a single library in the IDE, so you must blend both libraries with the LIB app, This is beyond the scope of this tutorial.
-The library you just loaded has a function INTERRUPTX you can CALL. It will take your data from a Regtype structure, copy them into the processors register, call the interrupt you asked for, and at the return it will copy the results into a Regtype structure, so it can be accessed from your program.
-The interrupt is in a separate file, that's treated as a module by QB. To be able to call a function in a different module you must declare it in the module calling it.
-The RegType user defined type muust be defined in a TYPE statement so we can create variables of this type.
QB has a file with this work made for you: it's called QB.BI, it can be included to our program by adding the line
'$INCLUDE:'QB.BI'
This makes the IDE read the QB.BI file and add it to the current source at the INCLUDE position. It's a sort of automated MERGE.
NOTE: The QB.LIB has another interrupt call, INTERRUPT, whose Regtype structure has a smaller number of registers. You can use the one which can acommodate the particular interrupt and function you are calling. I will be using the bigger one because I must provide also a method of calling interrupts for QBasic1.1 users and I want this tutorial to be the most general.

Enabling interrupt use in QBasic1.1

This downgraded version of QB4.5 does'nt support libraries, so how we coud do it? The only way is to load a pre-made assembler routine calling interrupts into a variable and use CALL INTERRUPTX to access it. Copy this to the start of your program
TYPE RegTypeX
	 ax    AS INTEGER
	 bx    AS INTEGER
	 cx    AS INTEGER
	 dx    AS INTEGER
	 bp    AS INTEGER
	 si    AS INTEGER
	 di    AS INTEGER
	 flags AS INTEGER
	 ds    AS INTEGER
	 es    AS INTEGER
END TYPE
Add this sub to your program
SUB INTERRUPTX (intnum AS INTEGER, InReg AS RegTypeX, OutReg AS RegTypeX)
'standard interrupt call compatibility with QBasic
'some  static variables  
STATIC a() AS LONG, bReady AS INTEGER
'If assembler array not created, create it
IF NOT bReady THEN
    'don't change anything
    I =50:DIM a(1 TO I ) AS LONG
    a(1) = &H53EC8B55: a(2) = &H1E575651: a(3) = &H5E8B9C06: a(4) = &HA078B0E
    a(5) = &HC70774E4: a(6) = &HE9FFFF07: a(7) = &HEC8300A1: a(8) = &HB3F88A0A
    a(9) = &HE85E89CD: a(10) = &HCBEA46C7: a(11) = &H74253C90: a(12) = &H75263C04
    a(13) = &HEA46C714: a(14) = &H46C701E8: a(15) = &HC7CB00EC: a(16) = &H2C2EE46
    a(17) = &HF046C7: a(18) = &H85E8B90: a(19) = &H5E8B37FF: a(20) = &HE37FF06
    a(21) = &H50008FB8: a(22) = &HE85E8D16: a(23) = &H8BDA8C53: a(24) = &H378B0A5E
    a(25) = &H8E0C5E8B: a(26) = &H10448B1F: a(27) = &H75FFFF3D: a(28) = &H50C28B02
    a(29) = &H3D12448B: a(30) = &H275FFFF: a(31) = &HC08EC28B: a(32) = &H5C8B048B
    a(33) = &H44C8B02: a(34) = &H8B06548B: a(35) = &H748B0C7C: a(36) = &H9CCB1F0A
    a(37) = &H83EC8B55: a(38) = &H1E5620C5: a(39) = &H89E476C5: a(40) = &H25C8904
    a(41) = &H89044C89: a(42) = &H7C890654: a(43) = &H12448C0C: a(44) = &H8F10448F
    a(45) = &H448F0A44: a(46) = &HE448F08: a(47) = &H9D0EC483: a(48) = &H5E5F1F07
    a(49) = &HCA5D5B59: a(50) = &H9165000A
   'Checksum, can be ommited 
    S1 = 0: S2 = 0: p = VARPTR(a(1)): DEF SEG = VARSEG(a(1))
      FOR I  = 0 TO 199
        S1 = (S1 + PEEK(p + I )) MOD 255: S2 = (S2 + S1) MOD 255
    NEXT I 
    IF S1 OR S2 THEN ERROR 2: intnum = -1: EXIT SUB ' Checksum Error
   'End of the checksum  
    bReady = -1
END IF
'This is where we call our assembler interrupt calling function
DEF SEG = VARSEG(a(1))
    CALL ABSOLUTE(intnum, VARSEG(InReg), VARPTR(InReg),VARSEG(OutReg), VARPTR(OutReg),0)
END SUB

There are a number of pre-made interrupt calls out there, as many as assembler programmers. I have chosen the one above for having the same parameters QB4.5 uses, this way the rest of the tutorial will can the same for QB4.5 and Qbasic users.

Our first interrupt call

-Well , both QB4.5 and QB1.1 userss are now able to use interrupts in their program so let's go...
We will be calling mouse interrupt function 0 without explanation, it will init and detect mouse. The explanation copmes later. First we create a variableof type Regtype so we can pass values to the interrupt. To receive the results back we will use the same variable, we could use a different one.
DIM SHARED Regs as RegtypeX
The SHARED is to be able to use Regs in the SUBS. This definition must come after the INCLUDE line or QB will not be able to guess what a Regtype is. For QB4.5 users, this is the structure of Regtype, as you can see if you edit QB.BI. QB1.1 users have already seen it, as they had to cut and paste it by hand.
TYPE RegTypeX
	 ax    AS INTEGER
	 bx    AS INTEGER
	 cx    AS INTEGER
	 dx    AS INTEGER
	 bp    AS INTEGER
	 si    AS INTEGER
	 di    AS INTEGER
	 flags AS INTEGER
	 ds    AS INTEGER
	 es    AS INTEGER
END TYPE

We have 8 fields, each one is equated to a 16 bit register of the processor. We need to fill in the form.. We will be using function 0 that does'nt need any parameter. Only the number of the function passed in register ax.So..
Regs.ax=0

Then we call the mouse interrupt, its nr 33H or 51 decimal, that's the same. INTERRUPTX is afunction that takes three parameters, For simplicity we use the same Regtupe varaible Regs for input and fo output
CALL INTERRUPTX(&H33,Regs,Regs)

At return we need to look at Regs for the results. Function 0 returns two results: so:
if REGS.AX then
	print "Mouse Enabled, it has ";Regs.BX;" buttons."
else
	print "Mouse does not exist, or the mouse driver is not loaded"
endif
And that's all for interrupt calling!

Mouse functions

This is a small reference of the available mouse functions. I will write here only the more useful. For the others, go check an interrupt reference.

INT 33,0 - Mouse Reset/Get Mouse Installed Flag

	input:
	AX = 00

	on return:
	AX = 0000  mouse driver not installed
	        FFFF  mouse driver installed
	BX = number of buttons

	- resets mouse to default driver values:
	  .  mouse is positioned to screen center
	  .  mouse cursor is reset and hidden
	  .  no interrupts are enabled (mask = 0)
	  .  double speed threshold set to 64 mickeys per second
	  .  horizontal mickey to pixel ratio (8 to 8)
	  .  vertical mickey to pixel ratio (16 to 8)
	  .  max width and height are set to maximum for video mode

INT 33,1 - Show Mouse Cursor

	input:
	AX = 01

	returns nothing

	- increments the cursor flag;  the cursor is displayed if flag
	  is zero;  default flag value is -1
	- mouse cursor is hiden at init, so you must call function 1 to display it

INT 33,2 - Hide Mouse Cursor

	AX = 02

	returns nothing

	- decrements cursor flag; hides cursor if flag is not zero
	- The cursor can be hidden for two good reasons:
		-You don't want the user can see it  (a good reason!)
		-In graphics modes the mouse driver saves the image behind the cursor, when cursor is moved, the image
		is restored. If you don't hide the cursor before updating an image, when the cursor gets moved the restored
		image will be the one before your update. So hide mouse - update - show mouse.  
					 

INT 33,3 - Get Mouse Position and Button Status

	AX = 03

	on return:
	CX = horizontal (X) position  (0..639)
	DX = vertical (Y) position  (0..199)
	BX = button status:    

		bit 0>     left button (1 = pressed)
	   bit 1>    right button (1 = pressed)
		bits 2-15 >unused       

      - an easy way to use mouse is to poll regularly this function 
      - see table of screen coordinates for different modes at the end of the chapter

INT 33,4 - Set Mouse Cursor Position

	AX = 4
	CX = horizontal position
	DX = vertical position

	returns nothing

	- after a mouse init the cursor is at the screen center
	- the position must be within the range of the current video mode
	- the position may be rounded to fit screen mode resolution
   - see table of screen coordinates for different modes at the end of the chapter

INT 33,7 - Set Mouse Horizontal Min/Max Position

	AX = 7
	CX = minimum horizontal position
	DX = maximum horizontal position

	returns nothing

	- restricts mouse horizontal movement to window
	- if min value is greater than max value they are swapped
   - see table of screen coordinates for different modes at the end of the chapter

INT 33,8 - Set Mouse Vertical Min/Max Position

	AX = 8
	CX = minimum vertical position
	DX = maximum vertical position

	returns nothing
 
	- restricts mouse vertical movement to window
	- if min value is greater than max value they are swapped
   - see table of screen coordinates for different modes at the end of the chapter

INT 33,9 - Set Mouse Graphics Cursor

	AX = 9
	BX = horizontal position of the hot spot (-16 to 16)
	CX = vertical position of the hot spot      (-16 to 16)
	
	ES:DX = pointer to screen and cursor masks (16 byte bitmap)

	returns nothing

      - Graphics cursor is always b/w
	- For hotspot ,the origin of the coordinates (0,0) is the top left corner of the bitmap
          so you can define the hotspot out of the bitmap. 
      - ES:DX points to a 64 bytes array
	     bytes   0-31 form the screen mask bitmap (16x16) 1 bit per pixel
	     bytes 31-63 form the cursor mask bitmap (16x16) 1 bit per pixel
	
	-Pixels corresponding to bits set to 1 in the screen mask are saved to memory, then the pixels 
	corresponding to bits set to 1 in the cursor mask are set white. When cursor is moved, pixels saved
      by the screen mask are restored. 

INT 33,A - Set Mouse Text Cursor

	AX = 0A
	BX = 00  software or attribute cursor
	         01  hardware cursor

      if hardware cursor:                                   if software or attribute cursor:
	CX = cursor start scan line                  mask:    attribute *256+ character to AND   
	DX = cursor end scan line                   mask:    attribute *256+character to XOR
	returns nothing
	-hardware cursor is a rectangle you can shape as you do with the text cursor in QB's LOCATE
      -software/attribute allows you to make colorful cursors. However, if you don't want the char below
           the cursor changed, the char part of AND  and XOR masks must be 255 and 0, in this order.

Coordinates used by mouse functions

Cursor coordinates used in the functions of INT33H does'nt always correspond to pixels or chars in the screen!
So after getting cursor coordinates you will have to rescale them to fit your needs! The coords to use are X in the range 0-639 for all VGA modes, Y depending on screen mode:
Mode 0: 50 lines Y=0-399
Mode0: 25 lines, Modes1, 2, 7, 8, 13 Y= 0-199
Modes 11, 12 Y= 0-479
Modes 9, 10 Y =0-349
'

A sample mouse program

'MOUSE  DEMO
'Modified from a post to QB45.COM by herman 

'QB4.5 users: Start Qb with /lqb
'QBasic1.1 users: erase next line and paste TYPE and SUB  above

'$INCLUDE:'QB.BI'
DIM SHARED Regs AS RegTypeX
CLS: SCREEN 13

Regs.Ax = 0: Mouse        				  'initialize it!
IF Regs.Ax = 0 THEN PRINT "Mouse not present!" : END      'no mouse > no demo
Regs.Ax = 1: Mouse                                        'show it
DO							  'poll it			
	Regs.Ax = 3: Mouse														
	LOCATE ,0: 
      PRINT USING "X: ###  Y:###" ; Regs.Cx ; Regs.Dx ;    'display coords
LOOP UNTIL Regs.Bx = 1                                     'until left button pressed 
END

SUB Mouse                                                      
CALL INTERRUPTX(&H33, Regs, Regs)
END SUB
That's all! You enable mouse, show it and keep on polling it in your program loop. You can compare the coordinates returned by function 3 with the coords of the objects on the screen to know if the user clicks on them. To know if the user clicks, read bit 0 (for left button) and bit 1 (for right button).
If you want to detect double clicks and drags, you will need to compare present state of the buttons with the previous one. If mouse has a button clicked, it has moved from last poll and button was already pressed, it's a drag. If mouse does'nt move and you detect pressed-not pressed and pressed again in a short period of time it's a double click.
You can create your own graphic cursors, it's more or less as creating a sprite and it's mask, but in a single color. If you want a multicolor cursor, you must hide the cursor and keep on PUTting your own sprite where the mouse cursor should be.
In text modes you always have a rectangular cursor, you only can modify its height or its color.

That's all

And that's what i know about mouse! I hope that will be useful to you. E-Mail me your problems. And excuse my poor english!
Antoni Gual
Originally posted at http://www.geocities.com/antonigual/qbasic.html