cmt0220
cmt0220

Reputation: 137

Throwing a MacOS notification from a minimal standalone Objective-C program?

I'm trying to implement push notifications on MacOS within a C codebase. Ideally, there'd just be one Objective-C file containing (1) a public C function I can call and (2) some Objective-C code I can use to throw a notification. This way, the source files can be compiled and linked seamlessly in the build process.

Toward this end, I've been trying to create a minimal example that can throw notifications with just a single .m file (not an entire XCode project), much like the one discussed in NSUserNotificationCenter not showing notifications. However, two problems:

  1. I still can't get the code to work despite trying the solutions in the aforementioned link. It compiles and runs but does not throw a notification.
  2. If possible, we'd like to switch to the new user notifications API. Not a big deal if this isn't possible for now, though.

Here's what I've tried so far:

#import <Foundation/Foundation.h>
#import <Foundation/NSUserNotification.h>

@interface AppDelegate : NSObject <NSUserNotificationCenterDelegate>
@end

@implementation AppDelegate

- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center 
                               shouldPresentNotification:(NSUserNotification *)notification {
  return YES;
}

- (void)throwNotification {
    NSUserNotification *userNotification = [[NSUserNotification alloc] init];
    userNotification.title = @"Some title";
    userNotification.informativeText = @"Some text";

    printf("trying to throw {%s %s}\n", [[userNotification title] UTF8String], [[userNotification informativeText] UTF8String]);

    [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
}

@end

int main (int argc, const char * argv[]) {
    AppDelegate *app = [[AppDelegate alloc] init];
    [app throwNotification];

    return 0;
}

This is compiled with cc -framework Foundation -o app main.m.

Any insight would be appreciated!

Upvotes: 2

Views: 921

Answers (1)

Anya Shenanigans
Anya Shenanigans

Reputation: 94654

The problem is that in order to display a notification, you need to have a proper bundle identifier.

We're going to take a bit of the code from here, where we wait for the notification to get displayed. We can embed an Info.plist file into the compiled binary, which will accomplish the same thing as the swizzling code in a file called notify.m:

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject<NSUserNotificationCenterDelegate>

@property (nonatomic, assign) BOOL keepRunning;

@end

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        NSApplication *app = [NSApplication sharedApplication];
        AppDelegate *appdel = [[AppDelegate alloc] init];
        app.delegate = appdel;
        NSUserNotificationCenter *nc = [NSUserNotificationCenter defaultUserNotificationCenter];
        nc.delegate = appdel;
        appdel.keepRunning = TRUE;
        NSUserNotification *userNotification = [[NSUserNotification alloc] init];
        userNotification.title = @"Some title";
        userNotification.informativeText = @"Some text";

        [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
        while (appdel.keepRunning) {
            [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
        }
    }

    return 0;
}

@implementation AppDelegate

- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
                               shouldPresentNotification:(NSUserNotification *)notification {
  return YES;
}

- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification
{
    self.keepRunning = NO;
}

@end

I construct an Info.plist file consisting of the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleIdentifier</key>
  <string>com.apple.finder</string>
</dict>
</plist>

Please use an appropriate bundle identifier, as com.apple.finder will cause all these notifications to appear to come from macOS Finder, which might be confusing to users. I then compile it using:

clang -o notify -framework Cocoa notify.m -Wl,-sectcreate,_\_TEXT,__info_plist,Info.plist

there's a \ in that build line to avoid the markdown parsing of the underscores, but it's not needed for the actual build command line.

Upvotes: 3

Related Questions