Programmatically Capture Energy Saver Events on Mac

By | May 21, 2020

Mac OS X device may go to sleep by forced or idle events. The force event is related to some action performed by users, such as closing laptop lid or clicking on Sleep menu item. The idle event happens when users did not make any any actions with device for a specific period of time: did not press keyboard keys or move the mouse and so on. The idle event is triggered according to power management settings, which may be changed using the Energy Saver System Preferences or from terminal by pmset command. To detect when Mac device goes to sleep or wake up is necessary to register for Root Power Domain IO Service for the purpose of getting sleep and wake up notifications happening on the system. For this registration the IORegisterForSystemPower function is used. Below is the code of sleep_and_wakeup.cpp c++ terminal application to capture sleep and wake up events:


#include <stdio.h>
#include <time.h>
#include <memory.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/IOMessage.h>
io_connect_t rootPowerDomain; // a reference to the Root Power Domain IOService
char szBuffer[32];
char * getTime()
{
   memset(szBuffer,0,32);
   time_t ltime;
   time (&ltime);
   struct tm * loctime = localtime(&ltime);
   strftime(szBuffer, sizeof(szBuffer), "%H:%M:%S", loctime);
   return szBuffer;
}
void sleepWakeupCallBack( void * refParam, io_service_t service, natural_t messageType, void * messageArgument )
{
   printf("%s MessageType: 0x%08lx, Message Argument: 0x%08lx, Param Ref: 0x%08lx\n",
      getTime(),
      (long unsigned int)messageType,
      (long unsigned int)messageArgument,
      (long unsigned int)refParam);
   switch ( messageType )
   {
   case kIOMessageCanSystemSleep:
      printf("kIOMessageCanSystemSleep message.\n");
         /* Idle sleep is about to kick in. This message will not be sent for forced sleep.
         Applications have a chance to prevent sleep by calling IOCancelPowerChange.
         Most applications should not prevent idle sleep.
         Power Management waits up to 30 seconds for you to either allow or deny idle
         sleep. If you don’t acknowledge this power change by calling either
         IOAllowPowerChange or IOCancelPowerChange, the system will wait 30
         seconds then go to sleep.
         */
         //Uncomment to cancel idle sleep
         //IOCancelPowerChange( rootPowerDomain, (long)messageArgument );
         // Allow idle sleep
         IOAllowPowerChange( rootPowerDomain, (long)messageArgument );
         IOAllowPowerChange( rootPowerDomain, (long)messageArgument );
      break;
   case kIOMessageSystemWillSleep:
      printf("kIOMessageSystemWillSleep message.\n");
         /* The system WILL go to sleep. If you do not call IOAllowPowerChange or
         IOCancelPowerChange to acknowledge this message, sleep will be
         delayed by 30 seconds.
         NOTE: If you call IOCancelPowerChange to deny sleep it returns
         kIOReturnSuccess, however the system WILL still go to sleep.
         */
      IOAllowPowerChange( rootPowerDomain, (long)messageArgument );
      break;
   case kIOMessageSystemWillPowerOn:
      printf("kIOMessageSystemWillPowerOn message.\n");
      break;
   case kIOMessageSystemHasPoweredOn:
      //System has finished waking up…
      printf("kIOMessageSystemHasPoweredOn message.\n");
      break;
   default:
      printf("default case.\n");
      break;
   }
}
int main( int argc, char **argv )
{
   // notification port allocated by IORegisterForSystemPower
   IONotificationPortRef notifyPortRef;
   // notifier object, used to deregister later
   io_object_t notifierObj;
   // this parameter is passed to the callback
   void* refParam;
   // register to receive system sleep notifications
   rootPowerDomain = IORegisterForSystemPower( refParam, &notifyPortRef, sleepWakeupCallBack, &notifierObj );
   if ( rootPowerDomain == 0 )
   {
      printf("IORegisterForSystemPower failed\n");
      return -1;
   }
   printf("IORegisterForSystemPower OK! Root port: %d\n", rootPowerDomain);
   // add the notification port to the application runloop
   CFRunLoopAddSource( CFRunLoopGetCurrent(),
      IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes );
   /* Start the run loop to receive sleep notifications. Don’t call CFRunLoopRun if this code
   is running on the main thread of a Cocoa or Carbon application. Cocoa and Carbon
   manage the main thread’s run loop for you as part of their event handling
    mechanisms.
   */
   printf("%s Starting CFRunLoopRun\n", getTime());
   CFRunLoopRun();
   //Unreachable code below. CFRunLoopRun doesn’t return.
   return 0;
}

Compilation


root# g++ -g -o sleep_and_wakeup -framework IOKit -framework CoreFoundation sleep_and_wakeup.cpp

Now testing. Using “pmset -g” I check power management settings:


root# pmset -g
System-wide power settings:
DestroyFVKeyOnStandby 0
Currently in use:
lidwake              1
autopoweroff         1
standbydelayhigh     86400
autopoweroffdelay    28800
proximitywake        0
standby              1
standbydelaylow      10800
ttyskeepawake        1
Standby Battery Threshold 50
powernap             1
gpuswitch            2
hibernatefile        /var/vm/sleepimage
hibernatemode        3
displaysleep         3
sleep                3 (sleep prevented by Webex Productivity Tools)
tcpkeepalive         1
halfdim              1
acwake               0
disksleep            10

The system should go to sleep after 3 minutes, however it could sleep because sleep was prevented by Webex Productivity Tools application. Using Activity Monitor I killed Webex Productivity Tools, checked Power Management settings again. Currenty looks OK!


root# pmset -g
System-wide power settings:
DestroyFVKeyOnStandby 0
Currently in use:
lidwake              1
autopoweroff         1
standbydelayhigh     86400
autopoweroffdelay    28800
proximitywake        0
standby              1
standbydelaylow      10800
ttyskeepawake        1
Standby Battery Threshold 50
powernap             1
gpuswitch            2
hibernatefile        /var/vm/sleepimage
hibernatemode        3
displaysleep         3
sleep                3
tcpkeepalive         1
halfdim              1
acwake               0
disksleep            10

At 13:45 I started sleep_and_wakeup, after 3 minutes screen turned off, approximately 4 minutes later I checked captured power events and it is what I see:


root# ./sleep_and_wakeup
IORegisterForSystemPower OK! Root port: 4611
13:45:10 Starting CFRunLoopRun
13:48:25 MessageType: 0xe0000270, Message Argument: 0x0047005e, Param Ref: 0x00000000
kIOMessageCanSystemSleep message.
13:48:55 MessageType: 0xe0000280, Message Argument: 0x0048005d, Param Ref: 0x00000000
kIOMessageSystemWillSleep message.
13:52:51 MessageType: 0xe0000320, Message Argument: 0x00000000, Param Ref: 0x00000000
kIOMessageSystemWillPowerOn message.
13:52:52 MessageType: 0xe0000300, Message Argument: 0x00000000, Param Ref: 0x00000000
kIOMessageSystemHasPoweredOn message.


Leave a Reply

Your email address will not be published. Required fields are marked *