Classic Computer Magazine Archive COMPUTE! ISSUE 92 / JANUARY 1988 / PAGE 76

ST Outlook

Philip I. Nelson

Anatomy Of A Desk Accessory

We all know what a desk accessory is. It's one of those gadgety little programs that lives in the Desk menu in the upper left corner of the screen. Whether you're running a GEM application or just noodling around on the desktop, accessories are ready and waiting to be used. This feature is not the same as true multitasking, since an accessory completely freezes the main application while it's active, and you can't have more than six accessories in memory at once. But it's a workable, practical scheme; and as a result, nearly every ST owner has a collection of favorite desk accessories.

An accessory sounds like a flashy item, and it is, but you can create one with surprising ease. The program listing with this article contains C source code for a complete, fully functional desk accessory. It compiles exactly as listed with Megamax C. The code may require minor tweaking for other compilers, specifically in the function named Assembly, which contains two in-line assembly language instructions. Don't feel bashful about modifying the program or adding your own code. It's provided as a skeleton—a minimal working example that you can flesh out to create an accessory of your own.

GEM Messages

Apart from the fact that they're useful, accessories provide an occasion for investigating the shadowy domain of GEM messages. Like the contents of a diplomat's briefcase, GEM messages have great importance, although they seldom see the light of day.

Messages are important because GEM can never be sure which process may be running at a given moment. Right now you may be running a word processor, but two minutes later you might pull down the Control Panel accessory to adjust the keyboard speed, and five minutes after that you might call the Install Printer accessory to prepare for printing a document.

Some form of interprocess communication is needed to keep processes from tangling one another and bringing down the whole system. The actual work of scheduling applications—deciding who gets to run and who doesn't—is done by GEM itself. But it's the job of each individual process to avoid tripping others.

An accessory's role is not unlike that of a minor actor in a play. You must be onstage and paying constant attention, ready to spring into life and speak your piece on cue. But you must never leap onto center stage at the wrong moment, just when the star of the production is about to begin a soliloquy.

Checking In

Fortunately, like the bit player in our fictional drama, a desk accessory has only a few cues to listen for. To learn what they are, let's take a brisk walk through the program listing.

The very first call in the program is to a function called appl_init. As its name suggests, appl_nit initializes the application, saying, in effect, "Here I am, GEM." Until GEM knows that you exist, you can't create any graphics with VDI functions, or call any AES functions to create dialog boxes, menus, and similar GEM features.

The program next calls a function named menu_register, which, for a desk accessory, is the equivalent of registering at a hotel. After this call, your accessory has an official place in the Desk menu, including a unique menu ID number to distinguish it from other accessories in that menu. In plain English, menu_register says something like, "I want to be a desk accessory. Sign me up and give me a badge."

Don't Wake Me Unless…

Once checked in, every good accessory is expected to go to sleep until awakened. But like a weary hotel guest who expects an important call, the accessory first tells GEM what messages it considers important enough to rouse it from its slumber.

The next function call in the program has two purposes. It both informs GEM which messages we want to hear about, and allows us to slip into the background. The name of this function is evnt_multi and it allows an application to watch for as many as six distinct events, simultaneously.

This call is complex because evnt_multi allows for so many different possibilities. Our accessory, however, cares about only one of those possibilities—the opening of a desk accessory—so most of the evnt_multi items are filled with dummy values or addresses.

The first parameter we pass to evnt_multi is a manifest constant named MU_MESAG. To GEM, this value says, "Send me a message whenever somebody touches the Desk menu." If we had been interested in other events, such as keyboard clicks or mouse activity, the values representing those events would have been added to this parameter.

The other item of interest here is Msg_Buffer, a 16-byte array. By passing the address of Msg_Buffer to GEM, we establish a pigeonhole where GEM can deposit message data.


In terms of program flow, our code comes to a complete halt here. No more program statements are executed until GEM tells us the designated event has taken place. A typical accessory spends nearly all of its time in this peculiar, wakeful rest in which, as the doctor said of the sleepwalking Lady Macbeth, it can "receive at once the benefit of sleep and do the effects of watching." In the meantime, you are using the computer for some other purpose.

Waking Up

Eventually, somebody chooses the accessory from the menu. Here's where Msg_Buffer becomes important. If someone selects an accessory—any accessory—from the Desk menu, then the value AC_OPEN appears in Msg_Buffer[O], the first byte of the Msg_Buffer array. And if our accessory happens to be the one selected, GEM puts our menu ID value into Msg_Buffer[4], the fifth byte.

The next three program statements (SWITCH, CASE, and IF) test the two values in Msg_Buffer. We could have combined both tests into one large IF statement, but that would make the program less useful as a platform for further development. You might write an accessory that's interested in events other than AC_OPEN, or you might write one that registers more than one entry in the Desk menu. This general structure can accommodate both needs. (The EMULATOR.ACC accessory, by the way, is an example of an accessory that registers two entries in the Desk menu.)

Assuming we survive both tests, it's time to perform our appointed mission, whatever that is. This accessory is a demo, so it plays a brief, harmless joke and then goes back to sleep.

When chosen, this accessory puts up a simple dialog with the form_alert function and waits for you to click OK. Then it calls the ROM routine that TOS normally uses to paint bombs on the screen after an operating system exception. The bombs look ominous, but they don't mean a thing. The computer works exactly as usual, and the shapes disappear as soon as you do something to refresh the screen area where they appear. If you're using this program as a skeleton, of course, you'll want to replace the bomb-painting code with something useful; delete the entire function named Assembly and the Supexec call that invokes it.

Immortality And Subversion

Before leaving this program, note one final singularity of desk accessories: They run forever. Just before the evnt_multi call, is the statement while(1), which puts the program into an endless loop. After the accessory wakes up and does its business, it immediately reenters the loop and makes another evnt_multi call, which puts it back to sleep. Once installed, an accessory runs continuously until you press the reset button or turn off the computer.

This everlasting quality, combined with an accessory's ability to spring into the foreground at any time, calls for some extra caution in programming. An accessory should follow a strict good-neighbor policy, never hogging resources unnecessarily or making unanticipated, irrevocable changes in the environment. If it allocates memory when waking up, it should release all that memory before going back to sleep, and so on.

Perhaps the most novel of accessories is one that installs itself as usual but never registers in the Desk menu. This highly subversive concept may be best suited to practical jokes, but maybe you can think of a sensible use f or it. Such an accessory won't have a menu ID, and it won't show up in the Desk menu, but it shares every other feature that we've described.

Magic Desk Accessory

#define AC_OPEN 40	/* Means an accessory was opened		*/
#define MU_MESAG 0x0010	/* Means menu message event			*/
#define BOMBS 0xfc0a70	/* Address of the bomb routine in ROM	*/
#include <osbind.h>	/* GEMDOS, XBIOS, BIOS definitions		*/
extern int gl_apid;
extern long Assembly();
char Our_Name[ ]  =  "Magic Desk";
char No_No[ ]  =  "[1] [I wish you | hadn't done that.] [ Bye ]";
  main( ) {
   int Event, Dummy, Menu_ID, Msg_Buffer[8];
   int contrl[12], intin[128], ptsin[128], intout[128], ptsout|128];
   /* Tell GEM that we exist	*/
   appl_init( );
   /* Check in at the front desk	*/
   Menu_ID  =  menu_register( gl_apid, Our_Name );
   /* Loop forever. Accessories never terminate.	*/
   while( 1 )
   /* Go to sleep until an event of interest wakes us	*/
   Event  =  evnt_multi(MU_MESAG, /* We want menu event messages…  */
    1,1,1,0,0,0,0,0,0,0,0,0,0, /*
    Lotsa things we don't care about */
    Msg_Buffer, /* This is the address of our message pipe buffer */
    0, 0, &Dummy, &Dummy, &Dummy, &Dummy, &Dummy, &Dummy); /* More chaff. */
  /* Check the contents of the message buffer */
  switch* Msg_Buffer[0] )
   /* Did someone open an accessory?	 */
   case AC_OPEN:
   /* Is the opened accessory OUR accessory?	 */
   if( Msg_Buffer[4] = =  Menu_ID )
    { /* Our accessory was opened. Do something. */
  form_alert( 1, No_No);
  /* Execute this routine in supervisor mode	 */
   Supexec( Assembly );
   /* All done. Go back to sleep until next time.	*/
  }       /* close if        */
 }        /* close switch    */
}         /* close while     */ }       /* close main */
/* This function calls a ROM routine to paint a	*/
/* wide swath of harmless bombs on your screen.	*/
 extern long Assembly( 	)
  { asm {
           move.l #39,D1
           jsr BOMBS
           } }