System events (notifications) listening on Mac

Hi there! Please advice how I can subscribe to listening system events (Lock Screen/User log off/Device mounted/etc)? I try to use NSDistributedNotificationCenter to do it, but I can receive only own notifications =(

This is the my test Console application:

//
//  main.m
//  EventsListener
//

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include <iostream>

@interface MyClass : NSObject

-(void)subscribe;
-(void)handleNotification:(NSNotification*)notification;

@end

@implementation MyClass : NSObject

-(void)subscribe
{
    [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:nil object:nil];

    [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(handleNotification2:) name:nil object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification3:) name:nil object:nil];

    [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification4:) name:@"com.apple.screenIsLocked" object:nil];

    [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(handleNotification4:) name:@"com.apple.screenIsLocked" object:nil];
}

-(void)handleNotification:(NSNotification*)notification
{
    NSLog(@"handleNotification: %@", notification);
}

-(void)handleNotification2:(NSNotification*)notification
{
    NSLog(@"handleNotification2: %@", notification);
}

-(void)handleNotification3:(NSNotification*)notification
{
    NSLog(@"handleNotification3: %@", notification);
}

-(void)handleNotification4:(NSNotification*)notification
{
    NSLog(@"handleNotification3: %@", notification);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");

        MyClass* mc = [MyClass new];
        [mc subscribe];

        [[NSNotificationCenter defaultCenter] postNotificationName:@"test notification" object:nil];

        NSLog(@"Press enter to abort");
        getchar();
    }

    return 0;
}

What wrong I does? Thanks!

Accepted Reply

But when I supply nil as value of name I should receive all notifications

Right, but that does not give you free reign to use those notification keys as if they were API.

[In retrospect this particular feature of the notification API was a poor choice, in that it actively encourages such behaviour. In its defence, that API was designed a long time ago.]

We should be able to run some 'job' on this event. User will can choose which event should be a trigger.

Most people who ask this question are building a security product, in which case the path forward is clear:

  • Our long-term direction for such things is the Endpoint Security (ES) API.

  • That does not currently support log in or log out events and, if you’re working on an ES client and need those events, I encourage you to file an enhancement request for them.

  • Pending that, you can use the now-deprecated audit (OpenBSM) subsystem to learn about log it and log out events in real time using auditpipe (see its man page).

However, if you’re not building a security product then I can’t recommend this approach. The problem is that first point: The ES client API is intended to be used as security products and not appropriate for non-security products.

A better approach in your case would be to implement a launchd agent. The system will start this agent in the GUI context created by the user when they log in, and stop it when they log out. It can then connect to your daemon (using some sort of IPC mechanism, preferably XPC) to inform it of these state changes.

This approach has a bunch of other advantages:

  • The code runs with no special privileges.

  • Because it’s running in a GUI login context, it can call APIs that are only available in such contexts. For example, NSWorkspace has some really useful notifications, but NSWorkspace is part of AppKit and thus only available in GUI contexts.

  • If the daemon needs something done in a GUI login context — for example, if one of these jobs is ‘launch an app’ — it can command the agent to do that on its behalf.

IMPORTANT If you’re building a product like this it’s critical to understand how execution contexts work on the Mac. Technote 2083 Daemons and Agents has the best info on that subject.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Replies

When dealing with stringly-typed APIs, like the notification APIs, you have to understand that the only supported strings are those documented by Apple, either by way of a string constant in the headers or explicitly in the documentation. Observing some random string may work today, but it’s not something that we consider API and thus it may stop working in the future.

So, to create a program that will work in the long term, you must restrict yourself to documented notification names.

With regards the specific events you mentioned:

Lock Screen/User log off/Device mounted

the answer depends on the context. Are you creating a security product? If not, why do you need to know about these events?

Oh, and are you targeting the Mac App Store? Or independent distribution using Developer ID?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hello, Eskimo,

When dealing with stringly-typed APIs, like the notification APIs, you have to understand that the only supported strings are those documented by Apple, either by way of a string constant in the headers or explicitly in the documentation. Observing some random string may work today, but it’s not something that we consider API and thus it may stop working in the future.

So, to create a program that will work in the long term, you must restrict yourself to documented notification names.

Of course. But when I supply nil as value of name I should receive all notifications (by documentation), right?

the answer depends on the context. Are you creating a security product? If not, why do you need to know about these events?

Oh, and are you targeting the Mac App Store? Or independent distribution using Developer ID?

We should be able to run some 'job' on this event. User will can choose which event should be a trigger. We distribute our product by self and can't distribute it via AppStore because of we have a daemon. But it's not a problem for our customers.

I'm not sure that observing on user's log off or lock screen is very insecure thing.

So, can You help me please?

But when I supply nil as value of name I should receive all notifications

Right, but that does not give you free reign to use those notification keys as if they were API.

[In retrospect this particular feature of the notification API was a poor choice, in that it actively encourages such behaviour. In its defence, that API was designed a long time ago.]

We should be able to run some 'job' on this event. User will can choose which event should be a trigger.

Most people who ask this question are building a security product, in which case the path forward is clear:

  • Our long-term direction for such things is the Endpoint Security (ES) API.

  • That does not currently support log in or log out events and, if you’re working on an ES client and need those events, I encourage you to file an enhancement request for them.

  • Pending that, you can use the now-deprecated audit (OpenBSM) subsystem to learn about log it and log out events in real time using auditpipe (see its man page).

However, if you’re not building a security product then I can’t recommend this approach. The problem is that first point: The ES client API is intended to be used as security products and not appropriate for non-security products.

A better approach in your case would be to implement a launchd agent. The system will start this agent in the GUI context created by the user when they log in, and stop it when they log out. It can then connect to your daemon (using some sort of IPC mechanism, preferably XPC) to inform it of these state changes.

This approach has a bunch of other advantages:

  • The code runs with no special privileges.

  • Because it’s running in a GUI login context, it can call APIs that are only available in such contexts. For example, NSWorkspace has some really useful notifications, but NSWorkspace is part of AppKit and thus only available in GUI contexts.

  • If the daemon needs something done in a GUI login context — for example, if one of these jobs is ‘launch an app’ — it can command the agent to do that on its behalf.

IMPORTANT If you’re building a product like this it’s critical to understand how execution contexts work on the Mac. Technote 2083 Daemons and Agents has the best info on that subject.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"