AppleEvents 101
Volume Number: 9
Issue Number: 5
Column Tag: C Workshop
From the beginning, how to do AppleEvents
By Jeffrey B. Kane, M.D., Boston, Massachusetts
Note: Source code files accompanying article are located on MacTech CD-ROM orinformation about the source code too.
It’s been over two years since Apple introduced System 7.0 and I still find it
suprising that there is such a dearth of programs which truly take advantage of System
7.0’s new features. The most obvious problem, is finding programs that used the
System 7.0’s new inter-application communication feature, AppleEvents.
AppleEvents provide an easy way for programs to work together, allowing the
user to integrate various tasks quickly and easily. The potential is amazing, word
processors that can use any dictionary program, spreadsheets that can easily ask a
symbolic or numeric mathe-matics program to do complex calculations the
possibilities are endless.
One thing I have noted is a lack of clear, step by step instructions to teach
programmers how to add AppleEvents to their own applications. This article will go
through the various steps you need to implement so that your programs can send,
receive, and process AppleEvents. One side benefit of this process is that it will allow
you to cleanly separate your program’s interface from the core code which does the
work.
Telling the System we are AE aware and System 7.0 savvy
The SIZE resource is more important than ever in system 7.0. The operating
system now uses it to determine what kind of events your application is able to
process. If the appropriate bits are not set properly, the operating system has to go
through a lot of gyrations to fool your program into doing what is necessary. In the
case of AppleEvents you won’t be able to receive or send them, unless these
appropriate bits are set. For our program we set the flags as shown below in Table 1.
The easiest way to set these flags is either to create the resource using ResEdit
with MPW and Think Pascal, or to use the “Set Project Type” menu item in Think C
5.0. Create a SIZE resource with a resource number of -1 for default use by the
system. The system will use our SIZE resource, unless the user changes the memory
size in via the Get Information dialog box, in which case it will create a SIZE id 0
resource with the new parameters.
Table 1. SIZE resouRce flags
example
flag value meaning
acceptSuspend-Resume
Events (formerly
isMulti finder-Aware) true tells us when we need to
convert the clipboard
canBackground false we don’t do anything in the
background, so we don’t
need this information
onlyBackground false we are a normal application
that runs in the foreground.
getFrontClicks false if someone clicks in one of
our windows when we are
in the background, just
bring us to the front
(resume), don’t also send
us a mousedown event too.
is32Bit-Compatible true Think C generates 32 bit
clean code
HighLevelEvent-Aware true lets us send and receive
AppleEvents.
localAndRemote-HLEvents true let other machines on the
network send us
isStationary-Aware false we don’t save any files, let
alone use stationery pads in
this simple example
useTextEdit-Services false we don’t use TextEdit in
this example
Checking if the system supports AppleEvents
Checking Gestalt
If you are using any of the current development environ-ments (MPW, Think
Pascal, or Think C), the prefered method of checking your system’s environment is to
use the new Gestalt functions which are available in System 6.0.4 or later. These
development environ-ments contain the glue necessary to check if the Gestalt trap is
available under the current operating system. If Gestalt is not found the glue calls
SysEnvirons for you, and return the results as if it came from Gestalt. As per Apple’s
current guidelines we will check to make sure that each system attribute we need is
available on the user’s CPU.
Gestalt lets the program query for specific environmental information. To use
Gestalt you pass it a selector and you get a response. If the selector is an attribute, it
returns the result as a bit field, with the appropriate bit set if the desired feature is
available. If the selector is a size, or type, it returns a value in the response.
In our program we will test if AppleEvents, the PPC toolbox, and color are
available. The calls look like:
/* 1 */
theErr = Gestalt(gestaltAppleEventsAttr, &response);
if BitTst(&response,31-gestaltAppleEventsPresent)
gHasAE = true;
theErr = Gestalt(gestaltPPCToolboxAttr,&response);
if BitTst(&response,31-gestaltPPCToolboxPresent)
gHasPPC = true;
theErr = Gestalt(gestaltQuickdrawFeatures, &response);
if BitTst(&response,31-gestaltHasColor)
gHasColor = true;
Note that the Macintosh toolbox routines number the bits in an order opposite to
the conventional assembly language and Motorola form(0 to 31 instead of 31 to 0).
Many development systems (including Think Pascal and MPW Pascal) offer equivalent
routines which use the normal syntax for numbering bits.
The attributes returned by the selector gestaltPPCToolboxAttr deserve some
special attention. If the bit “gestaltPPCSupportsRealTime” is not set, then we need to
initialize the PPC Toolbox. The current version of the PPC Toolbox only supports real
time mode (it can only talk to programs that are up and running), so a properly
initialized toolbox will always set this bit. If the bit gestaltPPCSupportsOutGoing is
not set then the user has not turned on AppleTalk, so the PPC Toolbox cannot send
messages to other machines on the network. If the bit gestaltPPCSupportsIncoming is
not set, then either the user hasn’t turned on AppleTalk from the Chooser desk
accessory, or they have turned off file sharing in the Sharing Setup control panel. If
you need any of these features and they are missing, you can then put a a dialog box
requesting that the user activates them.
Interacting with the user
The old Mac programing model we assumed that the user was always nearby, but
scripting and AppleEvents bring up the possibility of unattended machines. In Wingz
and Resolve, when an error is encountered from within a script, an alert appears,
halting the script until the user dismisses it. Obviously this method of dealing with
errors is inadequate, e specially if the machine is on an unattended server.
System 7.0 controls interaction with the user using a new call,
FUNCTION AEInteractWithUser(
timeOutInTicks: LONGINT;
nmReqPtr: NMRecPtr;
idleProc: IdleProcPtr): OSErr;
By calling this routine before each dialog is supposed to appear, two things are
accomplished. Firstly, the System will automatically do nothing, notify the user, or
bring the application to the front, whichever is appropriate. Secondly, the result
returned by this function will tell the program whether or not it should display its
error alert window. Both the client (sending application) and server ( target
application) determine what kind of interaction is allowed. When you initialize you
program you can use the default response of only having the user interact with events
sent from other programs on their own machine, or you can override this default by
using the function
FUNCTION AESetInteractionAllowed(level: AEInteractAllowed): OSErr;
Possible values to pass to AESetInteractionAllowed are:
kAEInteractWithSelf Only allow interaction if the current
program sends AppleEvents to itself
kAEInteractWithLocal (the default) Only allow interaction if
the a program running on the user’s
CPU sends the AppleEvents
kAEInteractWithAll Allow user interaction if the
AppleEvent is sent locally, or through
the network
As we will see, when we send an AppleEvent one of the parameters also specifies
what kind of user interaction is requested by the sending program.
Patch the main event loop to get AppleEvents
Now that the world knows that we can do AppleEvents, it’s time to patch our
program to accept them. In our WaitNextEvent loop we need to add cases for two new
types of events, operating system events, and high level events.
Check for Operating system events
Operating system events are the events that tell us when major context switching
between applications has taken place. Suspend and resume events will tell us if the
user is switching between applications under Multi finder. Mouse moved events are
reported if the user moves the mouse out of a predefined region that we passed to
WaitNextEvent at the beginning of the event loop.
Check for High Level Events
High level events such as AppleEvents or private PPC events are new to system
7.0. We add both of these to the standard main event loop as shown below:
/* 2 */
if (WaitNextEvent(everyEvent, &theEvent, 15, nil) {
switch (theEvent.what) {
case mouseDown:
DoMouseDown(&theEvent);
break;
.
.
.
// process the rest of your events
// just like you always do
.
.
.
case osEvt:
DoOSEvt(&theEvent);
break
case kHighLevelEvent:
if (gHasAppleEvents)
{
// check if the events
// message and where fields
// are some private PPC event
// that you have defined,
// otherwise assume it is
// an AppleEvent
AEProcessAppleEvent(&theEvent);
} /* gHasAppleEvent is true */
} /* end of switch */
} /* end of WaitNextEvent */
The call AEProcessAppleEvent is a new toolbox routine that lets the system
dispatch the AppleEvent to the proper application defined routine that handles it. We
will write the various routines to handle each AppleEvent, then register them with the
system so they can be called. This level of abstraction allows us to add handlers from
any locked code resource. One obvious use is to add routines that can process new
AppleEvents via XCMDs or other external code resources without changing the original
code of existing programs .
Process OSEvt to be Multifinder cool
Operating system events store their information in the Event record in a special
way. By examining the message field of the Event Record we determine what kind of
event it is - either a suspend/resume or mouse moved event.
Check the most significant byte of the event message to see what kind of event it is
First, look at the most significant byte of the event’s message field. If this byte
is set equal to the constant suspendResumeMessage then we are being either switched in
or out under Multi finder. If the byte is equal to the constant mouseMovedMessage then
the user has moved the mouse out of the predefined region we passed to WaitNextEvent.
/* 3 */
/* get rid of the last 24 bits */
topByte = theEvent.message>> 24;
/* needed because C does sign extended shifting */
topByte = topByte & 0x00FF;
Suspend Resume message
If the first byte of the message indicates that we have a suspend or resume event,
then look at bit zero of the message to tell us which it is. Apple defines the mask
resumeFlag to be 1 so if:
(theEvent.message) & resumeFlag
is zero, then our application is about to be suspended. We should respond to this event
by converting any private clipboards we have and put it on the scrap. We should also
execute our Deactivate event handler, and set any global variables we have defined to
tell us that we are now a background task.
If the above bit is set, then we are resuming. To test if the clipboard has been
changed, we test bit one. Apple defines the mask convertClipboardFlag as two so if:
(theEvent.message) & convertClipboardFlag
is not zero then we know to store the clipboard back into our private scrap.
Mouse Moved message
The mouse moved message saves us from constantly having to check the mouse
position to adjust the appearance of our cursor. By setting a mouseMoved region, the
system will let us know when we should check to see if the cursor needs to change
shape. In our example we define two regions, the content region of our window (less
the scroll bar and grow regions) over which we wish the cursor to appear as a plus.
We define the rest of the screen (including any additional monitors) as a second region,
over which we set the cursor to the standard arrow. Every time we receive a mouse
moved event, we first note which region the cursor is now in and appropriately adjust
it’s shape. We then pass this current region back to WaitNextEvent so the system will
tell us if the user moves the mouse out of the current region. It is also important to
remember to readjust the region if the user manipulates our window, such as
dragging, zooming, closing, etc.
Grab High Level Events
If the what field of the event indicates that we have received a high level event,
then the message and where fields have special meaning. You can check these fields to
see if they are equal to any special PPC event that you have defined, and so, jump to
your own special code to handle those events. If you don’t recognize the event as one of
your own PPC events then pass it to AEProcessEvent. The system uses AEProcessEvent
to dispatch the event to the appropriate AppleEvent handler which has been previously
been registered with the system. If AEProcessEvent does not have a proper handler
registered with it, it notifies the sending program that the event could not be
processed.
Type of event (message)
High level events use the message and where event fields in a special way. The
message contains the AppleEvent class, usually grouping AppleEvents by functional
groups. The core event class are those basic events that can be implemented by most
Macintosh programs. The Apple Event registry contains other classes, such as the text
edit class, that contains those events that relate to editing any kinds of text based