December 94 - Scripting the Finder From Your Application
Scripting the Finder From Your Application
Greg Anderson
The Finder has long been a black box to users and developers -- extending the Finder
or even examining its state has been nearly impossible. With System 7.5, Apple has
shipped a Finder that supports the Object Support Library; this Scriptable Finder
opens a new world to developers by allowing applications to interact with the Finder
through Apple events.
The System 7 Finder has always accepted a number of simple events that provide
services such as duplicating files, making aliases, and emptying the Trash. But the
System 7.0 and 7.1 Finder events are very limited and have strict requirements for
the order of parameters and for parameter data types. The Finder that shipped with
System 7.5 greatly expands the set of available events: it uses the Object Support
Library (OSL) to provide full compatibility with AppleScript, and it provides a new
set of events to do things such as examine the Finder's selection, change Finder
preferences, and modify file sharing settings.
The term Scriptable Finder refers to any Finder that's OSL compliant. In System 7.5,
this support is implemented by the Finder Scripting Extension in the Extensions
folder; however, future Finders will have scriptability built into their core code base.
Developers can count on the presence of the Scriptable Finder in all future versions of
system software.
The OSL and the Open Scripting Architecture are critical additions to the Macintosh
Toolbox. They mark the end of black-box applications and system software and pave the
way for configurable, component-based systems. A Scriptable Finder is only the first
step in providing a more unified, open system, but it's an important one.
This article shows you how to generate Finder events from your application. First
we'll look at event addressing and the Apple Event Manager, and then we'll see how to
specify Finder objects. Finally, the section "Making the Finder Do Tricks" provides a
taste of the power and flexibility of the Scriptable Finder, showing some practical uses
of this great new capability. On this issue's CD, you'll find the complete code for the
article's examples along with sample applications that show how to control the Finder
with Apple events. The header file FinderRegistry.h on the CD declares all of the event
message IDs, class IDs, and property IDs that the Finder defines.
CREATING AND ADDRESSING FINDER EVENTS
Every feature of the Scriptable Finder is accessible via AppleScript. For example, the
following script, if typed into the Script Editor and executed, would create a new folder
on the desktop:
tell application "Finder
make folder at desktop
end tell
An application doesn't need to compile and execute scripts, however, to use the features
of the Scriptable Finder; every command that a script can instruct the Finder to do has
a corresponding representation as an Apple event. An application that controls the
Finder may bypass AppleScript entirely and send Apple events to the Finder directly.
That's the technique we'll use in this article.
There are a number of ways to address an Apple event, but for sending an event to the
Finder on the local machine, the simplest and most straightforward technique is to
address the event by process serial number (PSN). To determine the Finder's PSN,
you walk the list of running processes and search for the Finder's file type and
creator, 'FNDR' and 'MACS'.
Listing 1 shows one way to generate an address targeted at the Finder on the local
machine. Notice that we've used TDescriptor, which is a C++ wrapper class that
corresponds to the Apple Event Manager type AEDesc. (See "C++ Wrappers" for an
explanation of wrappers used in this article.)
Listing 1. Getting the address of the Finder
TDescriptor GetAddressOfFinder()
ProcessSerialNumber psn;
ProcessInfoRec theProc;
TDescriptor finderAddressDescriptor;
// Initialize the process serial number to specify no process.
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = kNoProcess;
// Initialize the fields in the ProcessInfoRec, or we'll have memory
// hits in random locations.
theProc.processInfoLength = sizeof(ProcessInfoRec);
theProc.processName = nil;
theProc.processAppSpec = nil;
theProc.processLocation = nil;
// Loop through all processes, looking for the Finder.
while (true)
FailErr(GetNextProcess(&psn));
FailErr(GetProcessInformation(&psn, &theProc));
if ((theProc.processType == 'FNDR') &&
(theProc.processSignature == 'MACS'))
break;
}
finderAddressDescriptor.MakeProcessSerialNumber(psn);
return finderAddressDescriptor;
}
______________________________
C++ WRAPPERS
The sample code in this article makes extensive use of C++ wrappers. The file
AppleEventUtilities.h, included on this issue's CD, defines the wrapper classes
TDescriptor and TAEvent, which correspond to the Apple Event Manager types
AEDesc and AppleEvent, respectively. The class TDescriptor contains methods
for examining and extracting the contents of AEDesc, AEDescList, or AERecord
structures. TAEvent inherits from this class (since an Apple event really is an
AERecord) and adds methods for getting and setting attributes and addressing
and sending events.
The use of the C++ wrappers makes the code easier to read, but it would be a
simple matter to translate the code back into straight C or Pascal functions
that call the Apple Event Manager directly. If you do this, don't forget that the
C++ constructor of TDescriptor automatically initializes the fields of the
AEDesc to a null descriptor (descriptor type = typeNull, data handle = nil).
You must do this explicitly in your C or Pascal program, or you could cause
problems for the OSL. For example, CreateObjSpecifier will crash if its
second parameter is a pointer to an uninitialized AEDesc rather than a valid
object specifier or a null descriptor.
______________________________
Should the Finder not be running, looking for processes with the signature 'MACS' will
find other user interface shells, such as At Ease, and in some cases you might prefer
your application to do that. However, no shells other than the Finder currently
support the full Finder Event Suite, so the sample code provided here always requires
the process type to be 'FNDR'.
Earlier Finders were not only unaware of the OSL, but they also didn't use the Apple
Event Manager. That's right, the System 7.0 and 7.1 Finders never call
AEProcessAppleEvent -- they interpret and process high-level events in their own
special way, without ever informing the Apple Event Manager of what's going on. This
means that an application that sends any unrecognized high-level event to the System
7.0 or 7.1 Finder will never get a reply; the application will sit idle in AESend until
the event times out (assuming that the send mode was kAEWaitReply).
To determine whether the Finder on the local machine supports the Finder Event Suite,
an application can call Gestalt with the selector gestaltFinderAttr and check the
gestaltOSLCompliantFinder bit of the result. Before System 7 Pro, gestaltFinderAttr
didn't exist, so Gestalt will return the error gestaltUndefSelectorErr on some
machines.
Unfortunately, the only way to determine whether the Scriptable Finder is running on
a remote machine is to send it an event and wait for it to time out. The best event to
send is the Gestalt event from the Finder Event Suite (an event whose class is
kAEFinderSuite and whose ID is kAEGestalt) with a direct parameter whose type is
typeEnumeration and whose value is gestaltFinderAttr. If the Scriptable Finder is
running, the result will have the gestaltOSLCompliantFinder bit set. Under System 7
Pro, the Finder will return an error (event not handled) if the Scriptable Finder isn't
running, but the System 7.0 and 7.1 Finders will never return a result.
The Gestalt event can be used to ask for the value of any Gestalt selector. It's easier to
call Gestalt directly on the local machine (and more reliable, since the Scriptable
Finder might not be running), but some distributed computing applications may want
to examine the result of Gestalt selectors on remote machines to determine which are
suitable for use as remote hosts.
SPECIFYING FINDER OBJECTS
Most events operate on some Finder object, such as a file, a folder, or a window. These
objects are always specified with an Apple event descriptor (AEDesc) placed in the
direct object of the event. Some events require specification of more than one object;
for example, the Copy event requires parameters for both the objects to be copied and
the location to copy them to. In these cases, the direct object of the event is the object
being operated on, and other parameters are defined for any other object it requires.
The destination of the Copy event goes in the parameter keyAEDestination; other events
may define other keywords for parameters they use.
Most scriptable applications require object specification parameters to be in a very
specific format called an object specifier. The Finder is a little more flexible than that
-- it will accept a descriptor of type typeAlias (alias record) or typeFSS (FSSpec) in
any parameter that requires an object specifier. All the same, understanding object
specifiers is critical to sending events to the Finder, because many objects cannot be
represented by an alias record or an FSSpec, and therefore must be referenced by
object specifier.
Object specifiers are described in "Apple Event Objects and You
indevelop Issue 10 and in "Better Apple Event Coding Through Objects
indevelop Issue 12. See also Chapter 6, "Resolving and Creating Object
Specifier Records," in Inside Macintosh: Interapplication Communication.*
An object specifier is a descriptor whose type is typeObjectSpecifier, but it's actually
an Apple event record (AERecord), and can be accessed as such if coerced to type
typeAERecord. To build an object specifier, it's most convenient to use the routine
CreateObjSpecifier (MakeObjectSpecifier in AppleEventUtilities.cp), which takes four
parameters: the desired class of the specified object, the key form, the key data, and
the object container.
• The desired class indicates the kind of object. Some classes that the
Finder recognizes are disks, windows, and folders. The desired class may also
be set to typeWildCard to indicate any class of object.
• The key form specifies how the object is being addressed; the most
common choices are by name and by index.
• The key data contains the specification for the object in a format
compatible with the key form. For example, if the key form is formName, the
key data will contain the name of the object being specified.
• The object container is either another object specifier or a null
descriptor. Thus, object specifiers have a recursive definition that's always
terminated with a null descriptor.
The null descriptor in the object specifier's container is a reference to a special
container called the null container, which serves as the root container of every