Evidently, there's a way to make my GUI application scriptable with AppleScript. So for instance, I could have a command line app (like run from a LaunchDaemon) that tells my GUI app to post a sidebar notification. Does anyone have a simple example to explain this? Everything I've seen on the Apple website is hard to understand.
I want to create an AppleScript message like:
tell "My App" to notify with title "Title" subtitle "subtitle" text "some text"
in my CLI app and then my GUI app wakes up, receives it, and processes it.
This is easier than I thought! Some things to note:
NSAppleScriptEnabled
and OSAScriptingDefinition
.NSScriptCommand
and its method performDefaultImplementation
, of which you can then access [self directParameter]
and [self evaluatedArguments]
.1. Open your OSX Cocoa application project of which you want to receive AppleScript events.
2. Add in a commands.sdef file with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
<dictionary xmlns:xi="http://www.w3.org/2003/XInclude">
<!-- commented out next line because it has a bug ".sdef warning for argument 'FileType' of command 'save' in suite 'Standard Suite': 'saveable file format' is not a valid type name" -->
<!-- <xi:include href="file:///System/Library/ScriptingDefinitions/CocoaStandard.sdef" xpointer="xpointer(/dictionary/suite)"/> -->
<suite name="Acceptable Commands" code="SVrb" description="">
<command name="notify user with title" code="SVrbDpCm" description="">
<cocoa class="myCommand"/>
<direct-parameter description="">
<type type="text"/>
</direct-parameter>
<parameter name="subtitle" code="arg2" type="text" optional="yes"
description="">
<cocoa key="subtitle"/>
</parameter>
<parameter name="text" code="arg3" type="text" optional="yes"
description="">
<cocoa key="text"/>
</parameter>
<!-- uncomment below if you want to return a result string -->
<!-- <result type="text" description=""/> -->
</command>
</suite>
</dictionary>
3. Add in a myCommand.mm file with the following content:
#import <Cocoa/Cocoa.h>
@interface myCommand : NSScriptCommand
@end
@implementation myCommand : NSScriptCommand
- (id)performDefaultImplementation {
NSString *sResult = @"";
NSString *sTitle = [self directParameter];
NSDictionary *asArgs = [self evaluatedArguments];
NSString *sSubTitle = [asArgs objectForKey:@"subtitle"];
NSString *sText = [asArgs objectForKey:@"text"];
sResult = [sResult stringByAppendingFormat:@"TITLE=%@ SUBTITLE=%@ TEXT=%@",sTitle,sSubTitle,sText];
NSUserNotification *n = [[NSUserNotification alloc] init];
n.title = sTitle;
if (![sSubTitle isEqualToString:@""]) {
n.subtitle = sSubTitle;
}
n.informativeText = sText;
[NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:n];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
return sResult;
}
@end
4. Ensure the AppKit.framework is a linked binary like you normally do.
5. Edit your Info.plist file and add two parameters. (Note, below are their raw key names.)
NSAppleScriptEnabled = YES
OSAScriptingDefinition = commands.sdef
6. Compile your project. Then, right click on the product under the yellow Products folder in XCode, and choose Show in Finder.
7. Click on that app icon you see in Finder and then press your left Option key down. Then, while holding that Option key down, go to your Finder's Edit menu and choose Copy Pathname. That puts the long pathname to your application (before you install it in /Applications) on your clipboard.
8. Just for testing this, open a terminal window and then type this, replacing the path below to the path to your Cocoa application.
osascript -e 'tell app "/crazy/long/path/Example.app" to notify user with title "My Title" subtitle "my sub" text "my text"'
9. You should see your application open up with a dock icon if it's not open already, and then in the upper right you'll see a notification appear in your sidebar notifications. It will also have the icon of your GUI application if you installed one already with it. Note that if you run that command multiple times, AppleScript is smart and doesn't load your application multiple times. Note also that if you click the sidebar notification, it will open your application screen (rather than iconifying it to the dock only).
• You can revise the code in steps 2 and 3 to do a different command and to act upon it differently.
• You can revise your sdef (see the commented line) to return a result back, should you ever want that. (Great for debugging!)
• Your sdef file can have separate commands going to separate classes. See Apple's example for how this is done.
• The sdef file has some strange syntax such as the "code" parameter. More info is available here.
• This AppleScript connection is extremely useful if you have an application that works in conjunction with a LaunchDaemon but the LaunchDaemon needs to send a sidebar notification. Sure, there's code to make that happen, and there's a trick even to make that LaunchDaemon icon show your application icon instead of a Terminal icon, but the one thing it can't do is make it such that when you click that sidebar notification it opens the GUI application. Instead, when one clicks that sidebar notification, it opens the LaunchDaemon, which is an undesired result. So, instead, you can use this AppleScript technique to have your LaunchDaemon wake up and notify its GUI application companion to send the notification. That way, when one clicks the sidebar notification, it opens the GUI application, not the LaunchDaemon.
• It's trivial to make another application send some AppleScript:
NSString *sScript = @"tell app \"/Applications/Calculator.app\" to activate";
NSAppleScript *oScript = [[NSAppleScript alloc] initWithSource:sScript];
NSDictionary *errorDict;
NSAppleEventDescriptor *result = [oScript executeAndReturnError:&errorDict];