A command-line tool which ingests its arguments and turns them in to Apple Events is the way to go. You can see how this works from the user's point of view by installing the BBEdit command-line tools and then running man bbedit
or man bbdiff
in a Terminal window.
From your command-line tool's point of view, the "interesting" parts are:
Figure out whether the application is running: +[NSRunningApplication runningApplicationsWithBundleIdentifier:]
will help with that.
If the application is not running, then use -[NSWorkspaceURLForApplicationWithBundleIdentifier:]
to first locate the application by bundle ID, then -[NSWorkspace launchApplicationAtURL:options:configuration:error:]
to launch the application. This will return an NSRunningApplication
instance, or NIL and an error. (Make sure to handle the error case.)
Using the NSRunningApplication
instance obtained from either step 1 or step 2, you can now use either the NSAppleEventDescriptor
APIs or the low-level AppleEvent C APIs to construct an event. (The higher-level API is probably easier to use.)
That would go something like this:
Construct a target descriptor using the processIdentifier
from your running application:
targetDesc = [NSAppleEventDescriptor descriptorWithProcessIdentifier: myRunningApplication.processIdentifier;
Construct an "open documents" event, addressed to your target application:
event = [NSAppleEventDescriptor appleEventWithEventClass: kCoreEventClass eventID: kAEOpenDocuments targetDescriptor: targetDesc returnID: kAutoGenerateReturnID transactionID: kAnyTransactionID];
Note: I use kCoreEventClass
/kAEOpenDocuments
as an example - if you're trying to open one or more files with additional information, that's fine. If you're doing some other work, then you should invent a four-character code for an event class which is specific to your application, and a four-character event ID which is unique to the operation you're requesting.
Add the command arguments to the event. For each argument, this consists of creating an appropriate descriptor based on the argument's intrinsic type (boolean, int, string, file URL), and then adding it to the event using a keyword parameter.
(An Apple Event "keyword" is a four-character code. You can invent your own, with constraints (don't use all-lowercase, and you can use ones defined in AEDataModel.h
or AERegistry.h
where they fit with your needs).
For each descriptor you create, add it to the event using -[setParamDescriptor: forKeyword:]
:
myURLParamDesc = [NSAppleEventDescriptor descriptorWithFileURL: myFileURL];
[event setParamDescriptor: myURLParamDesc forKey: kMyFileParamKeyword];
When you've added all of the parameters to the event, send it:
[event sendWithOptions: kAENoReply timeout: FLOAT_MAX error: &error];
On the application side, you'll need to use -[NSAppleEventManager setEventHandler: andSelector: forEventClass: andID:]
. This will get called for your custom event class and ID that you invented above, at which point you can use the descriptor APIs to pull the event apart and run your operation.
Sandboxing takes care of itself: your application automatically gets a sandboxing extension for files that it's been passed via Apple Events.
Your command-line tool is not sandboxed -- it can't be, because it's run from Terminal and (potentially) other nonsandboxed apps.
However, the tool must be signed with the hardened runtime, and with com.apple.security.automation.apple-events = YES
and a com.apple.security.temporary-exception.apple-events
naming your application's bundle identifier, so that the tool can send Apple Events to your application.
(And the tool will need an Info.plist with an NSAppleEventsUsageDescription
string.)
I've left a fair amount as an exercise for the reader; but hopefully this will get you started.