9.15. Apple EventsMac 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.
AppleScript has a syntax similar to natural language. Consider the example shown in Figure 952. Figure 952. AppleScript program to speak the system version
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.
$ 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 AppleScriptThis 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
9.15.2. Building and Sending an Apple Event in a C ProgramIn 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
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 EventIn 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! |