How can I send a notification to the notification center from a command line app? My attemps so far compile and run, but don't succeed in notifying me.
Example
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
NSLog(@"Running notifications");
NSUserNotification *note = [[NSUserNotification alloc] init];
[note setTitle:@"Test"];
[note setInformativeText:@"Woot"];
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
[center scheduleNotification: note];
return 0;
}
I then compile like:
clang -framework cocoa /tmp/Notes.m
and I get
2012-07-29 16:08:35.642 a.out[2430:707] Running notifications
as output, but no notification :(
Is codesigning a factor in this?
I found that NSUserNotificationCenter
works when [[NSBundle mainBundle] bundleIdentifier]
returns the proper identifier. So, I wrote some swizzling code that you can find at https://github.com/norio-nomura/usernotification
It can send an NSUserNotification
without an Application Bundle.
While I haven't found any specific documentation on this, I assume that you need to be an application (bundle) to deliver notifications. Note that the Notification Center UI always shows the name and icon of the app from which a notification came. That wouldn't be possible with a command-line tool.
Code-signing doesn't seem to be required though.
Perhaps you could write a helper app that just delivers the notifications and just communicate with your helper app from your command-line tool (e.g. using NSDistributedNotificationCenter
).
I don't know for sure, but perhaps the scheduleNotification call is asynchronous and your app is exiting before anything gets a chance to happen.
Try adding:
[[NSRunLoop currentRunLoop] run];
to the end of main.
there's already a terminal notifier on github: https://github.com/julienXX/terminal-notifier
and README says:
It is currently packaged as an application bundle, because NSUserNotification does not
work from a ‘Foundation tool’. radar://11956694
This is what I had to do. Note the runloop like that has a very tiny delay. It's necessary in order to see that notification.
NSUserNotification *n = [[NSUserNotification alloc] init];
n.title = @"My Title";
n.subtitle = @"my subtitle";
n.informativeText = @"some informative text";
[NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:n];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
However, you may notice with this that it shows a terminal icon instead of a nice icon. To fix that, there's a poorly documented technique. Unfortunately with this technique, though, I don't know how to link it to my own custom icon -- only to an installed application icon.
Add an Info.plist file to your project. If your project is called acme, usually XCode creates a yellow folder in your project called acme and puts your files there. So, add an Info.plist there, and don't forget to capitalize that I in Info. BTW, don't worry about distributing that Info.plist file with your CLI app -- it gets bundled automatically into the binary itself.
Next, open the Info.plist and add a new item. It will prompt you for some default items. Choose Bundle Identifier. Now, on the value part of this key, set it to a valid bundle identifier for an installed application in the Applications folder. So for me, my LaunchDaemon was the command line app that needed to send notifications, and I had a GUI app that mated with it in the /Applications folder called (for sake here) as /Applications/Acme.app. So, inside the Acme.app's Info.plist file, I had my existing bundle identifier of com.acme.myapp. As well, that GUI application already had an icon on it. So, I set my value in my CLI app's Info.plist file bundle identifier to com.acme.myapp so that it would use that icon. Now, you could also set this to com.apple.finder if you wanted, and it will grab the Finder icon. (BTW, if you know how to bundle an icon with the CLI app itself, rather than borrowing it from another installed application, then please let me know.)
Now, go into your Build Settings and use the Search box and type "infoplist". You'll see an item called "Info.plist File". Set it to the relative folder path of your file. So, remember when I mentioned that my project had a yellow "acme" folder and my Info.plist file was inside that? So, all I had to do was type "acme/Info.plist" under the second column (the left one said "Resolved") and the third column.
Now, while in Build Settings, search on "create info". You'll see an entry "Create Info.plist Section in Binary". In the second and third columns, set it to Yes.
Another problem with this issue is that when you click the alert, it launches the command line app instead of a GUI application that you might want to load.