Reputation: 591
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??
Upvotes: 6
Views: 1061
Reputation: 51
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
Upvotes: 0
Reputation: 591
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 :)
Upvotes: 5
Reputation: 877
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.
Upvotes: 2