--- scheduler/sysman.c 2026-04-28 01:13:21.000000000 +0800 +++ scheduler/sysman.c 2026-06-09 00:23:46.000000000 +0800 @@ -17,6 +17,14 @@ #include "cupsd.h" #ifdef __APPLE__ # include + +# ifndef kIOPMAssertNetworkClientActive +# define kIOPMAssertNetworkClientActive CFSTR("NoIdleSleepAssertion") +# endif + +# ifndef kIOPSNotifyPowerSource +# define kIOPSNotifyPowerSource "com.apple.system.powersources" +# endif #endif /* __APPLE__ */ @@ -202,6 +210,7 @@ # include # include # include +# include # include @@ -266,7 +274,8 @@ static cupsd_sysevent_t LastSysEvent; /* Last system event (for delayed sleep) */ static int NameChanged = 0;/* Did we get a 'name changed' event during sleep? */ static int PSToken = 0; /* Power source notifications */ - +static int PSNotifyFD = -1; /* Power source notify pipe fd */ +static dispatch_source_t PSDispatchSource = NULL; /* Power source dispatch source */ /* * Local functions... @@ -282,9 +291,103 @@ static void sysEventTimerNotifier(CFRunLoopTimerRef timer, void *context); static void sysUpdate(void); static void sysUpdateNames(void); +static int sysGetACPower(void); +static void sysEventPowerSourceHandler(void *ctx); + +/* + * 'sysGetACPower()' - Return 1 if running on AC power, 0 on battery. + * + * Uses IOPSCopyPowerSourcesInfo() which is available since OS X 10.0, + * so this works on all macOS versions without needing + * IOPSGetTimeRemainingEstimate() (macOS 10.7+). + */ + +static int +sysGetACPower(void) +{ + CFTypeRef blob; /* Power sources blob */ + CFArrayRef list; /* Power source list */ + CFIndex i, count; /* Loop variables */ + int ac = 1; /* Result: assume AC if unknown */ + + + blob = IOPSCopyPowerSourcesInfo(); + if (!blob) + return 1; + + list = IOPSCopyPowerSourcesList(blob); + if (!list) + { + CFRelease(blob); + return 1; + } + + count = CFArrayGetCount(list); + if (count == 0) + { + /* + * No battery sources at all — AC-only hardware (desktop, etc.) + */ + + CFRelease(list); + CFRelease(blob); + return 1; + } + + ac = 0; + for (i = 0; i < count; i ++) + { + CFDictionaryRef src = IOPSGetPowerSourceDescription(blob, + CFArrayGetValueAtIndex(list, i)); + CFStringRef state; + + if (!src) + continue; + + state = CFDictionaryGetValue(src, CFSTR("Power Source State")); + if (state && + CFStringCompare(state, CFSTR("AC Power"), 0) == kCFCompareEqualTo) + { + ac = 1; + break; + } + } + + CFRelease(list); + CFRelease(blob); + return ac; +} /* + * 'sysEventPowerSourceHandler()' - Dispatch source handler for power source + * change notifications. + * + * Called on the main GCD queue when kIOPSNotifyPowerSource fires. + * Replaces the notify_register_dispatch block which required Apple Blocks. + * The context pointer carries the notify pipe fd so we can drain it. + */ + +static void +sysEventPowerSourceHandler(void *ctx) +{ + int fd = (int)(intptr_t)ctx; + int token; + + + /* + * Each notification delivers one int-sized token value to the pipe; + * drain it so the fd does not fill up. + */ + + read(fd, &token, sizeof(token)); + + ACPower = sysGetACPower(); + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "Power source changed, ACPower now %d.", ACPower); +} + +/* * 'cupsdAllowSleep()' - Tell the OS it is now OK to sleep. */ @@ -340,10 +443,33 @@ * Monitor for power source changes via dispatch queue... */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartSystemMonitor: IOPSGetTimeRemainingEstimate=%f", IOPSGetTimeRemainingEstimate()); - ACPower = IOPSGetTimeRemainingEstimate() == kIOPSTimeRemainingUnlimited; - notify_register_dispatch(kIOPSNotifyPowerSource, &PSToken, dispatch_get_main_queue(), ^(int t) { (void)t; - ACPower = IOPSGetTimeRemainingEstimate() == kIOPSTimeRemainingUnlimited; }); + /* + * Initialise the AC power state, then monitor for future changes via a + * GCD dispatch source. notify_register_file_descriptor (10.0+) delivers + * each notification as a 4-byte token written to a pipe; we watch that + * pipe with dispatch_source_create/dispatch_source_set_event_handler_f + * (10.6+, C function-pointer variant — no Blocks required). + */ + + ACPower = sysGetACPower(); + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cupsdStartSystemMonitor: ACPower=%d", ACPower); + + if (notify_register_file_descriptor(kIOPSNotifyPowerSource, + &PSNotifyFD, 0, &PSToken) + == NOTIFY_STATUS_OK && PSNotifyFD >= 0) + { + PSDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, + (uintptr_t)PSNotifyFD, 0, + dispatch_get_main_queue()); + if (PSDispatchSource) + { + dispatch_set_context(PSDispatchSource, (void *)(intptr_t)PSNotifyFD); + dispatch_source_set_event_handler_f(PSDispatchSource, + sysEventPowerSourceHandler); + dispatch_resume(PSDispatchSource); + } + } } @@ -390,11 +516,30 @@ cupsdClosePipe(SysEventPipes); } + /* + * Cancel and release the power source dispatch source first, then + * cancel the notify token, then close the pipe fd. + * Order matters: cancel the source before closing the fd it watches. + */ + + if (PSDispatchSource) + { + dispatch_source_cancel(PSDispatchSource); + dispatch_release(PSDispatchSource); + PSDispatchSource = NULL; + } + if (PSToken != 0) { notify_cancel(PSToken); PSToken = 0; } + + if (PSNotifyFD >= 0) + { + close(PSNotifyFD); + PSNotifyFD = -1; + } }