Advanced Programming On The Atari ST
Writing sophisticated programs on the Atari ST requires a more thorough understanding of the computer's operating system than was necessary on earlier machines. This article is an introduction to the various operating system routines available to ST programmers. It is an excerpt from COMPUTE!'s ST Programmers Guide (by the editors of COMPUTE! Publications.)
It seems quite natural to move the mouse pointer to an icon, click on it, and then double-click to open the window. Often you can choose options from menus at the top of the screen by simply pointing and clicking. Click on the menu bar and you can move the window around. You can resize it, expand it to fill the screen, or make the window go away, all with a few clicks of the mouse.
Making things simple for the user requires a wide variety of fairly complex routines for handling input, output, graphics, and so on. These routines are invisible to the user, who simply moves the mouse to point and click. But as a programmer, you may find it helpful to gain some acquaintance with the built-in TOS and GEM routines. The more you know about how they work, the more control you will have over the machine and the more power you can put into programs.
You can call many of these routines in BASIC with the GEMSYS or VDISYS commands, but to get the maximum speed and power from your ST, you'll need either a C compiler or a machine language assembler.
We'll be referring to the various collections of routines by their initials: TOS, VDI, AES, and so on. We'll first look at what they are and what they do.
The Operating System (TOS) is either built into your ST computer in ROM or on a TOS boot disk. The most identifiable element of this operating system is the Graphics Environment Manager (GEM) desktop. However, the operation of TOS involves the interplay of many different, specialized components. The first step toward understanding GEM and TOS is to learn the name and function of each of the parts.
The desktop environment is actually a special type of GEM application, which exists only to perform file operations, including running other GEM applications. Every function that it performs, from examining disk directories to running other applications, can be duplicated by any GEM application using the facilities of the AES, VDI, and GEMDOS.
The GEM Virtual Device Interface (VDI) provides low-level graphics display and mouse input routines. This routine library includes primitive drawing operations like line, marker, circle, and polygon, as well as display management routines like clipping and block image copying.
The GEM Application Environment Services, AES for short, performs higher-level graphic and data management operations for maintaining the GEM desktop environment. Built on top of the GEMDOS file system and the GEM VDI, the AES routines make it much easier for programs to perform mouse and window operations.
The Disk Operating System GEMDOS performs character-oriented file and device input/output (I/O). Many of its routines are used by the GEM AES. GEM applications can also use GEMDOS for file access and for device operations. For the actual low-level operations, GEMDOS calls the Basic Input/Output System (BIOS), a group of routines which perform machine-specific tasks on the ST. The Atari XBIOS provides additional machine-specific operations. They are not used by GEMDOS, but are available to applications which need routines not available through GEMDOS or the GEM BIOS.
AES In Detail
When you double-click on an application's icon, the desktop starts executing that application. Although you can move or resize an application's window, its output appears only in that window. An application can redraw its window when it is partially covered by an accessory without overwriting the accessory's window. When you click on the box at the upper left of an application's window, the application stops executing and the desktop returns.
All of these operations would be much harder to perform without the support of the AES, which is composed of a process dispatcher, a screen manager, a desk accessory buffer, and 11 subroutine libraries. The process dispatcher allows one application and several accessories to wait for a user action simultaneously, a limited form of multitasking. When the system is booted, the accessory buffer is loaded with accessories. The process dispatcher suspends all accessories until a menu item for one of them is selected.
The procedures in the subroutine libraries support common operations on the application environment, the desktop. An application could perform most desktop operations without the AES, using the same VDI routines, but the standard AES routines make the job considerably less difficult. This is a powerful motivation for programmers to make user interfaces more alike, so the programs are easier to learn and use. Nonetheless, nonstandard interfaces can be built where needed, using either VDI or lower-level AES routines.
While the information presented here is not a thorough treatment of the AES, it is intended to be a concise overview of the whole library. It should give you some idea of how the AES is organized, what its constituent parts are and how they interact, and what facilities are available to the GEM programmer. More specific details about each routine's parameters and their values can be found in the Digital Research GEM literature and in sample GEM programs.
The application library is, in effect, the gateway to the rest of the AES. Its appl_init routine is used by an application to register with the AES and obtain a process ID. It also tells the AES to set up data structures to keep track of this application or accessory. When the program has finished, the appl_exit routine tells the AES to deallocate the ID number and data structures for this application.
When waiting for user input from the mouse or keyboard, a program can call one of the routines in the event library instead of waiting in a loop. Besides saving the programmer from writing a few more lines of code, a call to the event library freezes the application and allows possible multitasking, if an event for another frozen process occurs first. Calls are available to wait for keyboard, mouse button, message, or timer events. Messages indicating that some action must be performed--like redrawing or moving a window--are usually awaited with an event library call. The routine most commonly used is evnt_multi, which allows an application to wait for more than one event at once.
The menu_bar routine, in the menu library, is used to display or erase the menu bar. Another routine, menu_register, is called by desk accessories to add a name to the Desk menu. Routines are also available to enable and disable menu items, to change the names of menu items, and to display a menu item in reverse video (as a selected item).
The single most distinguishing feature of the GEM operating environment is the window. The output of applications and accessories running on the ST is displayed on the screen in separate windows, many of which can be moved or changed in size with the mouse. There can be as many as eight windows on the screen at once, and they can overlap in any way, but applications are advised not to use more than four of them if the Desk menu is displayed. Since desk accessories need to have windows available when they are activated, it's best to reserve four of the available windows for accessories. The routines in the window library perform services which are useful in the management of these windows.
The wind_create and wind_delete routines are used to create windows and to dispose of them. When an application calls wind_create to generate a new window, it establishes the maximum possible size of the window and the features with which the window is endowed, such as sliders, a name bar, a full screen box, and so forth. This routine returns a numeric identifier for the new window, called a handle. The wind_open call actually displays the window on the screen at a particular location. Conversely, a window can be hidden with the wind_close routine. The expanding and shrinking box effects that are seen when many applications open and dose windows are not part of the window library subroutines. Rather, they are independent effects routines from the graphics library, which an application can call for a little more flash.
Several window library routines provide information about windows. Wind_get can provide information about windows, including a window's position and size, and the positions of its vertical and horizontal sliders if it is so equipped. It can also return the handle of the top window on the screen (the window with the highest priority), as well as the set of rectangles that make up the visible portion of a window's work area, which can be a irregular shape if a window is partially covered by another. The wind_set routine is used to change the size and position of a window, the positions of its sliders or its name, or the set of controls attached to the window. It can also move one window to the top of the list of windows.
To determine which window the mouse currently points to, the wind_find routine can be called. Wind_calc doesn't operate on any particular window, but instead determines the work area size of a hypothetical window, given its external size and the set of controls it contains. It can also perform the reverse calculation, determining the external dimensions of a window.
The wind_update routine tells AES that an application is going to draw in a window or that an update is finished. When a window is being updated, no alerts, dialogs, or menus will be displayed in front of the window.
Most items on the GEM desktop, including menus, alerts, and even windows, are organized as object trees. GEM objects include icons, strings, graphic boxes, and editable text fields. Trees--linked lists--of these objects can be managed with the routines in the object library. This library includes routines to add and delete objects from a tree, to compare the mouse's position to that of an object, to let the user edit a text object, and to draw the entire tree on the screen. Object trees are usually stored in a file separate from the application that uses them. These resource files can be handled with the resource library, described below.
A form is a standard mechanism for getting information from the user. A form usually includes at least one modifiable object, like a text string or a button. In the case of a dialog, one type of form, the program needs to call the form_dial routine to indicate that a dialog is beginning, and then draw the object tree which comprises the dialog, using the objc_draw routine from the object library. The form_do routine should then be called to perform the interaction. Another call to form_dial restores the area of the screen where the dialog took place.
Two other forms, the alert and the error box, are more limited in their content and, accordingly, easier to implement. In the case of an alert, the AES builds an object tree containing the text string passed in the call to form_alert and handles all the details of display and interaction during that one call. The error box is even simpler. All that is needed is the number of a GEMDOS error code. An object tree containing the text which corresponds with that error will be displayed when form_error is called.
The AES graphics library routines are lower-level interfaces to the display screen and mouse. The most commonly used routine in this library is graf_handle, which returns the handle--the identifier--for the currently opened VDI workstation. Every GEM application must call this routine at its start, after calling appl_init, since the handle is needed to open a new VDI virtual workstation to draw in. It is also necessary for calling VDI drawing routines.
To manage the mouse at a low level, the graf_mouse routine can be used to change the shape of the cursor. Graf_mkstate monitors the positions of the mouse, its buttons, and the keyboard, while graf_ watchbox modifies the state of a box object depending on whether or not the mouse's pointer is inside or outside the box.
Several common graphic effects are also available through the graphics library. Graf_rubberbox draws a rectangle between a fixed point and the mouse's position, changing the size of the box as the mouse moves. A moving box of fixed size attached to the mouse's position can be animated with graf_drawbox, while graf_movebox just moves a box between two positions without any consideration of the mouse. Graf_growbox and graf_shrinkbox are two animation routines which can be called when opening and closing windows, respectively, to show a box which moves and changes size.
The file selector library contains but one lonely routine, fsel_ input. This displays a standard dialog box, called a file selector, on the screen and allows the user to choose a filename from the directories of the various disks in the system. When the interaction is complete, it returns the pathname of the selected file to the calling application.
The objects that an application uses--its menus, dialogs, and so forth--can be stored separately from the application's code in a resource file. Resource files containing these objects are created with the GEM Resource Construction Set program. The resource library can then be used to load this file and to access its contents.
The rsrc_load routine searches for a resource file with a particular name and attempts to load it into memory. Rsrc_gaddr can be used by the application to find the address of a particular object or tree in the resource file that has been loaded. To allow applications to run with different screen resolutions in different display modes, all the sizes and positions of objects in a resource file can be expressed in characters instead of pixels. When the resource file is loaded, rsrc_obfix must be called to convert the sizes into pixels in the current display mode. When a resource file is no longer needed, rsrc_free deallocates the memory space that the resource occupies.
For many kinds of programs, the windowed GEM environment might not be needed, so an alternative is available. A complete set of character-oriented I/O functions is provided in the GEMDOS library. Unlike many operating systems, GEMDOS is easily called from languages like C. Instead of taking parameters in the microprocessor's internal registers, which are not directly controllable in high-level languages, parameters are passed to these routines on the stack in the same way that parameters are passed between C functions.
Even GEM applications need to call GEMDOS at least occasionally. For instance, the AES scrap facility expects applications to store and read the contents of the Clipboard directly from disk. Any program which handles some kind of document--a spreadsheet, word processor, database, and so on--will also need to call GEMDOS to load and store files.
To call GEMDOS, the 68000 microprocessor's TRAP #1 instruction must be executed. The last word pushed on the stack gives the number of the routine requested. If the routine returns a value, it will be stored in the 68000's D0 register. Although this register cannot be directly read by a C program, C functions also return results in this register. A GEMDOS call can returm a value in exactly the same manner as a call to another C function.
|00||Pterm0( ). Terminate with a return code of 0.|
|49||Ptermres(size,code). Terminate, but keep the program's code in memory. A 32-bit parameter indicates how much memory should remain allocated. A 16-bit parameter gives the return code. This function is used by background programs, like print spoolers, to give up control of the foreground process.|
|75||Pexec(runflag,pathname,tail,environ). Load a program from disk. A one-word parameter indicates whether it should be run or not (00=run, 03=load only). The second parameter is a pointer to the pathname of the file. Parameter 3 points to a command tail for the program, and parameter 4 is a pointer to its environment strings. If the file was loaded only, the result of the function is the load address. If it was executed, the result is its return code.|
|76||Pterm(code). Terminate, returning a one-word retum code.|
Device I/O Functions
|01||Cconin( ). Read a character from the console. No parameters are needed.|
|02||Cconout(char). Write a character to the console. A single, word-length parameter contains the character in its low byte.|
|03||Cauxin( ).Read a character from the auxiliary device.|
|04||Cauxout(char). Write a character to the auxiliary device.|
|05||Cprnout(chad. Write a character to the printer.|
|06||Crawio(char). If the parameter is not $00FF, write it as a character to the console; otherwise, return a character from the console with no echo, including control characters.|
|07||Crawcin( ). Read a character from the console with no echo and no control character trapping.|
|08||Cnecin( ). Read a character from the console with no echo, but trap ^C, ^S, and ^Q.|
|09||Cconws(string). Write a zero-terminated string to the console. The long word parameter contains the address of the string.|
|10||Cconrs(buffer). Input a line of characters from the console, allowing line editing. The long word parameter contains the address of a buffer, the first byte of which holds the buffer's length. The second byte of the buffer will get the length of the string, and the string will be zero-terminated.|
|11||Cconis( ). Check the status of the console input device. Returns -1 if a character is waiting, 0 if none is available.|
|16||Cconos( ). Check the status of the console output device. Returns -1 if it is ready to receive a character, 0 if it is not ready.|
|17||Cprnos( ). Check the status of the print device.|
|18||Cauxis( ). Check the status of the auxiliary input device.|
|19||Cauxos( ). Check the status of the auxiliary output device.|
|42||Tgetdate( ). Return the current system date. Bits 0-4 of the result contain the date, 5-8 contain the month, 9-15 contain the year minus 1980 (up to 2099).|
|43||Tsetdate(date). Set the current system date to the word value in the parameter.|
|44||Tgettime( ). Return the current system time. Bits 0-4 contain seconds/2, 5-10 contain minutes, 11-15 contain hours.|
|45||Tsettime(time). Set the current system time to the word value in the parameter.|
|32||Super( ). Enter 68000 supervisor mode.|
|48||Sversion( ). Return the GEM version number.|
|14||Dsetdrv(drive). Set the default disk drive to the value passed as a parameter. Values 0-15 indicate drives A-P.|
|25||Dgetdrv( ). Return the value of the current default drive.|
|54||Dfree(buffer,drive). Ask for information about a disk. The first parameter contains the address of bdffer to receive the information. The second parameter is a 16-bit value indicating the drive being queried. The buffer gets four values: free space, number of clusters on drive, size of sector in bytes, size of cluster in sectors.|
|57||Dcreate(pathname). Create a subdirectory. The long word parameter contains the address of null-terminated string for the pathname of the new directory. If the result is nonzero, the operation failed.|
|58||Ddelete(pathname). Delete a subdirectory.|
|59||Dsetpath(pathname). Set the current default pathname to the string addressed by the parameter.|
|71||Dgetpath(buffer,drive). Store the current directory for a drive in a 64-byte buffer addressed by the first parameter. The second parameter indicates which drive (00=default drive, 01-10 = A-P).|
|26||Fsetdta(DTAbuffer). Use the long word parameter to set the address for a 44-byte file data buffer. This buffer is used only when searching for a file (routines 78 and 79).|
|47||Fgetdta( ). Return the address of the file data buffer.|
|60||Fcreate(pathname,attributes). Create a file named by the string addressed by the first parameter. An additional 16-bit parameter indicates the file's attributes (01 =RO, 02 = hidden). The result is a 16-bit file handle.|
|61||Fopen(pathname,access). Open the file named by the string addressed by the first parameter. An additional 16-bit parameter indicates type of access (00 = read only, 01 = write only, 02 = read and write). The handle of the file is returned as the function's result.|
|62||Fclose(handle). Close a file. The handle of the file is passed as a parameter.|
|63||Fread(handle,count,buffer). Read from a file. The first parameter is the handle of an open file and the second is a four-byte count for the transfer. The third parameter is the address of a buffer to which the bytes are to be read. The result returned by the function is the number of bytes.|
|64||Fwrite(handle,count,buffer). Write to a file. The function uses the same parameters as Fread.|
|65||Fdelete(pathname). Delete the file named by the string pointed to by the parameter.|
|66||Fseek(count,handle,operation). Move the file pointer. The first parameter is a signed long word representing a byte count. The second parameter is a file handle. The third indicates the meaning of the byte count (00=absolute position N bytes after the start of file, 01 =N bytes forward or backward from current position, 02 = N bytes before the end of the file). As a result, it returns the absolute file pointer position.|
|67||Fattrib(pathname,operation,attributes). Read or change the attributes of file. The first parameter is a pointer to a pathname for the file. The second parameter is 00 for get, 01 for set. The third parameter is a word containing the attributes.|
|69||Fdup(handle). Return a copy of the file handle passed as a parameter.|
|70||Fforce(handle1,handle2). Force the first parameter, a file handle, to point to same device as the second parameter, also a handle,|
|78||Fsfirst(pathname,attributes). Search for the first file which matches the search string addressed by parameter 1. The string can contain the * and ? wildcards. Parameter 2 contains the attribute flags of the file. The 44-byte file data buffer set by Fsetdta holds size of file in bytes 26-29, and the file's name and type in bytes 30-43.|
|79||Fsnext( ). Search for another match to the file, using the data buffer at DTA. This function takes no parameters.|
|86||Frename(0,oldname,newname). Rename a file. Parameter 1 is a word with the value 0; parameter 2 is a pointer to the old pathname of the file; parameter 3 points to the new pathname.|
|87||Fdatime(buffer,handle,operation). Get or set a file's date and time information. Parameter 1 is a pointer to a two-word buffer--a time word and a date word. Parameter 2 is a file handle, and parameter 3 is a word which indicates which operation to perform (00=set, 01 = get).|
|72||Malloc(count). Allocate some number of bytes to the calling application. The length of the block requested is passed in the parameter, a long word. It returns a value of 0 if the request fails, or the address of the block allocated if it succeeds, If the parameter has a value of = 1, the number of free bytes is returned instead.|
|73||Mfree(address). Free a block of memory. The four-byte parameter should contain the address of the block.|
|74||Mshrink(0,address,length). Reduce the size of an allocated block of memory. The first parameter must be a word with value 0, the second parameter is the address of the block, and the third parameter is the number of bytes which should remain in the block.|