NSWorkspaceWillPowerOffNotification never called
Asked Answered
J

3

6

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??

Jennettejenni answered 6/9, 2017 at 23:29 Comment(1)
Hi, perhaps you've manage to overcome this obstacle. I'm also having the same problem. if yes, can you post your solution here ? thanksWarmblooded
J
5

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

Jennettejenni answered 16/1, 2018 at 9:8 Comment(2)
I tried the mach-ports approach on a MacBook Air (M2 chip) on macOS v.13.0.1, and "com.apple.system.loginwindow.logoutNoReturn" did not result in any notifications for a restart. Do I need to use a different value for that?Bootstrap
@SatyamRaikar: dennisbabkin.com/blog/…Bootstrap
W
2

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.

Warmblooded answered 30/11, 2017 at 22:52 Comment(4)
I also checked it with a cocoa application, which did not work. This is very annoying this that goes around with MAC.. It seems Apple are really trying to block the ability to be aware of total shutdown.Jennettejenni
According to some apple formal documentation I've read, you can use apple events such as kAEQuitApplication in order to get notification before the applications quits.. did you try it ?Warmblooded
@IradK Did you find a solution to listen to power off event? I am also look for the same solution.Turkic
I am also facing same issue. In my Daemon process, It is able to receive NSWorkspaceWillSleepNotification In case of sleep. But not logout events. One obvious reason that Daemo continue running on logout. And able to receive NSWorkspaceWillPowerOffNotification too in Application (not background process).Jacquelinejacquelyn
T
0

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
Tanguy answered 30/9, 2021 at 19:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.