8

I'm writing an application in C for the Mac (Leopard) that needs to do some work on receipt of power notifications, e.g. sleep, wake-up, shutdown, restart. It runs via launchd as a launchagent on login then begins monitoring for notifications. The code I'm using to do this is as follows:

/* ask for power notifications */
static void StartPowerNotification(void)
{
    static io_connect_t rootPort;   
    IONotificationPortRef notificationPort;
    io_object_t notifier;

    rootPort = IORegisterForSystemPower(&rootPort, &notificationPort, 
                                        PowerCallback, &notifier);
    if (!rootPort) 
        exit (1);

    CFRunLoopAddSource (CFRunLoopGetCurrent(),  
                        IONotificationPortGetRunLoopSource(notificationPort), 
                        kCFRunLoopDefaultMode);
}

/* perform actions on receipt of power notifications */
void PowerCallback (void *rootPort, io_service_t y, 
                    natural_t msgType, void *msgArgument)
{
    switch (msgType) 
    {
        case kIOMessageSystemWillSleep:
            /* perform sleep actions */
            break;

        case kIOMessageSystemHasPoweredOn:
            /* perform wakeup actions */
            break;

        case kIOMessageSystemWillRestart:
            /* perform restart actions */
            break;

        case kIOMessageSystemWillPowerOff:
            /* perform shutdown actions */
            break;
    }
}

However, only the top two for sleep and wake (kIOMessageSystemWillSleep and kIOMessageSystemHasPoweredOn) ever get called. I never get any notifcations for restart or shutdown (kIOMessageSystemWillRestart and kIOMessageSystemWillPowerOff).

Am I doing something wrong? Or is there another API that would give me the restart and shutdown notifications? I'd prefer to keep it as a C program (as thats what I'm familiar with) but am open to any sensible suggestions of alternatives (I've had a look at login/logout hooks but these seem to be deprecated in favour of launchd).

Thanks in advance for any help/tips!

binarybob
  • 3,529
  • 3
  • 23
  • 21

2 Answers2

6

I know you can register for the NSWorkspaceWillPowerOffNotification notification from NSWorkspace, which is not a C function but does work.

#import <AppKit/AppKit.h>
#import "WorkspaceResponder.h"

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSNotificationCenter *nc = [[NSWorkspace sharedWorkspace] notificationCenter];
    WorkspaceResponder *mainController = [[WorkspaceResponder alloc] init];

    //register for shutdown notications
    [nc addObserver:mainController
selector:@selector(computerWillShutDownNotification:)
          name:NSWorkspaceWillPowerOffNotification object:nil];
    [[NSRunLoop currentRunLoop] run];
    [pool release];
    return 0;
}

Then in WorkspaceResponder.m:

- (void) computerWillShutDownNotification:(NSNotification *)notification {
    NSLog(@"Received Shutdown Notification");
}
Rob Keniger
  • 45,830
  • 6
  • 101
  • 134
  • Thanks for that! Do you know if I need a window or dock icon in order to receive these notifications? – binarybob Aug 27 '09 at 19:06
  • Thanks again Rob, thats much appreciated. – binarybob Aug 28 '09 at 13:23
  • I tried the above solution with no luck. Running a Foundation tool as a LaunchDeamon, but I linked against AppKit just to try this. Doesn't seem shutdown is ever hit... is it not supported here? – bugfixr Nov 11 '11 at 21:49
  • The code in this answer looks correct but the notification will not be sent to a daemon process -- the code would work fine for an application. – ctpenrose Nov 30 '11 at 21:14
  • The original poster states his command-line tool *runs via launchd as a launchagent on login*. This means it is a user-space Launch Agent, not a Daemon, and it ***will*** receive notifications. If the tool were executed as a system-wide Launch Agent or Daemon then I would agree with you. – Rob Keniger Nov 30 '11 at 22:57
  • Hi, I know it's been a while.. but perhaps do you know if this method still works on modern macOS versions ? it seems like the following question represents a failed attempt to get those events https://stackoverflow.com/questions/46085718/nsworkspacewillpoweroffnotification-never-called/47583949#47583949 – Irad K Dec 02 '17 at 21:42
  • 1
    @IradK it will work if you run it as a `LaunchAgent`, not as a `LaunchDaemon`. `LaunchDaemon` processes are faceless and can't use AppKit. See my comment above. – Rob Keniger Dec 05 '17 at 23:52
  • @ctpenrose, It's indeed the case here. I just got a daemon that hold mount drive, and if I unmount it gracefully, the shutdown flow become much faster. I don't want to use agent instead, since this deamon is a singleton for the entire station (no per-user process).. perhaps you can advise me how to get this event anyway ? Thanks ! – Irad K Dec 06 '17 at 15:56
  • @IradK you can still run as a `LaunchAgent` system-wide. That's why there is a `/Library/LaunchAgents/` folder as well as the user-specific `~/Library/LaunchAgents/` folder. – Rob Keniger Dec 07 '17 at 23:33
  • @IradK and anyone else here, did you find out how to receive these notifications from a launch daemon? Am facing the same problem here. – c00000fd Nov 28 '22 at 07:34
3

Using IORegisterForSystemPower doesn't provide the events you seek. Quoting from function documentation :

/*! @function IORegisterForSystemPower

@abstract Connects the caller to the Root Power Domain IOService for the purpose of receiving sleep & wake notifications for the system.

Does not provide system shutdown and restart notifications.

Irad K
  • 867
  • 6
  • 20