1

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!

cmt0220
  • 117
  • 6
  • 1
    Please post the code you tried (I mean, probably you have already added something to the example code behind the link) – kirjosieppo Jan 04 '22 at 21:30
  • @kirjosieppo thanks, i added an example. i'm completely new to objective-c, so apologies in advance if it's just completely incomprehensible :') – cmt0220 Jan 04 '22 at 21:43
  • Does this answer your question? [NSUserNotificationCenter defaultNotificationcenter returns nil](https://stackoverflow.com/questions/24460372/nsusernotificationcenter-defaultnotificationcenter-returns-nil) – Willeke Jan 04 '22 at 21:57
  • Not voting to close, but you should take a look at the topic @Petesh linked to below: https://stackoverflow.com/questions/11712535/mac-mountain-lion-send-notification-from-cli-app Specifically, you should embed an `Info.plist` file into your binary. – Itai Ferber Jan 04 '22 at 22:19

1 Answers1

2

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.

Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122
  • Instead of hacking around `NSBundle`, why not embed a proper `Info.plist` into the tool directly? (e.g. https://pcable.net/posts/2020-11-14-inetaccesspolicy/ — with `-ldflags='-extldflags "-sectcreate __TEXT __info_plist $(shell pwd)/Info.plist"'`) – Itai Ferber Jan 04 '22 at 22:15
  • Or better yet: do as the commend on the top answer in that linked question suggests, and set the `CREATE_INFOPLIST_SECTION_IN_BINARY` flag during compilation to automate this. – Itai Ferber Jan 04 '22 at 22:17
  • 1
    Cool - I'll update my answer - they're looking for a solution that doesn't use xcode, though so the CREATE_INFOPLIST… might not be ideal – Anya Shenanigans Jan 04 '22 at 22:37
  • Ah, good point. And the updated suggestion look excellent! (Though I would suggest calling out that you shouldn't _actually_ use `com.apple.finder` as a bundle identifier, but come up with your own.) +1 – Itai Ferber Jan 05 '22 at 00:28