Mac Mountain Lion send notification from CLI app
Asked Answered
R

5

13

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?

Renfrow answered 29/7, 2012 at 20:9 Comment(3)
The documentation specifically mentions "applications or helper applications" so perhaps it refuses requests from command-line programs. Also, see if using deliverNotification: is any different.Hectare
@KevinGrant Thanks for the suggestion, no different result. It is look liking you and omz are correct about needing a helper application, but I will hold out hope for a little longer.Renfrow
@KevinGrant on an interesting note, deliverNotification is the only way that I get this to work in an App bundle. Good call on that.Renfrow
G
20

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.

Gorki answered 5/2, 2013 at 1:6 Comment(2)
This is a freaking awesome hack! Replacing one method by the other and tricking the runtime into calling your own defined method. Well done sir!Dorchester
You don't have to do the method swizzling hack (anymore?). Simply create a Info.plist file for your command line app, link it in the build settings and set up some bundle identifier. Then set the CREATE_INFOPLIST_SECTION_IN_BINARY build option (you need this as the command line app is no bundle and therefore cannot include the Info.plist as a file). Now mainBundle should return the correct bundle identifier and notifications should be displayed.Cardiac
P
3

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).

Pyroxenite answered 29/7, 2012 at 20:23 Comment(3)
Unfortunately it looks like you do have to have an application bundle. The code works in an application, but not in a cli tool. Thanks.Renfrow
I would really like some documentation on the requirements. I haven't been able to find any on Apple's site. I have at least ruled out code signing and entitlements as either necessary or sufficient though by adding them to a command line app that still failed to post notification and by getting notifications out of an unsigned, un-entitled application.Arethaarethusa
@barnes53 Yeah, I'd like to see those requirements too. Experimentally we can determine that the CLI app cannot do it, but I'd like to know what Apple has in mind. Unfortunately, I doubt we'll see it any time soon.Renfrow
P
3

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.

Polanco answered 29/7, 2012 at 20:49 Comment(4)
Unfortunately this just causes it to hang indefinitelyRenfrow
Well, yes that call will not return. It was just as a test to test the hypothesis. Does the notification happen?Polanco
IIRC using the NSUserNotificationCenter deliverNotification: does require some time and exiting too quickly will prevent the notification from appearing, but using NSUserNotificationCenter scheduleNotification: requires no additional time and you can exit immediately after it returns.Arethaarethusa
This is what you want: [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];Ruffina
D
2

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

Dove answered 13/8, 2012 at 7:16 Comment(0)
R
1

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.

  1. 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.

  2. 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.)

  3. 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.

  4. 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.

Ruffina answered 16/4, 2016 at 10:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.