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 (<ime); struct tm * loctime = localtime(<ime); 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, ¬ifyPortRef, sleepWakeupCallBack, ¬ifierObj ); 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. |