OS-9/OSK Answers! by Joel Mathew Hegberg
Premier issue


Oh my! Is it summer already? It seems to have come so fast! Time sure flies when you're having fun, and with all the recent CoCoFests, that exactly what a lot of us have been having! It's great to see our computers and operating systems getting so much support with so many gatherings.

One of my favorite parts of the fests is seeing what other people have been doing with OS-9 and learning/teaching new tricks to one another. I generally deal with large applications, but applications are just a consolidation of a great many software techniques that an author (or programming team) has learned. What I hope to accomplish with this monthly column is the sharing of software techniques and algorithms that are useful under OS-9, regardless of the programming language being used. Be it Assembly, BASIC, C, etc., I welcome any comments or suggestions that you may have for other OS-9 programmers.

Additionally, any questions you may have regarding OS-9 applications programming are welcome. I've written a variety of applications under OS-9, including a graphical word processor, a graphical desktop, graphical clock, games, small utilities, and more. I will do my best to answer your questions as simply as possible, although technicality is inevitable with this type of column.

For those readers who are not yet into OS-9: Believe it or not, I know how you feel. (Really, I do!) It took me five tries to get into OS-9. The first four times each ended up in failure, frustration, and a vow to give up on OS-9. I remember attending fests when Marty Goodman, during his seminars, would ask all the OS-9'ers to raise their hands, and I would silently scowl at the growing size of the OS-9 user base. I didn't see anything so great about OS-9, yet more and more users were migrating to it -- and with few complaints. Let me say if you just don't like OS-9, but have never really been able to play around with it yet, you're missing out. It's that simple. I would have never believed it myself, being the devout RS-DOS programmer I once was, but it really is better to have an operating system that can do a lot at one time, even if there are fewer "flashy" programs available for it. What OS-9 lacks in flashy programs, it makes up a hundred fold in work efficiency.

For you OS-9 readers out there, I know a lot of you are saying, "I love using OS-9, but I can't seem to write my own programs under it." Well, you've turned to the right page, my friend. Write in any problems you may be having, including source code snipits, and we'll set you on the right track! After all, that's what this new magazine is all about -- Support!

Now, on with this month's topic... Making your BASIC09 programs mouseable!

Over the past couple of years, a few people have asked me how to use the mouse under BASIC09. Unfortunately, the GFX2 module does not provide any mouse-reading capabilities to make the task trivial. To read the mouse you must use OS-9 system calls, which can be a little confusing to the average OS-9 BASIC09 programmer. This article may be difficult for a non- or novice-programmer to comprehend, but I believe an average BASIC09 programmer will be able to understand.

To read in mouse data, the I$GetStt (GetStat) system call is used along with the _GS_Mouse call (which is incorrectly labeled _SS_Mouse on page 8-123 of the Technical Reference manual). When this call is used, a 32-byte mouse data packet is sent to your program for examination. Here are some important offsets inside the mouse data packet (numbers in decimal):

   Offset  Size  Data Contained
   ======  ====  ===================================================
      0     1    pt_valid... 0 if user is in a different window.
      8     1    pt_cbsa... 1 if mouse button A is pressed.
      9     1    pt_cbsb... 1 if mouse button B is pressed.
     24     2    pt_acx... Mouse's X-position.
     26     2    pt_acy... Mouse's Y-position.

When using "syscall" (for information, see pg 11-166 in the BASIC09 manual) and the _GS_Mouse windowing function, you must load the X-register with the address of the area you have reserved for the mouse data packet to be loaded into, which must be (at least) 32 bytes in length. Additionally, (looking at pg 8-123 in the Technical Reference manual) register A needs the path number, B needs to be $89 (the _GS_Mouse function code), and Y is the "port select". This sounds a lot more complex than it really is. The path number should be 1, which refers to the current window a program is running in, and "port select" should always be 0, for automatic selection, since you do not know if the user has their mouse in the left or right joystick port. To get the address of your reserved area, use the ADDR function (pg 11-6 in the BASIC09 manual). Here's how to set-up what I've just tried to explain:

TYPE registers=cc,a,b,dp:byte; x,y,u:integer  \ (* for "syscall" *)
DIM regs:registers  \ (* for "syscall" *)
DIM callcode:byte   \ (* for "syscall" *)
DIM mouse_data:string[32]  \ (* 32-byte area where I want my mouse data to go *)
DIM valid,mouse_x,mouse_y,button_A,button_B:integer  \ (* For next example... *)
regs.a=0
regs.b=$89
regs.x=ADDR(mouse_data)
regs.y=0
callcode=$8D  \ (* $8D is the I$GetStt code *)
run syscall(callcode,regs)

There, not too difficult. After that is run, "mouse_data" will contain the mouse data packet given to you by OS-9 (isn't that nice of OS-9). Now, all you need to do is PEEK at the contents of "mouse_data" and you can extract all the information you need from the packet. Glancing again at pg 8-123 in the Technical Reference manual, you can see that upon exit, register X still contains the data storage area address that we gave it before we ran "syscall". So, to get the mouse's X- and Y-positions, button, and pt_valid information, you could use:

valid=PEEK(regs.x)
button_A=PEEK(regs.x+8)
button_B=PEEK(regs.x+9)
mouse_x=PEEK(regs.x+24)*256+PEEK(regs.x+25)
mouse_y=PEEK(regs.x+26)*256+PEEK(regs.x+27)
print "Mouse data; valid:";valid;" cbsa:";button_A;" cbsb:";button_B;
print " X:";mouse_x;" Y:";mouse_y

Again, not so bad. Realize that this program will not display the mouse pointer on screen, only mouse information.

The best way to learn, in my opinion, is through example, so I'm including a program listing that will show how you can also check for keypresses as well as mouse button clicks without pausing program operation. It is also important not to waste CPU time if the user has moved to a different window (which is when pt_valid is 0). You do this by going to sleep. The sleep technique is also shown in the program listing. (Note: All of the page numbers given in the program listing are in the Technical Reference manual.)

This program must be run from a graphics screen. To create the proper screen, use the command:

WCREATE /Wx -s=8 0 0 40 24 0 4 2
SHELL i=/Wx&

where x is replaced with the window number you wish to create. Use the CLEAR key to find the newly created window and run the program from there. This will give you a 40-column (320-pixel) graphics screen.

Routine 10000 merely updates the mouse pointer on the screen (and loads in the mouse data packet into "mouse_data"). Routine 15000 (which calls routine 10000 to update the mouse, and get the mouse data packet) simply waits until the mouse button is not being pressed and the user is in the current window. Routine 20000 will update the mouse pointer, check for a mouse click, check for a keypress, and sleep if the user leaves to a different window. Comments are included in the source code to help explain what's going on each step of the way.

PROCEDURE MouseTest

 TYPE registers=cc,a,b,dp:byte; x,y,u:integer
 dim regs:registers
 dim callcode:byte
 dim mouse_x,mouse_y:integer
 dim mouse_data:string[32]
 dim keypress$:string[1]

 gosub 5000
10 gosub 15000  \ (* Make sure mouse button is not pressed *)
 gosub 20000  \ (* Wait until mouse button or key is pressed *)
 run gfx2("CURXY",0,20)
 run gfx2("EREOLINE")
 if keypress$="" then
   print "Mouse click at X:";mouse_x;", Y:";mouse_y;
 else
   print "Keypress: ";keypress$;
 endif
 goto 10
5000 REM Turn mouse cursor on.
 run gfx2("GCSET",$ca,1)
 return
10000 REM Update mouse position on screen.
 regs.a=1  \ (* Path number 1, which is the current window. *)
 regs.b=$89  \ (* Code $89 is the SS.Mouse function, pg 8-123 *)
 regs.x=addr(mouse_data)  \ (* Load the mouse information into mouse_data *)
 regs.y=0  \ (* Automatic port selection *)
 callcode=$8D  \ (* $8D is the I$GetStt callcode, pg 8-54 *)
 run syscall(callcode,regs)  \ (* Go out and get the mouse information *)
 mouse_x=peek(regs.x+24)*256+peek(regs.x+25)  \ (* Get mouse's X-position *)
 mouse_y=peek(regs.x+26)*256+peek(regs.x+27)  \ (* Get mouse's Y-position *)
 run gfx2("PUTGC",(mouse_x)/2,mouse_y)  \ (* Move the mouse to new location *)
 REM Note in the line above that the x-coor is divided by 2.  This is only if
 REM   you are on a 320-pixel (40 column) graphics screen.  If you are on a
 REM   640-pixel (80 column) graphics screen, get rid of the "/2".
 REM   Unfortunately, CoCo-3 Windows does not scale the positioning of the
 REM   graphics cursor, so the programmer must take care of that.
 return
15000 REM Wait until mouse button is released and user is in current window.
 gosub 10000  \ (* Update mouse position and load in mouse data into mouse_data *)
 if peek(regs.x+8)<>0 then 15000  \ (* Mouse button is pressed, so wait *)
 if peek(regs.x)=0 then 15000  \ (* User is in another window, so wait *)
 return
20000 REM Wait until user clicks mouse button or presses a key.
 REM Upon exit, if keypress$="" then the mouse button was pressed, and mouse
 REM   coordinates are in mouse_x and mouse_y.
 REM If keypress$<>"" then a key was pressed, and is stored in keypress$.
 keypress$=""  \ (* Clear out keypress$ *)
20010 gosub 10000  \ (* Update mouse and load mouse data packet. *)
 if peek(regs.x)=0 then  \ (* The user is in a different window... *)
   callcode=$0A  \ (* so go to sleep!  Don't waste CPU time!  $0A is F$Sleep code, pg 8-35 *)
   regs.x=60  \ (* Sleep for 60 ticks, which is about 1 second *)
   run syscall(callcode,regs)  \ (* Go to sleep *)
   goto 20000  \ (* Ok, awake again, go back and see if user has returned. *)
 endif
 if peek(regs.x+8)<>0 then  \ (* Mouse button was pressed. *)
   return  \ (* Exit back to main program. *)
 endif
 regs.a=0  \ (* path #0, keyboard input. *)
 regs.b=1  \ (* Code 1 is SS.RDY function to see if keyboard data is waiting, pg 8-113 *)
 callcode=$8D  \ (* $8D is I$GetStt code, pg 8-54 *)
 run syscall(callcode,regs)  \ (* Go and check if there is any keyboard data waiting. *)
 if regs.b<>$F6 then  \ (* There is data waiting! *)
   callcode=$89  \ (* $89 is I$Read code, to read data from a device. *)
   regs.a=0  \ (* Path #0, keyboard input. *)
   regs.y=1  \ (* Read only 1 byte/keypress. *)
   regs.x=addr(keypress$)  \ (* Load the data into keypress$ *)
   run syscall(callcode,regs)  \ (* Go and read the keyboard data *)
   return  \ (* Exit back to main program. *)
 endif
 goto 20010  \ (* No keypress or mouse click, so go back and try again. *)

Any questions or comments for OS-9/OSK Answers! may be sent to Joel Mathew Hegberg in care of 68Micros magazine. You may also reach Joel electronically at the following e-mail addresses:

JOELHEGBERG on Delphi. joelhegberg@delphi.com on the internet.

* THE END *