NSWorkspaceWillPowerOffNotification never called

933 views Asked by At

I am trying to run a program in a background process that will register every shutdown event in the system.

Doing so by registering to NSWorkspaceWillPowerOffNotification as show below:

#import <AppKit/AppKit.h>

@interface ShutDownHandler : NSObject <NSApplicationDelegate>

- (void)computerWillShutDownNotification:(NSNotification *)notification;

@end


int main(int argc, char* argv[]) {
    NSNotificationCenter *notCenter;

    notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];

    ShutDownHandler* sdh = [ShutDownHandler new];

    [NSApplication sharedApplication].delegate = sdh;

    [notCenter addObserver:sdh
                  selector:@selector(computerWillShutDownNotification:)
                      name:NSWorkspaceWillPowerOffNotification
                    object:nil];

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [[NSFileManager defaultManager] createFileAtPath:@"./output.txt" contents:nil attributes:nil];
    });

    [[NSRunLoop currentRunLoop] run];

    return 0;
}


@implementation ShutDownHandler

- (void)computerWillShutDownNotification:(NSNotification *)notification {
    NSFileHandle* file = [NSFileHandle fileHandleForUpdatingAtPath: @"./output.txt"];
    [file seekToEndOfFile];

    NSDateFormatter* fmt = [NSDateFormatter new];
    [fmt setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    NSDate* current = [NSDate date];

    NSString* dateStr = [fmt stringFromDate:current];
    [dateStr writeToFile:@"./output.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
    return NSTerminateCancel;
}

@end

For some reason that I cannot understand, the NSWorkspaceWillPowerOffNotification handler is never called!

Also added the following to my .plist:

<key>NSSupportsSuddenTermination</key>
<false/>

but still no notification is register when shutting down my system.

Any idea as to why??

3

There are 3 answers

1
YanivH On BEST ANSWER

For future reference:

I was not able to register to the above event and see it fire.

Finally, I went with a different approach: apple open-source power management

Here, you can see we have notifications names such as: "com.apple.system.loginwindow.logoutNoReturn"

You can register to those, and that will do the trick.

Hope this will help someone one day :)

4
Irad K On

When you say you've ran your code as a background process, do you mean that it's based on launchd daemon handled according to plist file in /Library/LaunchDeamon ? In this case, the notification will not be sent. Try running it under Cocoa application

Seems like this notification doesn't work on LaunchDaemon linked against AppKit.

I'm still looking for a way to get these notifications on background deamon process.

0
H. Katsura On

i can make NSWorkspaceWillPowerOffNotification and applicationShouldTerminate: to work by adding NSApplicationActivationPolicyAccessory and replacing the run loop with [NSApp run]. it still won't be very useful if you want to continue to monitor all logouts (and/or power off attempts) since it only works if the process is launched while the user has already logged in, and it can only detect the current UI login session's power-off/logout notification and "app should terminate" delegate.

"apps" that belong to the current UI login session get terminated by the OS when the user logs out. non-app (no [NSApp run]) processes will continue to run even after the user logs out.

so, if you wan to continue to monitor the user logouts (or power off attempts), you would need a non-app (no [NSApp run]) process.

NSWorkspaceSessionDidBecomeActiveNotification and NSWorkspaceSessionDidResignActiveNotification work without [NSApp run] if you want to monitor if the user switched out or back in.

or you can try the System Configuration APIs if it works for your use case.

"Technical Q&A QA1133: Determining console user login status" https://developer.apple.com/library/archive/qa/qa1133/_index.html

sounds like com.apple.system.loginwindow.logoutNoReturn was the best option for whatever you wanted to do. good to know!

here is the updated code (although it's not really useful):

// cc powreoff-notification.m -o poweroff-notification -framework AppKit
#import <AppKit/AppKit.h>

@interface ShutDownHandler : NSObject <NSApplicationDelegate>

- (void)computerWillShutDownNotification:(NSNotification *)notification;

@end

int main(int argc, char* argv[]) {
    @autoreleasepool {
        NSLog(@"%s", __func__);
        NSNotificationCenter* notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];

        ShutDownHandler* sdh = [ShutDownHandler new];

        [NSApplication sharedApplication].delegate = sdh;

        [notCenter addObserver:sdh
                      selector:@selector(computerWillShutDownNotification:)
                          name:NSWorkspaceWillPowerOffNotification
                        object:nil];

        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [[NSFileManager defaultManager] createFileAtPath:@"./output.txt" contents:nil attributes:nil];
        });

        // without NSApplicationActivationPolicyAccessory, the [NSApp run] process gets killed on
        // poweroff/logout instead of getting the power off notification or applicationShouldTerminate:.
        [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];

        // without [NSApp run], you won't get the power off notification or applicationShouldTerminate:.
        //[[NSRunLoop currentRunLoop] run];
        [NSApp run];

        // this process needs to be launched while the user has already logged in. otherwise,
        // you won't get the power off notification or applicationShouldTerminate:.
    }

    return 0;
}

@implementation ShutDownHandler

- (void)computerWillShutDownNotification:(NSNotification *)notification {
    NSLog(@"%s", __func__);
    NSFileHandle* file = [NSFileHandle fileHandleForUpdatingAtPath: @"./output.txt"];
    [file seekToEndOfFile];

    NSDateFormatter* fmt = [NSDateFormatter new];
    [fmt setDateFormat:@"yyyy-MM-dd HH:mm:ss\n"];
    NSDate* current = [NSDate date];

    NSString* dateStr = [fmt stringFromDate:current];
    [dateStr writeToFile:@"./output.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
    NSLog(@"%s", __func__);
    return NSTerminateCancel;
}

@end