Appendix B. Apple Events Without AppleScript

Considering AppleScript purely as a vehicle for constructing and sending Apple events to scriptable applications (and for receiving and parsing the replies), and in light of the shortcomings and inconveniences of the AppleScript language, one may reasonably wonder why it might not be possible to construct and send Apple events using some other (possibly more attractive) language. Such an approach would have the advantage of letting you work in a language or a development environment you favor, plus at runtime you'd bypass the overhead of the AppleScript compiler and runtime engine. As a matter of fact, there are several such alternative languages to choose from. It is not at all trivial to mold another language to operate on Apple events in a way that parallels AppleScript's own approach, especially because AppleScript does some odd (or buggy) things under the hood, and also because of the inherent problem of extensible terminology; nevertheless, it can be and has been done. This is not, strictly speaking, an AppleScript matter; indeed, it is quite clearly an anti-AppleScript matter! So in theory, discussion of it should have no place in this book. Nevertheless, my own favorite ways of sending Apple events have often included some non-AppleScript language or other, and the subject is certainly an interesting one, so I've included some information on the topic here, as an appendix.

Here, then, are some ways to send Apple events without using AppleScript. Your choice might depend upon what languages you like or are already using, and upon the nature of a particular language's implementation of Apple events. Throughout this appendix, the "model events" (the Apple events we will attempt to construct and send for purposes of example) will be those corresponding to the following script:

tell application "BBEdit"
    make new document
    tell document 1
        set its text to "Hello, world!"
    end tell
end tell

At programming level, these events provide a nice balance between simplicity and complexity: we're forming a two-level object specifier, which is a fairly interesting thing, without getting too deep into the nitty-gritty. At the user level, the result is pleasantly satisfying, as (if our code is correct) we can see the new window appear in BBEdit with the words "Hello, world!" proudly emblazoned on it.

If you're a Carbon programmer, you can construct a raw Apple event yourself, directly, in code. A number of other languages, such as Objective-C/Cocoa and REALbasic , give you the wherewithal to do the same thing in a possibly more convenient form. Still, no matter how you slice it, building a raw Apple event is a lot of work, and because every Apple event is different, this approach is not particularly general or flexible. You can compensate by developing libraries of wrapper functions, and Apple provides such libraries (such as the MoreAppleEvents sample code available at the developer web site), but it's still a fairly unpleasant procedure.

Here's some code for generating and sending our model events in Objective-C/Cocoa. Actually it's blended with C/Carbon, because Objective-C, curiously, although it provides some fairly convenient ways to construct the pieces of an Apple event, provides no way to send the event once it's been constructed. Also it's a lot more convenient to construct an object specifier in Carbon than in pure Cocoa. For simplicity, issue of memory management and error checking are ignored:

[[NSWorkspace sharedWorkspace] launchApplication:@"BBEdit"];
NSAppleEventDescriptor* bbedit =
    [NSAppleEventDescriptor descriptorWithDescriptorType:typeApplicationBundleID
        data:[@"com.barebones.bbedit" dataUsingEncoding:NSUTF8StringEncoding]];
NSAppleEventDescriptor* ae =
    [NSAppleEventDescriptor appleEventWithEventClass:'core'
                        eventID:'crel'
                        targetDescriptor:bbedit
                        returnID:kAutoGenerateReturnID
                        transactionID:kAnyTransactionID];
[ae setParamDescriptor:[NSAppleEventDescriptor descriptorWithTypeCode:'docu']
                        forKeyword:'kocl'];
AESendMessage([ae aeDesc], NULL,
                        kAENoReply | kAENeverInteract,
                         kAEDefaultTimeout);
ae =
    [NSAppleEventDescriptor appleEventWithEventClass:'core'
                        eventID:'setd'
                        targetDescriptor:bbedit
                        returnID:kAutoGenerateReturnID
                        transactionID:kAnyTransactionID];
[ae setParamDescriptor:
    [NSAppleEventDescriptor descriptorWithString:@"Hello, world!"]
                        forKeyword:'data'];
AEDesc docu1;
CreateObjSpecifier('docu',
                        [[NSAppleEventDescriptor nullDescriptor] aeDesc],
                        formAbsolutePosition,
                        [[NSAppleEventDescriptor descriptorWithInt32:1] aeDesc],
                        YES, &docu1);
AEDesc allText;
CreateObjSpecifier('ctxt', &docu1, formAbsolutePosition,
                        [[NSAppleEventDescriptor descriptorWithDescriptorType:
                            'abso' bytes:"all " length:4] aeDesc],
                        YES, &allText);
NSAppleEventDescriptor* allTextDesc =
    [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&allText];
[ae setParamDescriptor:allTextDesc forKeyword:keyDirectObject];
AESendMessage([ae aeDesc], NULL,
                        kAENoReply | kAENeverInteract,
                        kAEDefaultTimeout);

First we make sure that BBEdit is running. Then we start forming our Apple events. An Apple event is composed of Apple event descriptors, and is itself an Apple event descriptor. Since we're going to be targeting BBEdit, we first form a target descriptor specifying BBEdit. Then we build a "make new" Apple event targeting BBEdit, set its parameter with the four-letter code for "document," and send it. Next we form a "set" Apple event. Filling in the "data" parameter (what we're going to set something to) is easy enough, but filling in the direct parameter is more involved, because this is an object specifier. We form the object specifier in two stages, working from the outside in: first we make an object specifier for "document 1," then we make a second object specifier for "[every] text of" and link it to the first object specifier. We shove the specifier into our Apple event and send it.

For more information about sending raw Apple events with Cocoa and Carbon, see the Apple documentation listed in Appendix C, along with Apple's sample code.

REALbasic's native support for forming raw Apple events remains largely incomplete, but it is sufficient for constructing and sending for the model events. The code is certainly much more compact than doing the same thing in Cocoa and Carbon:

dim ae as AppleEvent
dim theDoc, theText as AppleEventObjectSpecifier
dim s as new shell
s.execute("open -a 'BBEdit'")
ae = NewAppleEvent("core","crel", "R*ch")
ae.macTypeParam("kocl") = "docu"
call ae.send
theDoc = getIndexedObjectDescriptor("docu", nil, 1)
theText = getOrdinalObjectDescriptor("ctxt",theDoc,"all ")
ae = NewAppleEvent("core","setd","R*ch")
ae.objectSpecifierParam("----") = theText
ae.stringParam("data") = "Hello, world!"
call ae.send

For more information, see the REALbasic online help, along with Chapter 31 of my book REALbasic: The Definitive Guide, 2nd ed., O'Reilly Media, 2001 (now out of print).