Blog Post

Tips For macOS Programming - Part 2

Shutdown/reboot/logoff notifications, sleep/wake notifications, sending macOS to sleep or setting a wake event, rebooting or shutting down.

Tips For macOS Programming - Part 2 - Shutdown/reboot/logoff notifications, sleep/wake notifications, sending macOS to sleep or setting a wake event, rebooting or shutting down.

Intro

Continuing on from where we left off in part 1, let's review a few more options of writing launch daemons, or how we can deal with power operations in macOS.

The full source code for the code samples that I used below could be found in my GitHub repo.

Contents

This post needs its own table of contents:

Shutdown/Reboot/Logoff Notifications

While writing a regular GUI or a console app, you may never need to know when the macOS had initiated, or about to initiate a shutdown, reboot, or when an interactive user is logging off. But if you are writing a launch daemon, or a service that runs in the background, you may need to receive such notifications so that you can start saving state, or perform some other actions.

Unfortunately such notifications are not well documented by Apple and it took me a while to figure out how to receive them. And if you ask someone else, the go-to response you get on the popular forums is the smarmy, "Why do you need to know this?"

Well, this website is not your popular toxic programming forum, and I can't assume why you need to know this. Thus, let me show you how to get these notifications.

To figure out how Apple does it in their power management components, I had to dig through the source code. Because of that, a usual warning applies to this section.
The following contains functions and features that are not documented by the original manufacturer. By following it, you're doing so at your own risk. The methods presented below may rely on the internal implementation and may not work in the future.

I was able to come up with the following class that encapsulates all the work:

C++[Copy]
#include <CoreFoundation/CoreFoundation.h>
#include <notify.h>
#include <mach/mach_port.h>

struct Notif_RebootShutdown
{
	Notif_RebootShutdown()
	{
	}
	
	~Notif_RebootShutdown()
	{
		//Remove callback from a destructor
		if(!remove_Notifications(false))
		{
			//Should not fail here!
			assert(false);
		}
	}

	///Set the callback to receive reboot, shutdown & logoff notifications (cannot be called repeatedly)
	///'pPortName' = name of the mach port for the notification:
	///           - "com.apple.system.loginwindow.shutdownInitiated" for when shut-down warning is shown, may be aborted
	///           - "com.apple.system.loginwindow.restartinitiated" for when restart warning is shown, may be aborted
	///           - "com.apple.system.loginwindow.logoutInitiated" for when log-out warning is shown, may be aborted
	///           - "com.apple.system.loginwindow.logoutcancelled" previous shutdown, restart, logout was aborted
	///           - "com.apple.system.loginwindow.logoutNoReturn" previous shutdown, restart, logout is proceeding, can't abort
	///'pfn' = callback function that is called when event happens, or null not to call it
	///'pParam1' = passed directly into 'pfn' when it's called
	///'pParam2' = passed directly into 'pfn' when it's called
	///RETURN:
	///     - true if success
	bool init_Notifications(const char* pPortName,
	                        void (*pfn)(mach_msg_header_t* pHeader,
	                                    const char* pPortName,
	                                    const void* pParam1,
	                                    const void* pParam2) = nullptr,
	                        const void* pParam1 = nullptr,
	                        const void* pParam2 = nullptr)
	{
		bool bRes = false;
		
		//We must have a port name
		if(pPortName &&
			pPortName[0])
		{
			//Act from within a lock
			WRITER_LOCK wrl(_lock);
			
			if(!_bCallbackSet)
			{
				//Remember parameters
				_pfnCallback = pfn;
				_pParam1 = pParam1;
				_pParam2 = pParam2;
				
				//Register for notifications
				uint32_t nResShtdn = notify_register_mach_port(pPortName,
				                                               &_shutDownMachPort,
				                                               (_shutDownMachPort == MACH_PORT_NULL) ? 0 : NOTIFY_REUSE,
				                                               &_nShutdownNtf);
				
				if(nResShtdn == NOTIFY_STATUS_OK)
				{
					//Add to run-loop notifications
					CFMachPortContext ctx = {};
					ctx.info = this;
					
					_strPortName = pPortName;
					
					Boolean bShouldFree = false;
					
					_shutDownMachPortRef = CFMachPortCreateWithPort(kCFAllocatorDefault,
																	_shutDownMachPort,
																	_onCallback,
																	&ctx,
																	&bShouldFree);
					if(_shutDownMachPortRef)
					{
						_shutdownRunLoopRef = CFMachPortCreateRunLoopSource(nullptr, _shutDownMachPortRef, 0);
						if(_shutdownRunLoopRef)
						{
							//Add to the run loop
							CFRunLoopAddSource(CFRunLoopGetMain(),
												_shutdownRunLoopRef,
												kCFRunLoopDefaultMode);
							
							//Set flag that we set it
							_bCallbackSet = true;
							
							//Done
							bRes = true;
						}
						else
						{
							//Failed
							assert(false);
						}
					}
					else
					{
						//Failed
						assert(false);
					}
					
					//We are creating the object here - thus 'bShouldFree' should never be true
					assert(!bShouldFree);
					
					if(!bRes)
					{
						//Clear port name if we failed
						_strPortName.clear();
					}
				}
				else
				{
					//Failed
					assert(false);
				}
			}
			else
			{
				//Can't init again
				assert(false);
			}
		}
		else
		{
			//No port name
			assert(false);
		}

		return bRes;
	}

	///Checks if the callback to receive notifications was set
	///RETURN:
	///     - true if yes
	bool is_ReceivingNotifications()
	{
		//Act from within a lock
		READER_LOCK rdl(_lock);
		
		return _bCallbackSet;
	}
	
	
	///RETURN:
	///     = Port name that is currently used for this class,
	///     = "" if it was not initialized
	const char* getPortName()
	{
		//Act from within a lock
		READER_LOCK rdl(_lock);
		
		return _strPortName.c_str();
	}
		
	///Remove the callback that was set by init_Notifications()
	///INFO: It does nothing if the callback wasn't set before.
	///'bRebooting' = true if we're calling it when the OS is rebooting
	///RETURN:
	///     - true if no errors
	bool remove_Notifications(bool bRebooting)
	{
		bool bRes = true;
		
		//Act from within a lock
		WRITER_LOCK wrl(_lock);
		
		if(_bCallbackSet)
		{
			//Unregister from receiving notifications
			if(_nShutdownNtf)
			{
				uint32_t resCancel = notify_cancel(_nShutdownNtf);
				if(resCancel != NOTIFY_STATUS_OK)
				{
					if(bRebooting &&
						resCancel == NOTIFY_STATUS_SERVER_NOT_FOUND)
					{
						//Not an error
					}
					else
					{
						//Error
						assert(false);
						
						bRes = false;
					}
				}
				
				_nShutdownNtf = 0;
			}
			
			if(_shutdownRunLoopRef)
			{
				CFRunLoopRemoveSource(CFRunLoopGetMain(),
				                      _shutdownRunLoopRef,
				                      kCFRunLoopDefaultMode);
				
				CFRelease(_shutdownRunLoopRef);
				_shutdownRunLoopRef = nullptr;
			}
			
			if(_shutDownMachPortRef)
			{
				CFRelease(_shutDownMachPortRef);
				_shutDownMachPortRef = nullptr;
			}
			
			
			//Reset parameters
			_bCallbackSet = false;
			
			_strPortName.clear();
			
			_shutDownMachPort = MACH_PORT_NULL;
			
			_pfnCallback = nullptr;
			_pParam1 = nullptr;
			_pParam2 = nullptr;
		}
		
		return bRes;
	}

	
private:
	
	static void _onCallback(CFMachPortRef port,
	                        void *msg,
	                        CFIndex size,
	                        void *info)
	{
		Notif_RebootShutdown* pThis = (Notif_RebootShutdown*)info;
		assert(pThis);
		
		//Act from within a lock
		READER_LOCK rdl(pThis->_lock);
		
		if(pThis->_pfnCallback)
		{
			//Invoke the callback
			mach_msg_header_t *header = (mach_msg_header_t *)msg;
			
			pThis->_pfnCallback(header,
								pThis->_strPortName.c_str(),
								pThis->_pParam1,
								pThis->_pParam2);
		}

	}
	
	
private:
	///Copy constructor and assignments are NOT available!
	Notif_RebootShutdown(const Notif_RebootShutdown& s) = delete;
	Notif_RebootShutdown& operator = (const Notif_RebootShutdown& s) = delete;
	
private:
	
	RDR_WRTR _lock;                     //Lock for accessing this struct

	bool _bCallbackSet = false;         //true if we set callback OK

	std::string _strPortName;           //Port name for Mach port notifications

	int _nShutdownNtf = 0;
	mach_port_t _shutDownMachPort = MACH_PORT_NULL;
	CFMachPortRef _shutDownMachPortRef = nullptr;
	CFRunLoopSourceRef _shutdownRunLoopRef = nullptr;

	
	void (*_pfnCallback)(mach_msg_header_t* pHeader,
							const char* pPortName,
							const void* pParam1,
							const void* pParam2) = nullptr;
	const void* _pParam1 = nullptr;
	const void* _pParam2 = nullptr;
};
To build this class in Xcode you will need to include the CoreFoundation.framework in the project settings.

There's a lot of code there. So let me explain it a little bit.

First off, you probably noticed that I'm using my RDR_WRTR struct for synchronization. This is a class that implements a reader/writer lock. (I described it in detail in part 1.) That class is also wrapped in the WRITER_LOCK/READER_LOCK RAII constructs, that I also showed in part 1 of this post. The main idea behind using the _lock is to ensure that my Notif_RebootShutdown class supports multithreading.

Otherwise, the main work for setting up notifications is done by the Notif_RebootShutdown::init_Notifications function, that internally calls notify_register_mach_port, CFMachPortCreateWithPort and CFMachPortCreateRunLoopSource system functions, that set up the run-loop for the main thread to receive our needed notifications.

Note that you will need to set up the main run-loop in your process (like I showed in my main function on GitHub) for the Notif_RebootShutdown class to broadcast its notifications.

The interesting parameter that Notif_RebootShutdown::init_Notifications accepts is the pPortName. It should specify the exact port-name constant that the power management component expects. The following string constants seem to be used:

C[Copy]
//User clicked shutdown to show the UI. (It may be aborted later.)
#define kLWShutdownInitiated "com.apple.system.loginwindow.shutdownInitiated"

//User clicked restart to show the UI. (It may be aborted later.)
#define kLWRestartInitiated  "com.apple.system.loginwindow.restartinitiated"

//User clicked `logout user` to show the UI. (It may be aborted later.)
#define kLWLogoutInitiated   "com.apple.system.loginwindow.logoutInitiated"

//A previously shown UI for shutdown, restart, or logout has been cancelled.
#define kLWLogoutCancelled   "com.apple.system.loginwindow.logoutcancelled"

//A previously shown shutdown, restart, or logout was initiated and can no longer be cancelled!
#define kLWPointOfNoReturn   "com.apple.system.loginwindow.logoutNoReturn"

The notifications with the kLW*Initiated constants will be dispatched when a user clicks the Apple logo in the top-left corner of the screen and selects either: Restart, Shut Down, or Log Out user, which may show the following UI:

Power action UI

If the user later cancels this UI, the OS will dispatch the kLWLogoutCancelled notification. While the kLWPointOfNoReturn notification will be dispatched if the user confirms the action. It will serve as a point-of-no-return from performing it. If your app receives it, it needs to proceed with a quick run-down as it won't have much time left until its process is killed. (This doesn't apply to launch daemons and the kLWLogoutInitiated notification.)

Additionally, note that the Notif_RebootShutdown class uses the CFRunLoopGetMain function instead of a more traditional CFRunLoopGetCurrent. This is done because the class assumes that all notifications will be processed by the run-loop on the main UI thread, instead of whatever thread may be calling methods of the Notif_RebootShutdown class. This is a subtle detail that may be crucial for the correct functioning of the notification class.

I should admit that the kLWPointOfNoReturn notification seems kinda weird, because it doesn't tell you which operation was initiated. To know which action has been initiated, you need to receive all other notifications, store them somewhere in a persistent variable and then use that info when you receive the kLWPointOfNoReturn notification. I showed this in my callback_RebootShutdownLogout function in the GitHub repo example.

Finally, each notification must be removed with a call to Notif_RebootShutdown::remove_Notifications.

To see an example how one can use these notifications, check my GitHub repo.

Sleep/Wake Notifications

While on the subject of power notifications, how about a notification when a macOS enters sleep or wakes up from it?

One would think that there will be a similar set of functions like I showed above for this task. But that is not the case. For reasons that are probably lost in time, the approach is somewhat different.

Unlike the reboot/shutdown notifications, the sleep/wake notifications are somewhat documented. The recommended way to get them is with the IORegisterForSystemPower function. That API is not the most straightforward to use, so I wrapped it in a thread-safe class as well:

C++[Copy]
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/IOMessage.h>

struct Notif_SleepWake
{
    Notif_SleepWake()
    {
    }

    ~Notif_SleepWake()
    {
        //Remove callback from a destructor
        if(!remove_SleepWakeNotifications())
        {
            //Should not fail here!
            assert(false);
        }
    }
    
    ///Set the callback to receive sleep/wake notifications (cannot be called repeatedly)
    ///'pfn' = callback function that is called when event happens, or null not to call it
    ///'pParam1' = passed directly into 'pfn' when it's called
    ///'pParam2' = passed directly into 'pfn' when it's called
    ///RETURN:
    ///     - true if success
    bool init_SleepWakeNotifications(void (*pfn)(natural_t msgType,
                                                 void *msgArgument,
                                                 io_connect_t portSleepWake,
                                                 const void* pParam1,
                                                 const void* pParam2) = nullptr,
                                     const void* pParam1 = nullptr,
                                     const void* pParam2 = nullptr)
    {
        bool bRes = false;
        
        //Act from within a lock
        WRITER_LOCK wrl(_lock);
        
        if(!_bCallbackSet)
        {
            //Remember parameters
            _pfnCallback = pfn;
            _pParam1 = pParam1;
            _pParam2 = pParam2;
            
            //Register for sleep/wake notifications
            _pwrSleepWakeKernelPort = IORegisterForSystemPower(this,
                                                               &_pwrSleepWakeNtfPort,
                                                               _pwrSleepWakeCallback,
                                                               &_pwrSleepWakeNotifier);
            
            if(_pwrSleepWakeKernelPort != MACH_PORT_NULL)
            {
                //Add it to the run-loop
                CFRunLoopAddSource(CFRunLoopGetMain(),
                                   IONotificationPortGetRunLoopSource(_pwrSleepWakeNtfPort),
                                   kCFRunLoopDefaultMode);
                
                //Set flag that we set it
                _bCallbackSet = true;
                
                //Done
                bRes = true;
            }
            else
            {
                //Failed
                assert(false);
            }
        }
        else
        {
            //Can't init again
            assert(false);
        }
        
        return bRes;
    }
    
    ///Checks if the callback to receive wake/sleep notifications was set
    ///RETURN:
    ///     - true if yes
    bool is_ReceivingSleepWakeNotifications()
    {
        //Act from within a lock
        READER_LOCK rdl(_lock);
        
        return _bCallbackSet;
    }
    
    ///Remove the callback that was set by init_SleepWakeNotifications()
    ///INFO: It does nothing if the callback wasn't set before.
    ///RETURN:
    ///     - true if no errors
    bool remove_SleepWakeNotifications()
    {
        bool bRes = true;
        
        //Act from within a lock
        WRITER_LOCK wrl(_lock);
        
        if(_bCallbackSet)
        {
            //Unregister sleep/wake notifications
            if(_pwrSleepWakeNtfPort != nullptr)
            {
                //Remove the sleep notification port from the application runloop
                CFRunLoopRemoveSource(CFRunLoopGetMain(),
                                      IONotificationPortGetRunLoopSource(_pwrSleepWakeNtfPort),
                                      kCFRunLoopDefaultMode);
                
                //Deregister from system sleep notifications
                IOReturn ioRes = IODeregisterForSystemPower(&_pwrSleepWakeNotifier);
                if(ioRes != kIOReturnSuccess)
                {
                    //Error
                    assert(false);
                    bRes = false;
                }
                
                //IORegisterForSystemPower implicitly opens the Root Power Domain,
                //so we need to close it here
                kern_return_t resKern = IOServiceClose(_pwrSleepWakeKernelPort);
                if(resKern != KERN_SUCCESS)
                {
                    //Error
                    assert(false);
                    bRes = false;
                }
                
                //Destroy the notification port allocated by IORegisterForSystemPower
                IONotificationPortDestroy(_pwrSleepWakeNtfPort);
            }
            else
            {
                //Should not be here
                assert(false);
            }
            
            
            //Reset parameters
            _bCallbackSet = false;
            
            _pwrSleepWakeKernelPort = {};
            _pwrSleepWakeNtfPort = nullptr;
            _pwrSleepWakeNotifier = {};
            
            _pfnCallback = nullptr;
            _pParam1 = nullptr;
            _pParam2 = nullptr;
            
        }
        
        return bRes;
    }

    
private:
    static void _pwrSleepWakeCallback(void* pContext,
                                       io_service_t svc,
                                       natural_t msgType,
                                       void *msgArgument)
    {
        Notif_SleepWake* pThis = (Notif_SleepWake*)pContext;
        assert(pThis);
        
        //Act from within a lock
        READER_LOCK rdl(pThis->_lock);
        
        if(pThis->_pfnCallback)
        {
            //Invoke the callback
            pThis->_pfnCallback(msgType,
                                msgArgument,
                                pThis->_pwrSleepWakeKernelPort,
                                pThis->_pParam1,
                                pThis->_pParam2);
        }
    }

    
private:
    ///Copy constructor and assignments are NOT available!
    Notif_SleepWake(const Notif_SleepWake& s) = delete;
    Notif_SleepWake& operator = (const Notif_SleepWake& s) = delete;
    
private:
    
    RDR_WRTR _lock;                     //Lock for accessing this struct

    bool _bCallbackSet = false;         //true if set callback OK

    io_connect_t            _pwrSleepWakeKernelPort = {};
    IONotificationPortRef   _pwrSleepWakeNtfPort = nullptr;
    io_object_t             _pwrSleepWakeNotifier = {};
    
    void (*_pfnCallback)(natural_t msgType,
                         void *msgArgument,
                         io_connect_t portSleepWake,
                         const void* pParam1,
                         const void* pParam2) = nullptr;
    const void* _pParam1 = nullptr;
    const void* _pParam2 = nullptr;
};

Again, there's a lot to unpack here.

First, notice that I'm again using my RDR_WRTR reader/writer lock with the READER_LOCK/WRITER_LOCK RAII constructs to provide thread safety for my Notif_SleepWake class. This ensures that its methods can be called from any thread in a process.

Then as before, to set up a callback to receive the sleep/wake notifications, you'd call Notif_SleepWake::init_SleepWakeNotifications during the process initialization, and then call Notif_SleepWake::remove_SleepWakeNotifications to unregister it and to release system resources.

Note that you will need to set up the main run-loop in your process (like I showed in my main function on GitHub) for the Notif_SleepWake class to broadcast its notifications.

The interesting processing happens in the sleep/wake callback itself. It receives the natural_t msgType argument as its first parameter, that denotes the type of a notification. There are several options available there:

A much better way to understand the sequence of these notifications is to see them in action if you build and run my test Xcode project on GitHub. Refer to the callback_SleepWake function for details.
  • kIOMessageCanSystemSleep - it is broadcast when the macOS is preparing to enter idle sleep. (This usually happens because of a user inactivity for a certain period of time, that one can specify in the macOS settings.) This notification lets running programs veto the idle sleep if they need to.

    In a kinda counterintuitive way, a process that receives this notification must always reply with a call to either IOAllowPowerChange or IOCancelPowerChange. You can't just do-nothing in this case.

    I wonder what would happen if you just ignore to call either of these APIs? I will let the reader to test it out. But don't blame me if it creates a ripple in the space-time continuum of Steve Jobs' reality distortion field. (In all seriousness though, it may create a memory leak because of a bad design of this callback. If you find it out, please leave a comment below.)
    Note that there is no guaranteed way to prevent all types of sleep in macOS from the user-land. You need to write a kernel extension to fully control it.

    On top of that, there are multiple reasons why macOS may be entering sleep. Some of these ways may be controlled from user-land, some can't. To name just a few:

    • User selects "Sleep" from the menu when they click on the Apple logo in the top-left corner of the screen.
    • Another running process invokes sleep programmatically.
    • User closes the lid on a MacBook laptop.
    • A laptop battery is dangerously low.
    • User removes external monitor when the MacBook was in the Clamshell mode. (Or when user connected an external keyboard, mouse and a monitor to the MacBook and closed the lid to work with the external periphery.)
    • User changes the power source from AC to DC with the lid closed.

    If you absolutely need to keep macOS awake from sleep, you may use wake events, as I describe below.

  • kIOMessageSystemWillNotSleep - it is broadcast if one of the running processes aborts, or vetoes the idle sleep, as I described above for the kIOMessageCanSystemSleep notification.
  • kIOMessageSystemWillSleep - it is broadcast when it is known that the macOS will be entering sleep. It is a so-called point-of-no-return moment when sleep cannot be aborted.

    This is the moment when your process needs to do its quick preparations before entering sleep.

    Also, in the same vein, this notification assumes that you will call IOAllowPowerChange to acknowledge it. I would call this API last after you finish all processing in preparation for sleep.

  • kIOMessageSystemWillPowerOn - it is broadcast early in the sequence of waking up from sleep. At this stage macOS has not yet fully woken up itself, where some network drivers and external devices may not be fully re-connected.

    I would not be doing any serious processing here at this early stage, unless you are one of those external devices that needs to wake up.

  • kIOMessageSystemHasPoweredOn - it is broadcast when the macOS has fully woken up from sleep. Note that there could be a few second delay between kIOMessageSystemWillPowerOn and this notification.

    This is the moment when most software should be restoring its state from sleep.

Note, that although the following notifications are present in the source code, they are most certainly a legacy of the old-times. They are no longer delivered to the callback and are useless: kIOMessageCanDevicePowerOff, kIOMessageDeviceWillNotPowerOff, kIOMessageCanSystemPowerOff, kIOMessageDeviceWillPowerOn, kIOMessageDeviceHasPoweredOff.

If you need to receive reboot/shutdown notifications follow my advice above.

Lastly, to build the Notif_SleepWake class in Xcode you will need to include the CoreFoundation.framework and IOKit.framework in the project settings.

You can get a complete source code for my Notif_SleepWake class and how to use it in my GitHub repo.

How To Wake macOS From Sleep

And since we started talking about sleep/wake notifications, is there a way to wake macOS from sleep programmatically?

Some time ago I've written a small tool that can do it for Windows. And it turns out that one can do it for macOS as well.

To make long story short, you can use IOPMSchedulePowerEvent function to schedule a wake event. It's a pretty versatile function that can do way more than what is available on Windows.

To showcase its might for waking macOS at a specific time, I wrapped it up in a thread-safe class:

C++[Copy]
#include <CoreFoundation/CoreFoundation.h>
#include <string>
#include <vector>

struct WakeTimer
{
    ///'pstrTimerBundleID' = string with unique bundle identifier for this timer. ex: "com.dennisbabkin.wake01"
    ///                      INFO: This ID must remain the same between different instances of this app,
    ///                            as it will be saved in the global scope on disk by the macOS
    WakeTimer(const char* pstrTimerBundleID)
    {
        assert(pstrTimerBundleID && pstrTimerBundleID[0]);       //Must be provided
        
        if(pstrTimerBundleID)
        {
            _strTmrBundleID = pstrTimerBundleID;
        }
        
    }
    
    ~WakeTimer()
    {
        //Stop wake event
        stopWakeEvent();
    }
    
#define WAKE_TIMER_EVENT_TYPE kIOPMAutoWake
    
    ///Set wake event as the relative time from the current moment
    ///WARNING: When wake timer fires it will also temporarily wake the screen for a few seconds!
    ///'msFromNow' = number of ms from "now" to wake the system
    ///'pdtOutWhen' = if not 0, receives UTC date/time when the wake event was set for. Or receives 0 if didn't set it.
    ///RETURN:
    ///     = true if set OK
    bool setWakeEventRelative(UInt32 msFromNow,
                              CFAbsoluteTime* pdtOutWhen = nullptr)
    {
        bool bRes = false;
        
        //Time when to fire the wake event
        CFAbsoluteTime dtWhen = CFAbsoluteTimeGetCurrent() + ((CFTimeInterval)msFromNow) / 1000.0;
        
        if(true)
        {
            //Act from within a lock
            WRITER_LOCK wrl(_lock);
         
            bRes = _setWakeEvent(dtWhen,
                                 WAKE_TIMER_EVENT_TYPE,
                                 pdtOutWhen);
        }
        
        return bRes;
    }
    
    ///Set wake event as the absolute time
    ///WARNING: When wake timer fires it will also temporarily wake the screen for a few seconds!
    ///'dtWake' = UTC date/time when to set this wake event (use Set_CFAbsoluteTime() function to create it)
    ///'pdtOutWhen' = if not 0, receives UTC date/time when the wake event was set for. Or receives 0 if didn't set it.
    ///RETURN:
    ///     = true if set OK
    bool setWakeEventAbsolute(CFAbsoluteTime dtWake,
                              CFAbsoluteTime* pdtOutWhen = nullptr)
    {
        bool bRes = false;
        
        if(true)
        {
            //Act from within a lock
            WRITER_LOCK wrl(_lock);
         
            bRes = _setWakeEvent(dtWake,
                                 WAKE_TIMER_EVENT_TYPE,
                                 pdtOutWhen);
        }
        
        return bRes;
    }
    
    ///Stop wake event that was set by setWakeEvent*() functions
    ///INFO: Does nothing if event was not set.
    ///RETURN:
    ///     = true if no errors
    bool stopWakeEvent()
    {
        bool bRes = true;
        
        if(true)
        {
            //Act from within a lock
            WRITER_LOCK wrl(_lock);
            
            //Cancel all events
            size_t szCnt;
            if(!_cancelEvents(_strTmrBundleID.c_str(), NULL, szCnt))
            {
                //Failed
                bRes = false;
            }
            
            //Reset parameters
            _dtmWake = 0;
            
            _bWakeEvtSet = false;
        }
        
        return bRes;
    }
    
    ///Get this wake event info
    ///'pdtOutWhenWake' = if not 0, receives cached UTC date/time when the wake event was supposed to be set for.
    ///'pstrOutBundleID' = if not 0, receives the bundle ID for this wake event - is always returned, even if return is false
    ///RETURN:
    ///     = true if wake event was set
    ///     = false if wake event was not set - all data returned cannot be used
    bool getWakeEventInfo(CFAbsoluteTime* pdtOutWhenWake = nullptr,
                          std::string* pstrOutBundleID = nullptr)
    {
        bool bResult = false;
        
        CFAbsoluteTime dtmWake = 0;
        std::string strBundle;

        if(true)
        {
            //Act from within a lock
            READER_LOCK rdl(_lock);
            
            dtmWake = _dtmWake;
            strBundle = _strTmrBundleID;
         
            bResult = _bWakeEvtSet;
        }
        
        if(pdtOutWhenWake)
            *pdtOutWhenWake = dtmWake;
        if(pstrOutBundleID)
            *pstrOutBundleID = strBundle;
        
        return bResult;
    }
    
    ///Cancel specific (wake) event(s)
    ///'pstrBundleID' = bundle ID to cancel events for, ex: "com.dennisbabkin.wake01", or 0 or "" to cancel all events
    ///'pstrEventType' = event type to cancel events for, or 0 or  "" to cancel for all event types.
    ///                  Ex. kIOPMAutoWake, kIOPMAutoPowerOn,  or kIOPMAutoWakeOrPowerOn
    ///'pszOutCountCanceled' = if not 0, receives the number of events that were canceled
    ///RETURN:
    ///     = true if no errors
    ///     =false if at least one error took place
    bool cancelEvents(const char* pstrBundleID,
                      const char* pstrEventType,
                      size_t* pszOutCountCanceled = nullptr)
    {
        bool bRes;
        size_t szCnt;
        
        if(true)
        {
            //Act from within a lock
            WRITER_LOCK wrl(_lock);
            
            bRes = _cancelEvents(pstrBundleID, pstrEventType, szCnt);
        }
        
        if(pszOutCountCanceled)
            *pszOutCountCanceled = szCnt;
        
        return bRes;
    }
    
    
    ///Put OS to sleep
    ///INFO: Any user can call it. Or, in other words, it does not require administrative permissions.
    ///INFO: This function runs asynchronously, or in other words, it initiates sleep and then returns.
    ///RETURN:
    ///     = kIOReturnSuccess if success
    ///     = Other if error
    static IOReturn performSleep()
    {
        IOReturn iResult;
        
        io_connect_t ioConn = IOPMFindPowerManagement(MACH_PORT_NULL);
        if(ioConn != 0)
        {
            iResult = IOPMSleepSystem(ioConn);
            
            //Free port
            IOServiceClose(ioConn);
        }
        else
        {
            //Failed
            iResult = kIOReturnOffline;
        }
        
        return iResult;
    }
    
    ///RETURN:
    ///     = true if full-sleep is supported by hardware
    ///     = false if only doze-sleep is supported
    static bool isFullSleepSupported()
    {
        return IOPMSleepEnabled();
    }
    
    
#define DIFF_UNIX_EPOCH_AND_MAC_TIME_SEC 978307200      //Difference in seconds between Mac time and Unix Epoch
    
    ///Set 'pOutDtm' from an absolute date & time (for the local time zone)
    ///RETURN:
    ///     = true if success
    static bool Set_CFAbsoluteTime(CFAbsoluteTime* pOutDtm,
                                   int nYear,       //4-digit year
                                   int nMonth,      //[1-12]
                                   int nDay,        //[1-31]
                                   int nHour,       //[0-23]
                                   int nMinute,     //[0-59]
                                   int nSecond,     //[0-59]
                                   int nMillisecond //[0-999]
                                  )
    {
        bool bRes = false;
        
        CFAbsoluteTime dtm = 0;
        
        struct tm t = {};
        t.tm_year = nYear - 1900;       //years since 1900
        t.tm_mon = nMonth - 1;          //months since January [0-11]
        t.tm_mday = nDay;               //day of the month [1-31]
        t.tm_hour = nHour;              //hours since midnight [0-23]
        t.tm_min = nMinute;             //minutes after the hour [0-59]
        t.tm_sec = nSecond;             //seconds after the minute [0-60]
        
        t.tm_isdst = -1;

        //Convert to number of seconds since midnight Jan 1, 1970
        time_t time = mktime(&t);
        if(time != -1)
        {
            //CFAbsoluteTime: Time in fractional seconds since midnight of Jan 1, 2001
            dtm = time;
            
            //Adjust from Jan 1, 1970 to Jan 1, 2001
            dtm -= DIFF_UNIX_EPOCH_AND_MAC_TIME_SEC;
            
            //And apply milliseconds
            dtm += (double)nMillisecond / 1000.0;
            
            bRes = true;
        }
        
        if(pOutDtm)
            *pOutDtm = dtm;
        
        return bRes;
    }

    
private:
    
    ///IMPORTANT: Must be called from within a lock!
    ///'dtWhen' = date/time in UTC
    ///'pstrEventType' = event type, eg: kIOPMAutoWake, kIOPMAutoPowerOn, or kIOPMAutoWakeOrPowerOn
    ///'pdtOutWhen' = if not 0, receives when wake timer was set, or 0 if error
    ///RETURN:
    ///     = true if success
    bool _setWakeEvent(CFAbsoluteTime dtWhen,
                       const char* pstrEventType,
                       CFAbsoluteTime* pdtOutWhen = nullptr)
    {
        bool bRes = false;
        
        CFDateRef refDtm = CFDateCreate(kCFAllocatorDefault, dtWhen);
        if(refDtm)
        {
            CFStringRef refEventType = CFStringCreateWithCString(kCFAllocatorDefault,
                                                                 pstrEventType,
                                                                 kCFStringEncodingUTF8);

            if(refEventType)
            {
                CFStringRef refID = CFStringCreateWithCString(kCFAllocatorDefault,
                                                              _strTmrBundleID.c_str(),
                                                              kCFStringEncodingUTF8);
                
                if(refID)
                {
                    //Cancel previous wake event (if it was set)
                    size_t szCnt;
                    if(!_cancelEvents(_strTmrBundleID.c_str(),
                                      pstrEventType,
                                      szCnt))
                    {
                        //Failed
                        assert(false);
                    }
                    
                    //Create new wake event
                    IOReturn ioRes = IOPMSchedulePowerEvent(refDtm,
                                                            refID,
                                                            refEventType);
                    
                    if(ioRes == kIOReturnSuccess)
                    {
                        //Done
                        bRes = true;
                        
                        _bWakeEvtSet = true;
                    }
                    else
                    {
                        //Error
                        //eg: kIOReturnNotPrivileged = 0xE00002C1 = -536870207
                        assert(false);
                        
                        //Reset parameters
                        _dtmWake = 0;
                        
                        _bWakeEvtSet = false;
                    }
                 
                    CFRelease(refID);
                    refID = NULL;
                }
                else
                {
                    //Error
                    assert(false);
               }
             
                CFRelease(refEventType);
                refEventType = NULL;
            }
            else
            {
                //Error
                assert(false);
            }

            //Free
            CFRelease(refDtm);
        }
        else
        {
            //Error
            assert(false);
        }
        
        
        //Set time when we set it?
        if(pdtOutWhen)
        {
            //Retrieve when the wake timer was set from the OS itself
            if(!_getWakeEventTime(pdtOutWhen))
            {
                //Error
                assert(false);
            }
        }
        
        return bRes;
    }
    
    ///IMPORTANT: Must be called from within a lock!
    ///'pstrBundleID' = bundle ID to cancel events for, ex: "com.dennisbabkin.wake01", or 0 or "" to cancel all events
    ///'pstrEventType' = event type to cancel events for, or 0 or  "" to cancel for all event types.
    ///                  Ex. kIOPMAutoWake, kIOPMAutoPowerOn,  or kIOPMAutoWakeOrPowerOn
    ///'szCntCanceled' = receives the number of events that were canceled
    ///RETURN:
    ///     = true if no errors
    ///     =false if at least one error took place
    bool _cancelEvents(const char* pstrBundleID,
                       const char* pstrEventType,
                       size_t& szCntCanceled)
    {
        bool bResult = true;
        
        szCntCanceled = 0;
        
        CFStringRef refID = NULL;
        if(pstrBundleID &&
           pstrBundleID[0])
        {
            refID = CFStringCreateWithCString(kCFAllocatorDefault,
                                              pstrBundleID,
                                              kCFStringEncodingUTF8);
            if(!refID)
            {
                //Error
                assert(false);
                bResult = false;
            }
        }
        
        CFStringRef refEvtType = NULL;
        if(pstrEventType &&
           pstrEventType[0])
        {
            refEvtType = CFStringCreateWithCString(kCFAllocatorDefault,
                                                   pstrEventType,
                                                   kCFStringEncodingUTF8);
            if(!refEvtType)
            {
                //Error
                assert(false);
                bResult = false;
            }
        }
        
        CFArrayRef refArr = NULL;
        
        struct REM_EVT_INFO
        {
            CFDateRef refDate;
            CFStringRef refID;
            CFStringRef refType;
            
            void clearIt()
            {
                refDate = NULL;
                refID = NULL;
                refType = NULL;
            }
        };
        
        CFTypeRef resVal;
        REM_EVT_INFO rei;
        
        std::vector<REM_EVT_INFO> arrRem;
        
        if(bResult)
        {
            //Enumerate all wake events in the system
            refArr = IOPMCopyScheduledPowerEvents();
            if(refArr)
            {
                CFIndex nCnt = CFArrayGetCount(refArr);
                
                for(CFIndex i = 0; i < nCnt; i++)
                {
                    CFDictionaryRef refDic = (CFDictionaryRef)CFArrayGetValueAtIndex(refArr, i);
                    if(refDic)
                    {
                        CFTypeID type = CFGetTypeID(refDic);
                        if(type == CFDictionaryGetTypeID())
                        {
                            //See if we need to remove this entry
                            rei.clearIt();
                            
                            //Get the ID
                            if(CFDictionaryGetValueIfPresent(refDic,
                                                             CFSTR(kIOPMPowerEventAppNameKey),
                                                             &resVal))
                            {
                                type = CFGetTypeID(resVal);
                                if(type == CFStringGetTypeID())
                                {
                                    //See if it's our ID
                                    if(refID == NULL ||
                                       CFStringCompare(refID,
                                                       (CFStringRef)resVal,
                                                       kCFCompareCaseInsensitive) == kCFCompareEqualTo)
                                    {
                                        //Remove it
                                        rei.refID = (CFStringRef)resVal;
                                    }
                                }
                                else
                                {
                                    //Bad type
                                    assert(false);
                                    bResult = false;
                                }
                            }
                            else
                            {
                                //No such key
                                assert(false);
                                bResult = false;
                            }
                            
                            if(rei.refID)
                            {
                                //See if we need to match the type
                                
                                //Get the event type
                                if(CFDictionaryGetValueIfPresent(refDic,
                                                                 CFSTR(kIOPMPowerEventTypeKey),
                                                                 &resVal))
                                {
                                    type = CFGetTypeID(resVal);
                                    if(type == CFStringGetTypeID())
                                    {
                                        //See if it's our ID
                                        if(refEvtType == NULL ||
                                           CFStringCompare(refEvtType,
                                                           (CFStringRef)resVal,
                                                           0) == kCFCompareEqualTo)
                                        {
                                            //Remove it
                                            rei.refType = (CFStringRef)resVal;
                                        }
                                    }
                                    else
                                    {
                                        //Bad type
                                        assert(false);
                                        bResult = false;
                                    }
                                }
                                else
                                {
                                    //Error
                                    assert(false);
                                    bResult = false;
                                }
                                
                                if(rei.refType)
                                {
                                    //Get the date from this event
                                    if(CFDictionaryGetValueIfPresent(refDic,
                                                                     CFSTR(kIOPMPowerEventTimeKey),
                                                                     &resVal))
                                    {
                                        type = CFGetTypeID(resVal);
                                        if(type == CFDateGetTypeID())
                                        {
                                            rei.refDate = (CFDateRef)resVal;
                                            
                                            //Add it to our list (we'll delete it later)
                                            arrRem.push_back(rei);
                                        }
                                        else
                                        {
                                            //Bad type
                                            assert(false);
                                            bResult = false;
                                        }
                                    }
                                    else
                                    {
                                        //Error
                                        assert(false);
                                        bResult = false;
                                    }
                                }
                            }
                        }
                        else
                        {
                            //Error
                            assert(false);
                            bResult = false;
                        }
                    }
                    else
                    {
                        //Error
                        assert(false);
                        bResult = false;
                    }
                }
            }
            else
            {
                //There are no scheduled events
            }
            
            size_t szCntRem = arrRem.size();
            if(szCntRem > 0)
            {
                const REM_EVT_INFO* pREI = arrRem.data();
                for(size_t i = 0; i < szCntRem; i++, pREI++)
                {
                    assert(pREI->refID);
                    assert(pREI->refDate);
                    assert(pREI->refType);
                    
                    IOReturn ioRes = IOPMCancelScheduledPowerEvent(pREI->refDate,
                                                                   pREI->refID,
                                                                   pREI->refType);
                    
                    if(ioRes == kIOReturnSuccess)
                    {
                        //Done
                        szCntCanceled++;
                    }
                    else
                    {
                        //Failed - don't report some errors
                        if(ioRes != kIOReturnNotFound)      //This may happen because of a delay of us finding it and then canceling it later
                        {
                            assert(false);
                            bResult = false;
                        }
                    }
                }
                
            }
        }
        
        //Free mem
        if(refID)
        {
            CFRelease(refID);
            refID = nullptr;
        }
        
        if(refEvtType)
        {
            CFRelease(refEvtType);
            refEvtType = nullptr;
        }
        
        if(refArr)
        {
            CFRelease(refArr);
            refArr = nullptr;
        }
        
        return bResult;
    }
    
    ///Retrieves wake date/time from this struct
    ///'pdtOut' = if not 0, receives wake event date/time in UTC
    ///RETURN:
    ///     = true if retrieved OK
    bool _getWakeEventTime(CFAbsoluteTime* pdtOut)
    {
        bool bRes = false;

        CFAbsoluteTime dtWake = 0;
        
        CFStringRef refID = CFStringCreateWithCString(kCFAllocatorDefault,
                                                      _strTmrBundleID.c_str(),
                                                      kCFStringEncodingUTF8);
        if(refID)
        {
            CFTypeRef resVal;
            
            //Enumerate all global wake events
            CFArrayRef refArr = IOPMCopyScheduledPowerEvents();
            if(refArr)
            {
                CFIndex nCnt = CFArrayGetCount(refArr);
                
                for(CFIndex i = 0; i < nCnt; i++)
                {
                    CFDictionaryRef refDic = (CFDictionaryRef)CFArrayGetValueAtIndex(refArr, i);
                    if(refDic &&
                       CFGetTypeID(refDic) == CFDictionaryGetTypeID())
                    {
                        //Find our bundle ID
                        if(CFDictionaryGetValueIfPresent(refDic,
                                                         CFSTR(kIOPMPowerEventAppNameKey),
                                                         &resVal) &&
                           CFGetTypeID(resVal) == CFStringGetTypeID() &&
                           CFStringCompare(refID,
                                           (CFStringRef)resVal,
                                           kCFCompareCaseInsensitive) == kCFCompareEqualTo)
                        {
                            //See if this is the right event type
                            if(CFDictionaryGetValueIfPresent(refDic,
                                                             CFSTR(kIOPMPowerEventTypeKey),
                                                             &resVal) &&
                               CFGetTypeID(resVal) == CFStringGetTypeID() &&
                               CFStringCompare(CFSTR(WAKE_TIMER_EVENT_TYPE),
                                               (CFStringRef)resVal,
                                               0) == kCFCompareEqualTo)
                            {
                                //Get date/time
                                if(CFDictionaryGetValueIfPresent(refDic,
                                                                 CFSTR(kIOPMPowerEventTimeKey),
                                                                 &resVal) &&
                                   CFGetTypeID(resVal) == CFDateGetTypeID())
                                {
                                    //Got it
                                    dtWake = CFDateGetAbsoluteTime((CFDateRef)resVal);
                                    
                                    bRes = true;
                                }
                                else
                                {
                                    assert(false);
                                }
                             
                                break;
                            }
                        }
                    }
                    else
                    {
                        assert(false);
                    }
                }
                
                //Free mem
                CFRelease(refArr);
            }
            else
            {
                assert(false);
            }
         
            //Free
            CFRelease(refID);
            refID = NULL;
        }
        else
        {
            assert(false);
        }
        
        if(pdtOut)
            *pdtOut = dtWake;
        
        return bRes;
    }
    

private:
    ///Copy constructor and assignments are NOT available!
    WakeTimer(const WakeTimer& s) = delete;
    WakeTimer& operator = (const WakeTimer& s) = delete;
    
private:
    
    RDR_WRTR _lock;                     //Lock for accessing this struct

    std::string _strTmrBundleID;        //Bundle ID for this timer
    
    bool _bWakeEvtSet = false;          //true if we set the wake event
    
    CFAbsoluteTime _dtmWake = 0;        //UTC date/time when wake event was scheduled
};

Again, I need to explain a few things in the WakeTimer class.

As before, I'm using my RDR_WRTR reader/writer lock to ensure that the WakeTimer class is thread-safe.

Then, to set the wake event you would call either WakeTimer::setWakeEventRelative or WakeTimer::setWakeEventAbsolute functions. The former one sets it as a relative time from the moment it is called, and the former one sets it as an absolute date and time.

To make up an absolute time, I provide a helper function to set the macOS CFAbsoluteTime type:

C++[Copy]
///Set 'pOutDtm' from an absolute date & time (for the local time zone)
///RETURN:
///     = true if success
static bool Set_CFAbsoluteTime(CFAbsoluteTime* pOutDtm,
                               int nYear,       //4-digit year
                               int nMonth,      //[1-12]
                               int nDay,        //[1-31]
                               int nHour,       //[0-23]
                               int nMinute,     //[0-59]
                               int nSecond,     //[0-59]
                               int nMillisecond //[0-999]
                               )
{
	bool bRes = false;
	
	CFAbsoluteTime dtm = 0;
	
	struct tm t = {};
	t.tm_year = nYear - 1900;       //years since 1900
	t.tm_mon = nMonth - 1;          //months since January [0-11]
	t.tm_mday = nDay;               //day of the month [1-31]
	t.tm_hour = nHour;              //hours since midnight [0-23]
	t.tm_min = nMinute;             //minutes after the hour [0-59]
	t.tm_sec = nSecond;             //seconds after the minute [0-60]
	
	t.tm_isdst = -1;

	//Convert to number of seconds since midnight Jan 1, 1970
	time_t time = mktime(&t);
	if(time != -1)
	{
		//CFAbsoluteTime: Time in fractional seconds since midnight of Jan 1, 2001
		dtm = time;
		
		//Adjust from Jan 1, 1970 to Jan 1, 2001
		dtm -= DIFF_UNIX_EPOCH_AND_MAC_TIME_SEC;
		
		//And apply milliseconds
		dtm += (double)nMillisecond / 1000.0;
		
		bRes = true;
	}
	
	if(pOutDtm)
		*pOutDtm = dtm;
	
	return bRes;
}

In that case DIFF_UNIX_EPOCH_AND_MAC_TIME_SEC is defined as a number of seconds between Unix Epoch and the Mac time:

C[Copy]
#define DIFF_UNIX_EPOCH_AND_MAC_TIME_SEC 978307200
One significant limitation that macOS imposes on the process that wants to set a wake event is that it has to run as a root user for the IOPMSchedulePowerEvent function to set a wake event. Otherwise it will return kIOReturnNotPrivileged error code or -536870207.
I am not sure I understand such limitation. My only guess is that Apple didn't want any run-of-the-mill program to set up a wake event and undermine how long a MacBook can run on the battery power source. They are pretty diligent after all about the lifespan of batteries in their devices. But that is just a guess. Maybe someone can correct me in the comments?

Another peculiarity of the wake events on macOS is that they are global, or in other words, once set up they will remain in the system even if the process that set them up exits. This unexpected behavior necessitates the need to cancel each wake event before it can be re-set again. To ensure that one can find a wake event, each one comes with its own bundle identifier, which, in most cases, must be some unique string for your program, like a reverse-DNS string or a unique GUID. Example: "com.dennisbabkin.wake01".

Additionally, as you can probably notice in the WakeTimer class, the wake event does not come with a callback. If you need to do something when the system wakes up, you can use the kIOMessageSystemHasPoweredOn wake notification. Or, you can use your own timer that will invoke your callback, such as the one from the Grand Central Dispatch (GCD) for the Mac, and set it up using the dispatch_source_set_event_handler_f and dispatch_source_set_timer functions. I won't delve into it here, as this post is getting too long. If you want me to show it, let me know in the comments?

Finally, to build the WakeTimer class in Xcode you will need to include the CoreFoundation.framework in the project settings.

You can get a complete source code for my WakeTimer class and how to use it in my GitHub repo.

How To Put macOS to Sleep, or to Reboot, Shutdown

Finally, let me briefly show how to programmatically initiate some power operations that I discussed in this post.

Put macOS to Sleep

The function to put macOS to sleep is pretty straightforward. It uses the IOPMSleepSystem API:

C++[Copy]
///Put OS to sleep
///RETURN:
///     = kIOReturnSuccess if success
///     = Other if error
static IOReturn performSleep()
{
	IOReturn iResult;
	
	io_connect_t ioConn = IOPMFindPowerManagement(MACH_PORT_NULL);
	if(ioConn != 0)
	{
		iResult = IOPMSleepSystem(ioConn);
		
		//Free port
		IOServiceClose(ioConn);
	}
	else
	{
		//Failed
		iResult = kIOReturnOffline;
	}
	
	return iResult;
}

Note that it seems like any process can invoke this function.

Keep in mind that IOPMSleepSystem function works asynchronously, meaning that it initiates sleep and returns, thus the system may enter sleep at some later time.

Additionally, an interesting function is IOPMSleepEnabled, that checks if the hardware that the process runs on supports full sleep, or just a doze-sleep:

C++[Copy]
///RETURN:
///     = true if full-sleep is supported by hardware
///     = false if only doze-sleep is supported
static bool isFullSleepSupported()
{
	return IOPMSleepEnabled();
}

It seems like a doze-sleep is an older form of sleep. I could not identify it with any of my available Apple hardware.

Reboot/Shutdown macOS

Unlike sleep, these two power operations exist in two flavors: "soft" and "hard". The distinction comes from what they do when running programs refuse such power action. Since by definition reboot (or restart) and shutdown reset computer RAM, this may lead to a data loss if some running process did not commit user content to persistent storage.

Thus, a soft power action requests permission from all running programs to perform reboot or shutdown, and only if none of them refuse it, the power operation is initiated. This is a safe way to perform these power operations. The obvious downside of it though is that there's no guarantee that reboot or shutdown will happen, since any program may halt it and a user may ultimately cancel it.

A hard power action proceeds without asking any running programs. Thus it is guaranteed to succeed. But this comes with an obvious downside that some unsaved user data in running apps may be lost.

So having outlined the distinction, here's the function to performs "soft" reboot or shutdown. It is also documented by Apple:

C++[Copy]
#include <Carbon/Carbon.h>

///Perform "soft" reboot or shutdown of the OS
///'bReboot' = true to reboot, false to shut down
///RETURN:
///     - true if successfully started the operation (note that it may be canceled by a user later)
bool RebootShutdownSoft(bool bReboot)
{
	bool bRes = false;
	
	AEEventID evt;
	if(bReboot)
	{
		evt = kAERestart;
	}
	else
	{
		evt = kAEShutDown;
	}
	
	static const ProcessSerialNumber kPSNOfSystemProcess = { 0, kSystemProcess };
	
	AEAddressDesc targetDesc;
	OSStatus err = AECreateDesc(typeProcessSerialNumber,
								&kPSNOfSystemProcess,
								sizeof(kPSNOfSystemProcess),
								&targetDesc);
	if(err == noErr)
	{
		AppleEvent appleEventToSend = {typeNull, nullptr};
		
		err = AECreateAppleEvent(kCoreEventClass,
									evt,
									&targetDesc,
									kAutoGenerateReturnID,
									kAnyTransactionID,
									&appleEventToSend);
		
		if(err == noErr)
		{
			AppleEvent eventReply = {typeNull, nullptr};
			
			err = AESend(&appleEventToSend,
							&eventReply,
							kAENoReply,
							kAENormalPriority,
							kAEDefaultTimeout,
							nullptr,
							nullptr);
			
			if(err == noErr)
			{
				//All good
				bRes = true;
				
				//Free
				AEDisposeDesc(&eventReply);
			}
			else
			{
				//Error
				assert(false);
			}
			
			//Free
			AEDisposeDesc(&appleEventToSend);
		}
		else
		{
			//Error
			assert(false);
		}
		
		//Free data
		AEDisposeDesc(&targetDesc);
	}
	else
	{
		//Error
		assert(false);
	}
	
	return bRes;
}

To compile it in Xcode you need to include the Carbon.framework in the project settings.

Note that my RebootShutdownSoft function, that uses Apple's Carbon framework, does not require a process that calls it to run as a root user to succeed.

And finally, the following function performs a "hard" reboot or shutdown. Note that it uses a low-level API that simply invokes the reboot syscall to the kernel:

C++[Copy]
#include <sys/reboot.h>

///Perform "hard" reboot or shutdown of the OS
///'bReboot' = true to reboot, false to shut down
///RETURN:
///     - true if successfully started the operation
///     - false if failed, check errno for details
bool RebootShutdownHard(bool bReboot)
{
	int nPwrOpFlgs;
	
	if(bReboot)
	{
		//Hard reboot
		nPwrOpFlgs = RB_AUTOBOOT;
	}
	else
	{
		//Hars shutdown
		nPwrOpFlgs = RB_HALT;
	}
	
	//INFO: As I see in practice this function rarely returns, or
	//      reboot is executed really fast...
	int nErr = reboot(nPwrOpFlgs);
	
	//Set error code before returning
	errno = nErr;
	
	return nErr == 0;
}
The reboot function that is used by my RebootShutdownHard requires a process that calls it to run as a root user to succeed.

And as before, you can find a complete source code for my sleep, soft-reboot-shutdown and hard-reboot-shutdown functions in my GitHub repo.

Conclusion

So again we need to make a break. In the next part of this blog post we will review techniques for finding bugs in your native applications developed with Xcode, as well as how to diagnose memory corruption and crashes.

Related Articles