Section 9.15. Apple Events


9.15. Apple Events

Mac OS X includes a system-wide user-level IPC mechanism called Apple Events. An Apple Event is a message capable of encapsulating arbitrarily complex data and operations. The Apple Events mechanism provides the framework for transporting and dispatching these messages. Apple Events communication can be intraprocess or interprocess, including between processes on different networked computers. In general, one entity can request information or services from another entity by exchanging Apple Events.

AppleScript is the preferred scripting system on Mac OS X, providing direct control of applications and of many parts of the system. You can use the AppleScript scripting language to write programs (or AppleScripts) to automate operations and exchange data with or send commands to applications. Since AppleScript uses Apple Events to communicate with applications, the applications must be able to understand such messages and perform the requested operationsthat is, the applications must be scriptable. The Mac OS X Cocoa and Carbon frameworks provide support for creating scriptable applications.[16] Most GUI-based Mac OS X applications support at least some basic Apple Events, such as the ones used by the Finder for launching an application and providing a list of documents for it to open.

[16] Java applications, although typically not scriptable, can be made scriptable to some extent without including native code.

AppleScript has a syntax similar to natural language. Consider the example shown in Figure 952.

Figure 952. AppleScript program to speak the system version

-- osversion.scpt tell application "Finder"     -- get "raw" version     set the version_data to system attribute "sysv"     -- get the 'r' in MN.m.r, where MN=major, m=minor, r=revision     set the revision to ((version_data mod 16) as string)     -- get the 'm' in MN.m.r     set version_data to version_data div 16     set the minor to ((version_data mod 16) as string)     -- get the 'N' in MN.m.r     set version_data to version_data div 16     set the major to ((version_data mod 16) as string)     -- get the 'M' in MN.m.r     set version_data to version_data div 16     set major to ((version_data mod 16) as string) & major     -- paste it all together     set the os_version to major & "." & minor & "." & revision     set the message to "This is Mac OSX " & os_version     say message     return os_version end tell

Mac OS X provides a standard, extensible mechanism called the Open Scripting Architecture (OSA), which can be used to implement and use Apple Eventsbased IPC in any language.[17] AppleScript is the only OSA language provided by Apple. The osalang command-line tool lists all installed OSA languages.

[17] There exist third-party implementations for other languages such as JavaScript.

$ osalang -l ascr appl cgxervdh  AppleScript scpt appl cgxervdh  Generic Scripting System


The first column in osalang's output is the component subtype, followed by the manufacturer, capability flags, and the language name. Each letter (unless it is the - character) in the capability flags string indicates whether a particular group of optional routines is supported. For example, c means that compilation of scripts is supported, whereas r means that recording scripts is supported. The Generic Scripting System entry is a pseudo-entry that transparently supports all installed OSA scripting systems.

You can use the osascript command-line tool to execute AppleScriptsor scripts written in any installed OSA language. Let us run our sample script from Figure 952 using osascript.

$ osascript osversion.scpt 10.4.6


This will result in several Apple Events being sent and received. The Finder will be instructed to retrieve the Mac OS X system version, which will be stored in the version_data variable. A human-friendly version string and a message announcing the version will be constructed, after which the Finder will run the say AppleScript command to convert the announcement string to speech.

If you wish to see internals of the Apple Events generated because of running your AppleScript, you can run it with one or more AppleScript debugging environment variables set. For example, setting each of the AEDebugVerbose, AEDebugReceives, and AEDebugSends environment variables to 1 will print an excruciatingly detailed trace of Apple Events being sent and received.

$ AEDebugVerbose=1 AEDebugSends=1 AEDebugReceives=1 osascript osversion.scpt AE2000 (2185): Sending an event: ------oo start of event oo------ { 1 } 'aevt':  ascr/gdut (ppc ){           return id: 143196160 (0x8890000)      transaction id: 0 (0x0)   interaction level: 64 (0x40)      reply required: 1 (0x1)              remote: 0 (0x0)   target:     { 2 } 'psn ':  8 bytes {       { 0x0, 0x2 } (osascript)     }   optional attributes:     < empty record >   event data:     { 1 } 'aevt':  - 0 items {     } } ... { 1 } 'aevt':   - 0 items {     } } ------oo  end of event  oo------ 10.4.6


The AppleScript Studio application (included with Xcode) can be used to rapidly develop complex AppleScripts, including those with user-interface elements. You can also compile your textual AppleScripts into stand-alone application bundles.


Let us look at some more examples of using Apple Events, including how to generate and send Apple Events in C programs.

9.15.1. Tiling Application Windows Using Apple Events in AppleScript

This example is an AppleScript program called NTerminal.scpt, which communicates with the Mac OS X Terminal application (Terminal.app) and instructs it to open and tile a given number of windows. Running NTerminal.scpt will launch Terminal.app if it is not already running. Then, a given number of Terminal windowsas specified through the desiredWindowsTotal variablewill be opened. If there are already desiredWindowsTotal or more windows open, no further Terminal windows will be opened. Finally, NTerminal.scpt will tile desiredWindowsTotal windows in a grid, with desiredWindowsPerRow windows per row.

Note that the script is naïveit does not handle varying window sizes that would lead to complex arrangements. Moreover, its real-life utility is superfluous, since Terminal.app already supports saving window arrangements in .term files for subsequent restoration.


Figure 953 shows the NTerminal.scpt program. You can adjust the desiredWindowsTotal and desiredWindowsPerRow parameters based on the size of the display screen.

Figure 953. AppleScript program for opening and tiling Terminal application windows

-- NTerminal.scpt tell application "Terminal"     launch     -- Configurable parameters     set desiredWindowsTotal to 4     set desiredWindowsPerRow to 2     -- Ensure we have N Terminal windows: open new ones if there aren't enough     set i to (count windows)     repeat         if i >= desiredWindowsTotal             exit repeat         end if         do script with command "echo Terminal " & i         set i to i + 1     end repeat     -- Adjust window positions     set i to 1     set j to 0     set { x0, y0 } to { 0, 0 }     set listOfWindows to windows     repeat         if i > desiredWindowsTotal then             exit repeat         end if         tell item i of listOfWindows             set { x1, y1, x2, y2 } to bounds             set newBounds to { x0, y0, x0 + x2 - x1, y0 + y2 - y1 }             set bounds to newBounds             set j to j + 1             set { x1, y0, x0, y1 } to bounds             if j = desiredWindowsPerRow then -- Move to the next row                 set x0 to 0                 set y0 to y1                 set j to 0             end if         end tell         set i to i + 1     end repeat end tell

9.15.2. Building and Sending an Apple Event in a C Program

In this example, we "manually" craft and send Apple Events to the Finder. We will send two types of events: one that will cause the Finder to open the given document using the preferred application for the document's type and another that will cause the Finder to reveal the given document in a Finder window.

We will use the AEBuild family of functions to construct in-memory Apple Event structures, which can be sent to other applications through the AESend() function. While constructing Apple Events using the AEBuild functions, we use an event description language that employs C-style formatting strings to describe events. The AEBuild functions parse the programmer-provided strings to yield event descriptors, simplifying an otherwise painful process wherein we would have to construct such event descriptors incrementally.

An event descriptor record is an arbitrarily ordered group of name-value pairs, where each name is a four-letter type code, and the corresponding value is a valid descriptor. The name and value within a name-value pair are separated by a colon, whereas multiple name-value pairs are separated by commas.

Let us compile and run the program shown in Figure 954.

Figure 954. Sending Apple Events to the Finder from a C program

// AEFinderEvents.c #include <Carbon/Carbon.h> OSStatus AEFinderEventBuildAndSend(const char   *path,                           AEEventClass  eventClass,                           AEEventID     eventID) {     OSStatus     err = noErr;     FSRef        fsRef;     AliasHandle  fsAlias;     AppleEvent   eventToSend = { typeNull, nil };     AppleEvent   eventReply  = { typeNull, nil };     AEBuildError eventBuildError;     const OSType finderSignature = 'MACS';     if ((err = FSPathMakeRef((unsigned char *)path, &fsRef, NULL)) != noErr) {         fprintf(stderr, "Failed to get FSRef from path (%s)\n", path);         return err;     }     if ((err = FSNewAliasMinimal(&fsRef, &fsAlias)) != noErr) {         fprintf(stderr, "Failed to create alias for path (%s)\n", path);         return err;     }     err = AEBuildAppleEvent(               eventClass,            // Event class for the resulting event               eventID,               // Event ID for the resulting event               typeApplSignature,     // Address type for next two parameters               &finderSignature,      // Finder signature (pointer to address)               sizeof(OSType),        // Size of Finder signature               kAutoGenerateReturnID, // Return ID for the created event               kAnyTransactionID,     // Transaction ID for this event               &eventToSend,          // Pointer to location for storing result               &eventBuildError,      // Pointer to error structure               "'----':alis(@@)",     // AEBuild format string describing the                                      // AppleEvent record to be created               fsAlias         );     if (err != noErr) {         fprintf(stderr, "Failed to build Apple Event (error %d)\n", (int)err);         return err;     }     err = AESend(&eventToSend,                  &eventReply,                  kAEWaitReply,      // Send mode (wait for reply)                  kAENormalPriority,                  kNoTimeOut,                  nil,               // No pointer to idle function                  nil);              // No pointer to filter function     if (err != noErr)         fprintf(stderr, "Failed to send Apple Event (error %d)\n", (int)err);     // Dispose of the send/reply descs     AEDisposeDesc(&eventToSend);     AEDisposeDesc(&eventReply);     return err; } int main(int argc, char **argv) {     switch (argc) {     case 2:         (void)AEFinderEventBuildAndSend(argv[1], kCoreEventClass,                                         kAEOpenDocuments);         break;     case 3:         (void)AEFinderEventBuildAndSend(argv[2], kAEMiscStandards,                                         kAEMakeObjectsVisible);         break;     default:         fprintf(stderr, "usage: %s [-r] <path>\n", argv[0]);         exit(1);         break;     }     exit(0); }

When run with only the pathname to a file or directory, the program will cause the Finder to open that file system objectsimilar to using the /usr/bin/open command. A file will be opened with the preferred application for that file type, whereas a directory will be opened by virtue of its contents being shown in a new Finder window. When the -r option is provided to the program, it will reveal the file system objectthat is, a file or directory will be shown in a Finder window with the corresponding icon selected. Note that a directory's contents will not be shown in this case.

$ gcc -Wall -o AEFinderEvents AEFinderEvents.c -framework Carbon $ ./AEFinderEvents -r /tmp/ ... $ echo hello > /tmp/file.txt $ ./AEFinderEvents /tmp/file.txt ...


The open command is built atop the AppKit framework's NSWorkspace class, which in turn uses the Launch Services framework.


9.15.3. Causing the System to Sleep by Sending an Apple Event

In this example, we will create and send a kAESleep Apple Event to the system process, causing the system to go to sleep. The loginwindow program is the system process, although we do not refer to the system process by name or process ID in our programwe use the special process serial number { 0, kSystemProcess } to specify the target of the Apple Event.

// sleeper.c #include <Carbon/Carbon.h> int main(void) {     OSStatus            osErr = noErr;     AEAddressDesc       target;     ProcessSerialNumber systemProcessPSN;     AppleEvent          eventToSend, eventToReceive;     // Initialize some data structures     eventToSend.descriptorType     = 0;     eventToSend.dataHandle         = NULL;     eventToReceive.descriptorType  = 0;     eventToReceive.dataHandle      = NULL;     systemProcessPSN.highLongOfPSN = 0;     systemProcessPSN.lowLongOfPSN  = kSystemProcess;     // Create a new descriptor record for target     osErr = AECreateDesc(typeProcessSerialNumber,// descriptor type                          &systemProcessPSN,        // data for new descriptor                          sizeof(systemProcessPSN), // length in bytes                          &target);                 // new descriptor returned     if (osErr != noErr) {         fprintf(stderr, "*** failed to create descriptor for target\n");         exit(osErr);     }     // Create a new Apple Event that we will send     osErr = AECreateAppleEvent(                 kCoreEventClass,       // class of Apple Event                 kAESleep,              // event ID                 &target,               // target for event                 kAutoGenerateReturnID, // use auto ID unique to current session                 kAnyTransactionID,     // we are not doing an event sequence                 &eventToSend);         // pointer for result     if (osErr != noErr) {         fprintf(stderr, "*** failed to create new Apple Event\n");         exit(osErr);     }     // Send the Apple Event     osErr = AESend(&eventToSend,                    &eventToReceive,   // reply                    kAENoReply,        // send mode                    kAENormalPriority, // send priority                    kAEDefaultTimeout, // timeout in ticks                    NULL,              // idle function pointer                    NULL);             // filter function pointer   if (osErr != noErr) {         fprintf(stderr, "*** failed to send Apple Event\n");         exit(osErr);     }     // Deallocate memory used by the descriptor     AEDisposeDesc(&eventToReceive);     exit(0); } $ gcc -Wall -o sleeper sleeper.c -framework Carbon $ ./sleeper # make sure you mean to do this!





Mac OS X Internals. A Systems Approach
Mac OS X Internals: A Systems Approach
ISBN: 0321278542
EAN: 2147483647
Year: 2006
Pages: 161
Authors: Amit Singh

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net