While working on my golang chime SDK I ran across the issue of properly reconnecting when macOS wakes up from sleep mode. Apple provides an official example using IOKit, so I had to wrap this with cgo to use it. This post outlines the required steps and provides an example implementation.
-
write a small wrapper to register/ unregister notifications
# main.h #include <ctype.h> #include <stdlib.h> #include <stdio.h> #include <mach/mach_port.h> #include <mach/mach_interface.h> #include <mach/mach_init.h> #include <IOKit/pwr_mgt/IOPMLib.h> #include <IOKit/IOMessage.h> io_connect_t root_port; IONotificationPortRef notifyPortRef; io_object_t notifierObject; void *refCon; CFRunLoopRef runLoop; void MySleepCallBack(void *refCon, io_service_t service, natural_t messageType, void *messageArgument) { switch (messageType) { case kIOMessageCanSystemSleep: if (CanSleep()) { IOAllowPowerChange(root_port, (long)messageArgument); } else { IOCancelPowerChange(root_port, (long)messageArgument); } break; case kIOMessageSystemWillSleep: WillSleep(); IOAllowPowerChange(root_port, (long)messageArgument); break; case kIOMessageSystemWillPowerOn: WillWake(); break; case kIOMessageSystemHasPoweredOn: break; default: break; } } // registerNotifications is called from go to register wake/sleep notifications void registerNotifications() { root_port = IORegisterForSystemPower(refCon, ¬ifyPortRef, MySleepCallBack, ¬ifierObject); if (root_port == 0) { printf("IORegisterForSystemPower failed\n"); } CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes); runLoop = CFRunLoopGetCurrent(); CFRunLoopRun(); } // unregisterNotifications is called from go to remove wake/sleep notifications void unregisterNotifications() { CFRunLoopRemoveSource(runLoop, IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes); IODeregisterForSystemPower(¬ifierObject); IOServiceClose(root_port); IONotificationPortDestroy(notifyPortRef); CFRunLoopStop(runLoop); }
-
Expose go functions to cgo, so that we can call into existing go code for network reconnection handling:
# gofuncs.go package main import ( "C" "log" ) //export CanSleep func CanSleep() C.int { return 1 } //export WillWake func WillWake() { log.Printf("Will Wake") } //export WillSleep func WillSleep() { log.Printf("Will Sleep") }
-
Use the notification handler:
package main // #cgo LDFLAGS: -framework CoreFoundation -framework IOKit // int CanSleep(); // void WillWake(); // void WillSleep(); // #include "main.h" import "C" import ( "log" "time" ) func main() { go func() { C.registerNotifications() log.Printf("Go bye") }() log.Printf("Hello, World") time.Sleep(time.Minute) C.unregisterNotifications() time.Sleep(time.Minute) }
that’s it - actually quite simple. The trickiest part was getting the LDFLAGS right. Hopefully I won’t need to look this up the next time I need it :)