From fdb52e14b5187a7af190b11e10ce9a441ea72520 Mon Sep 17 00:00:00 2001 From: Kevin Bernhagen <6762784+kbernhagen@users.noreply.github.com> Date: Tue, 7 Sep 2021 18:02:54 -0700 Subject: [PATCH] Support osx idle on screensaver, screenlock Add fah-screen-agent Support idle/not idle notifications Add tools.scons --- .gitignore | 1 + SConstruct | 20 +- install/osx/fah-screen-agent.plist | 25 ++ install/osx/scripts/postinstall | 14 +- install/osx/scripts/preinstall | 10 + src/fah/client/osx/OSXOSImpl.cpp | 81 ++++++- src/fah/client/osx/OSXOSImpl.h | 13 +- src/fah/screen-agent/defines.h | 39 +++ src/fah/screen-agent/screen-agent.c | 362 ++++++++++++++++++++++++++++ src/osx-screen-agent.scons | 27 +++ src/tools.scons | 19 ++ 11 files changed, 590 insertions(+), 21 deletions(-) create mode 100644 install/osx/fah-screen-agent.plist create mode 100644 src/fah/screen-agent/defines.h create mode 100644 src/fah/screen-agent/screen-agent.c create mode 100644 src/osx-screen-agent.scons create mode 100644 src/tools.scons diff --git a/.gitignore b/.gitignore index 1ce9cc3..a6e6abd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ *.app # Build byproducts +/bin /build /package.txt /package-description.txt diff --git a/SConstruct b/SConstruct index 526c0d0..bac8b41 100644 --- a/SConstruct +++ b/SConstruct @@ -64,12 +64,15 @@ if env['PLATFORM'] == 'win32' or int(env.get('cross_mingw', 0)): duplicate = 0) Default(hide_console) +tools = SConscript('src/tools.scons', variant_dir = 'build', duplicate = 0) +Default(tools) +Depends(tools, client) # Clean -Clean(client, ['build', 'config.log']) +Clean(client, ['bin', 'build', 'config.log']) # Dist docs = ['README.md', 'CHANGELOG.md', 'LICENSE'] -distfiles = docs + [client, 'images/fahlogo.png'] +distfiles = docs + client + tools + ['images/fahlogo.png'] if env['PLATFORM'] == 'posix': distfiles.append('install/lin/fah-client.service') if hide_console is not None: distfiles.append(hide_console) @@ -122,14 +125,18 @@ if 'package' in COMMAND_LINE_TARGETS: # Specify components for the osx distribution pkg client_home = '.' client_root = client_home + '/build/pkg/root' - pkg_files = [[str(client[0]), 'usr/local/bin/', 0o755], - ['build/install/osx/fahclient.url', + pkg_files = [['build/install/osx/fahclient.url', 'Applications/Folding@home/fahclient.url', 0o644], ['build/install/osx/uninstall.url', 'Applications/Folding@home/uninstall.url', 0o644], ['build/install/osx/launchd.plist', 'Library/LaunchDaemons/' + - 'org.foldingathome.fahclient.plist', 0o644]] + 'org.foldingathome.fahclient.plist', 0o644], + ['build/install/osx/fah-screen-agent.plist','Library/LaunchAgents/' + + 'org.foldingathome.fah-screen-agent.plist', 0o644]] + for tool in client + tools: + pkg_files += [[str(tool), 'usr/local/bin/', 0o755]] + pkg_components = [ { # name is component pkg file name and name shown in installer @@ -145,7 +152,7 @@ if 'package' in COMMAND_LINE_TARGETS: # default build/pkg/root, as per cbang config pkg module 'root' : client_root, # relative to root - 'sign_tools' : ['usr/local/bin/fah-client'], + 'sign_tools' : ['usr/local/bin/*'], 'must_close_apps': [ 'org.foldingathome.fahviewer', 'org.foldingathome.fahcontrol', @@ -221,7 +228,6 @@ if 'package' in COMMAND_LINE_TARGETS: pkg_background = 'fah-opacity-50.png', pkg_customize = 'always', pkg_target = pkg_target, - pkg_arch = env.get('package_arch', 'x86_64'), pkg_components = pkg_components, ) diff --git a/install/osx/fah-screen-agent.plist b/install/osx/fah-screen-agent.plist new file mode 100644 index 0000000..3ed8ac2 --- /dev/null +++ b/install/osx/fah-screen-agent.plist @@ -0,0 +1,25 @@ + + + + + KeepAlive + + SuccessfulExit + + + Label + org.foldingathome.fah-screen-agent + LimitLoadToSessionType + Aqua + LowPriorityIO + + Program + /usr/local/bin/fah-screen-agent + RunAtLoad + + StandardOutPath + /dev/null + Umask + 18 + + diff --git a/install/osx/scripts/postinstall b/install/osx/scripts/postinstall index 94fdd17..7ccdba1 100755 --- a/install/osx/scripts/postinstall +++ b/install/osx/scripts/postinstall @@ -11,16 +11,28 @@ chmod -R u+rwX,go-w "$RUN_DIR" chown -R nobody:nobody "$RUN_DIR" PLIST=/Library/LaunchDaemons/org.foldingathome.fahclient.plist +AGENT_PLIST="/Library/LaunchAgents/org.foldingathome.fah-screen-agent.plist" "$SCRIPTS"/organize-credits.sh & # Start service -chmod 0644 "$PLIST" +chmod 0644 "$PLIST" "$AGENT_PLIST" launchctl load -w "$PLIST" # start, in case RunAtLoad is false launchctl start org.foldingathome.fahclient || true +# restart any running agents in other sessions +# note that this does not reload their launchd jobs +killall -HUP fah-screen-agent || true + +# start agent in user's gui session +conuser=$(/usr/bin/stat -f "%Su" /dev/console) || conuser="root" +conuid=$(/usr/bin/id -u "$conuser") || conuid=0 +if [[ $conuid != 0 ]]; then + launchctl bootstrap gui/$conuid "$AGENT_PLIST" || true +fi + # Don't launch GUI if CLI install [ "$COMMAND_LINE_INSTALL" == "1" ] && exit 0 diff --git a/install/osx/scripts/preinstall b/install/osx/scripts/preinstall index 71ae8df..ff07158 100755 --- a/install/osx/scripts/preinstall +++ b/install/osx/scripts/preinstall @@ -10,6 +10,7 @@ SCRIPTS="$(dirname "$0")" OLD_LAUNCHD="/Library/LaunchDaemons/FAHClient.plist" OLD_LAUNCHD2="/Library/LaunchDaemons/edu.stanford.folding.fahclient.plist" NEW_LAUNCHD="/Library/LaunchDaemons/org.foldingathome.fahclient.plist" +AGENT_PLIST="/Library/LaunchAgents/org.foldingathome.fah-screen-agent.plist" if [ -f "$NEW_LAUNCHD" ]; then launchctl unload -w "$NEW_LAUNCHD" || true @@ -23,6 +24,15 @@ if [ -f "$OLD_LAUNCHD" ]; then rm -f "$OLD_LAUNCHD" || true fi +if [ -f "$AGENT_PLIST" ]; then + # stop agent running in console user's session and unload job + conuser=$(/usr/bin/stat -f "%Su" /dev/console) || conuser="root" + conuid=$(/usr/bin/id -u "$conuser") || conuid=0 + if [[ $conuid != 0 ]]; then + launchctl bootout gui/$conuid "$AGENT_PLIST" || true + fi +fi + # Assuming upgrade, remove old stuff F1="/Applications/FAHClient.url" F2="/Applications/Folding@home/Web Control.url" diff --git a/src/fah/client/osx/OSXOSImpl.cpp b/src/fah/client/osx/OSXOSImpl.cpp index 49f2985..1b59ab2 100644 --- a/src/fah/client/osx/OSXOSImpl.cpp +++ b/src/fah/client/osx/OSXOSImpl.cpp @@ -49,6 +49,8 @@ #include #include +#include + using namespace FAH::Client; using namespace cb; using namespace std; @@ -66,6 +68,9 @@ enum { namespace { + CFStringRef kScreenIdle = CFSTR(SCREEN_IDLE_NOTIFICATION); + CFStringRef kScreenNotIdle = CFSTR(SCREEN_NOT_IDLE_NOTIFICATION); + #pragma mark c callbacks void consoleUserCB(SCDynamicStoreRef s, CFArrayRef keys, void *info) { @@ -94,6 +99,17 @@ namespace { OSXOSImpl::instance().requestExit(); } + void screenIdleCB(CFNotificationCenterRef center, void *observer, + CFNotificationName name, const void *object, + CFDictionaryRef info) { + OSXOSImpl::instance().noteScreenIdle(); + } + + void screenNotIdleCB(CFNotificationCenterRef center, void *observer, + CFNotificationName name, const void *object, + CFDictionaryRef info) { + OSXOSImpl::instance().noteScreenNotIdle(); + } } @@ -204,7 +220,7 @@ void OSXOSImpl::run() { void OSXOSImpl::finishInit() { - LOG_DEBUG(5, "OSXOSImpl::finishInit() on thread " << pthread_self() << + LOG_DEBUG(5, "OSXOSImpl::finishInit() on thread " << pthread_self() << (pthread_main_np() ? " main" : "")); // Init display power state if registration succeeded @@ -222,8 +238,10 @@ void OSXOSImpl::finishInit() { void OSXOSImpl::updateSystemIdle() { - bool shouldBeIdle = displayPower == kDisplayPowerOff || loginwindowIsActive || - screensaverIsActive || screenIsLocked; + bool shouldBeIdle; + if (gotScreenNotIdleRecently()) shouldBeIdle = false; + else shouldBeIdle = displayPower == kDisplayPowerOff || loginwindowIsActive || + gotScreenIdleRecently(); if (shouldBeIdle == systemIsIdle) return; systemIsIdle = shouldBeIdle; event->activate(); @@ -483,6 +501,45 @@ bool OSXOSImpl::registerForConsoleUserNotifications() { } +void OSXOSImpl::noteScreenIdle() { + screenIdleExpiry = + dispatch_time(DISPATCH_TIME_NOW, SCREEN_NOTIFICATION_EXPIRES * NSEC_PER_SEC); + bool wasIdle = screenIdle; + screenIdle = true; + dispatch_after(screenIdleExpiry + NSEC_PER_SEC, dispatch_get_main_queue(), ^{ + updateSystemIdle(); + }); + if (!wasIdle) delayedUpdateSystemIdle(5); +} + + +void OSXOSImpl::noteScreenNotIdle() { + screenNotIdleExpiry = + dispatch_time(DISPATCH_TIME_NOW, SCREEN_NOTIFICATION_EXPIRES * NSEC_PER_SEC); + screenNotIdle = true; + dispatch_after(screenNotIdleExpiry + NSEC_PER_SEC,dispatch_get_main_queue(),^{ + updateSystemIdle(); + }); + updateSystemIdle(); +} + + +bool OSXOSImpl::gotScreenIdleRecently() { + if (!screenIdle) return false; + if (dispatch_time(DISPATCH_TIME_NOW, 0) < screenIdleExpiry) return true; + screenIdle = false; + return false; +} + + +bool OSXOSImpl::gotScreenNotIdleRecently() { + if (!screenNotIdle) return false; + if (dispatch_time(DISPATCH_TIME_NOW, 0) < screenNotIdleExpiry) return true; + screenNotIdle = false; + return false; +} + + bool OSXOSImpl::registerForDarwinNotifications() { CFNotificationCenterRef nc = CFNotificationCenterGetDarwinNotifyCenter(); @@ -496,16 +553,18 @@ bool OSXOSImpl::registerForDarwinNotifications() { CFStringRef name = CFStringCreateWithCString(0, key.c_str(), kCFStringEncodingUTF8); - if (name) { - CFNotificationCenterAddObserver( - nc, (void *)this, ¬eQuitCB, name, 0, - CFNotificationSuspensionBehaviorCoalesce); - CFRelease(name); + if (!name) return false; + CFNotificationCenterAddObserver(nc, (void *)this, ¬eQuitCB, + name, 0, CFNotificationSuspensionBehaviorCoalesce); + CFRelease(name); - return true; - } + CFNotificationCenterAddObserver(nc, (void *)this, &screenIdleCB, + kScreenIdle, 0, CFNotificationSuspensionBehaviorCoalesce); - return false; + CFNotificationCenterAddObserver(nc, (void *)this, &screenNotIdleCB, + kScreenNotIdle, 0, CFNotificationSuspensionBehaviorCoalesce); + + return true; } diff --git a/src/fah/client/osx/OSXOSImpl.h b/src/fah/client/osx/OSXOSImpl.h index 6dbad2d..87b2c89 100644 --- a/src/fah/client/osx/OSXOSImpl.h +++ b/src/fah/client/osx/OSXOSImpl.h @@ -46,10 +46,15 @@ namespace FAH { static OSXOSImpl *singleton; std::atomic systemIsIdle = false; - bool screensaverIsActive = false; - bool screenIsLocked = false; + bool loginwindowIsActive = false; + // these are notification received bools; both are needed + bool screenIdle = false; + bool screenNotIdle = false; + dispatch_time_t screenIdleExpiry = 0; + dispatch_time_t screenNotIdleExpiry = 0; + io_service_t displayWrangler = 0; IONotificationPortRef displayNotePort = 0; CFRunLoopSourceRef displayNoteSource = 0; @@ -87,11 +92,15 @@ namespace FAH { (void *context, io_service_t service, natural_t mtype, void *marg); void finishInit(); void updateTimerFired(CFRunLoopTimerRef timer, void *info); + void noteScreenIdle(); + void noteScreenNotIdle(); protected: void initialize(); void addHeartbeatTimerToRunLoop(CFRunLoopRef loop); bool registerForConsoleUserNotifications(); + bool gotScreenIdleRecently(); + bool gotScreenNotIdleRecently(); bool registerForDarwinNotifications(); bool registerForDisplayPowerNotifications(); bool registerForLaunchEvents(); diff --git a/src/fah/screen-agent/defines.h b/src/fah/screen-agent/defines.h new file mode 100644 index 0000000..0ec2de0 --- /dev/null +++ b/src/fah/screen-agent/defines.h @@ -0,0 +1,39 @@ +/******************************************************************************\ + + This file is part of the Folding@home Client. + + The fah-client runs Folding@home protein folding simulations. + Copyright (c) 2001-2023, foldingathome.org + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + For information regarding this software email: + Joseph Coffland + joseph@cauldrondevelopment.com + +\******************************************************************************/ + +#pragma once + +// Defines shared with fah-client + +#define SCREEN_IDLE_NOTIFICATION "org.foldingathome.screen.idle" +#define SCREEN_NOT_IDLE_NOTIFICATION "org.foldingathome.screen.notidle" + +#define SCREEN_NOTIFICATION_INTERVAL 60 +#define SCREEN_NOTIFICATION_LEEWAY 2 +#define SCREEN_NOTIFICATION_EXPIRES (\ + SCREEN_NOTIFICATION_INTERVAL + 2 * SCREEN_NOTIFICATION_LEEWAY + 1) diff --git a/src/fah/screen-agent/screen-agent.c b/src/fah/screen-agent/screen-agent.c new file mode 100644 index 0000000..485d5d3 --- /dev/null +++ b/src/fah/screen-agent/screen-agent.c @@ -0,0 +1,362 @@ +/******************************************************************************\ + + This file is part of the Folding@home Client. + + The fah-client runs Folding@home protein folding simulations. + Copyright (c) 2001-2023, foldingathome.org + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + For information regarding this software email: + Joseph Coffland + joseph@cauldrondevelopment.com + +\******************************************************************************/ + +// Observe screensaver start/stop, screen lock/unlock notifications. +// Periodically post darwin notifications for screen idle/notidle. +// These notifications are observed by fah-client, and expire +// Agent will exit non-zero on SIGHUP, possibly to be restarted by launchd. + +#ifndef __APPLE__ +#error "This can only be compiled for macOS" +#endif + +#include +#include +#include +#include +#include +#include + +#include "defines.h" + +#define AGENT_LABEL "org.foldingathome.fah-screen-agent" + +#define STRINGIFY(x) XSTRINGIFY(x) +#define XSTRINGIFY(x) #x +#ifndef VERSION +#define VERSION 0.0.0 +#endif +static const char *version = STRINGIFY(VERSION); + +struct _DTimer { + dispatch_source_t ds; + dispatch_time_t start; + uint64_t interval; + uint64_t leeway; +}; +typedef struct _DTimer DTimer; + +static DTimer timer; + +static dispatch_source_t sig_ds[NSIG] = {0}; + +static int quit_sig = 0; +static bool restart = false; +static CFAbsoluteTime start_time = 0; +static os_log_t logger = OS_LOG_DEFAULT; + +static bool screenIsLocked = false; +static bool screensaverIsActive = false; +static bool screenIdle = false; + +static CFNotificationCenterRef nc; +static const char *observer = AGENT_LABEL; + +const char *kScreenIdle = SCREEN_IDLE_NOTIFICATION; +const char *kScreenNotIdle = SCREEN_NOT_IDLE_NOTIFICATION; + + +#if DEBUG +static void print_time() { + time_t now = time(0); + struct tm sTm; + char buff[24]; + gmtime_r(&now, &sTm); + strftime(buff, sizeof(buff), "%F %TZ", &sTm); + printf("%s ", buff); +} + +#define LOGDEBUG(format, ...) do { \ + if (isatty(STDOUT_FILENO)) { \ + print_time(); \ + printf(format, ## __VA_ARGS__); \ + printf("\n"); \ + } else os_log_debug(logger, format, ## __VA_ARGS__); \ + } while(0) + +#else +#define LOGDEBUG(format, ...) + +#endif + + +static void notify() { + LOGDEBUG("%s %s", __FUNCTION__, screenIdle ? kScreenIdle : kScreenNotIdle); + notify_post(screenIdle ? kScreenIdle : kScreenNotIdle); +} + + +static void create_timer() { + // periodically repeat notifications, which expire in client + dispatch_queue_t q = dispatch_get_main_queue(); + timer.ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q); + timer.interval = SCREEN_NOTIFICATION_INTERVAL * NSEC_PER_SEC; + timer.leeway = SCREEN_NOTIFICATION_LEEWAY * NSEC_PER_SEC; + timer.start = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 4); + assert(timer.ds); + dispatch_source_set_timer(timer.ds,timer.start,timer.interval,timer.leeway); + dispatch_source_set_event_handler(timer.ds, ^{notify();}); + dispatch_resume(timer.ds); +} + + +static void reset_timer() { + timer.start = dispatch_time(DISPATCH_TIME_NOW, timer.interval); + dispatch_source_set_timer(timer.ds,timer.start,timer.interval,timer.leeway); +} + + +static void update() { + LOGDEBUG("%s", __FUNCTION__); + bool shouldBeIdle = screenIsLocked || screensaverIsActive; + if (screenIdle == shouldBeIdle) return; + screenIdle = shouldBeIdle; + reset_timer(); + notify(); +} + + +void screenDidLock(CFNotificationCenterRef center, void *observer, + CFStringRef name, const void *object, CFDictionaryRef userInfo) { + LOGDEBUG("%s", __FUNCTION__); + screenIsLocked = true; + update(); +} + + +void screenDidUnlock(CFNotificationCenterRef center, void *observer, + CFStringRef name, const void *object, CFDictionaryRef userInfo) { + LOGDEBUG("%s", __FUNCTION__); + screenIsLocked = false; + update(); +} + + +void screensaverDidStart(CFNotificationCenterRef center, void *observer, + CFStringRef name, const void *object, CFDictionaryRef userInfo) { + LOGDEBUG("%s", __FUNCTION__); + screensaverIsActive = true; + update(); +} + + +void screensaverDidStop(CFNotificationCenterRef center, void *observer, + CFStringRef name, const void *object, CFDictionaryRef userInfo) { + LOGDEBUG("%s", __FUNCTION__); + screensaverIsActive = false; + update(); +} + + +struct note_item { + CFStringRef in; + CFNotificationCallback cb; +}; + + +static const struct note_item notes[] = { + {CFSTR("com.apple.screensaver.didstart"), screensaverDidStart}, + {CFSTR("com.apple.screensaver.didstop"), screensaverDidStop}, + {CFSTR("com.apple.screenIsLocked"), screenDidLock}, + {CFSTR("com.apple.screenIsUnlocked"), screenDidUnlock}, + {NULL, NULL} +}; + + +static void register_for_launch_events() { + // register for xpc launchd.plist LaunchEvents + // this MUST be done if launchd might have LaunchEvents for us + // we never un-register + dispatch_queue_t q = dispatch_get_main_queue(); + xpc_set_event_stream_handler("com.apple.iokit.matching", q, + ^(xpc_object_t event) { + // DO NOTHING but consume event +#if DEBUG + const char *ename = xpc_dictionary_get_string(event, XPC_EVENT_KEY_NAME); + LOGDEBUG("LaunchEvents IO: %s", ename); +#endif + }); + + xpc_set_event_stream_handler("com.apple.notifyd.matching", q, + ^(xpc_object_t event) { + // DO NOTHING but consume event +#if DEBUG + const char *nname = xpc_dictionary_get_string(event, "Notification"); + LOGDEBUG("LaunchEvents notification: %s", nname); +#endif + }); +} + + +static void got_signal(int sig) { + // dispatch callback, NOT a signal handler + LOGDEBUG("%s %s", __FUNCTION__, sys_signame[sig]); + switch (sig) { + case SIGTERM: + quit_sig = sig; + // fallthrough + case SIGINT: + CFRunLoopStop(CFRunLoopGetMain()); + break; + case SIGHUP: + restart = true; + CFRunLoopStop(CFRunLoopGetMain()); + break; + default: break; + } +} + + +static void dummy_signal_handler(int sig) {} + + +static void install_signal_handlers() { + sigset_t sigs; + sigemptyset(&sigs); + + sigaddset(&sigs, SIGTERM); + sigaddset(&sigs, SIGINT); + sigaddset(&sigs, SIGHUP); + + struct sigaction action = {0}; + action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &action, NULL); + + dispatch_queue_t q = dispatch_get_main_queue(); + // dummy handler instead of SIG_IGN so syscalls can still be interrupted + action.sa_handler = dummy_signal_handler; + for(int s = 1; s < NSIG; s++) { + if (sigismember(&sigs, s)) { + sigaction(s, &action, NULL); + sig_ds[s] = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, s, 0, q); + assert(sig_ds[s]); + dispatch_source_set_event_handler(sig_ds[s], ^{got_signal(s);}); + dispatch_resume(sig_ds[s]); + } + } +} + + +static void release_signal_handlers() { + for(int s = 1; s < NSIG; s++) + if (sig_ds[s]) dispatch_release(sig_ds[s]); +} + + +static void usage(int argc, const char * argv[]) { + printf("usage: %s [{start|stop}]\n", argv[0]); + printf(" start convenience for /bin/launchctl start %s\n", AGENT_LABEL); + printf(" stop convenience for /bin/launchctl stop %s\n", AGENT_LABEL); + printf(" -h,--help\n"); + printf(" --version\n"); +} + + +static void parse_args(int argc, const char * argv[]) { + if (getppid() != 1 && 1 < argc) { + const char *prog = "/bin/launchctl"; + if (strcmp("start", argv[1]) == 0 || strcmp("stop", argv[1]) == 0) { + printf("%s %s %s\n", prog, argv[1], AGENT_LABEL); + execl(prog, prog, argv[1], AGENT_LABEL); + } + if (strcmp("--help", argv[1]) == 0 || strcmp("-h", argv[1]) == 0) { + usage(argc, argv); + } else if (strcmp("--version", argv[1]) == 0) { + printf("%s\n", version); + } else { + printf("error: unknown argument: %s\n", argv[1]); + usage(argc, argv); + exit(EX_USAGE); + } + exit(0); + } +} + + +static void init(int argc, const char * argv[]) { + start_time = CFAbsoluteTimeGetCurrent(); +#if DEBUG + logger = os_log_create(AGENT_LABEL, "main"); +#endif + nc = CFNotificationCenterGetDistributedCenter(); + + if (geteuid() == 0) { + fprintf(stderr, "error: %s cannot be run as root\n", argv[0]); + exit(1); + } + // FIXME: refuse to run if not GUI session + + parse_args(argc, argv); + + install_signal_handlers(); + register_for_launch_events(); + + // TODO: maybe get initial values from CGSessionCopyCurrentDictionary + // https://stackoverflow.com/a/11511419 + // this should always be correct for a fresh gui login + screenIsLocked = false; + screensaverIsActive = false; + screenIdle = false; + + // set up notifications + for (int i = 0; notes[i].in; i++) + CFNotificationCenterAddObserver(nc, observer, notes[i].cb, + notes[i].in, NULL, CFNotificationSuspensionBehaviorCoalesce); + + create_timer(); +} + + +int main(int argc, const char * argv[]) { + init(argc, argv); + CFRunLoopRun(); + + // any critical cleanup would go here + + // exit immediately if think launchd signaled us + if (quit_sig) exit(0); + + // exit non-zero if want to restart (assume appropriate KeepAlive) + if (restart) exit(EX_TEMPFAIL); + +#if DEBUG + // non-critical cleanup + CFNotificationCenterRemoveEveryObserver(nc, observer); + dispatch_source_cancel(timer.ds); + dispatch_release(timer.ds); + release_signal_handlers(); +#endif + + // run 10+ sec or launchd may think we crashed and relaunch us + if (getppid() == 1) { + CFTimeInterval run_time = CFAbsoluteTimeGetCurrent() - start_time; + if (run_time < 10.0) sleep(10 - (int)run_time); + } + + return 0; +} diff --git a/src/osx-screen-agent.scons b/src/osx-screen-agent.scons new file mode 100644 index 0000000..cccb795 --- /dev/null +++ b/src/osx-screen-agent.scons @@ -0,0 +1,27 @@ +Import('*') + +env = env.Clone() +env.Replace(LIBS = []) +env.Replace(LIBPATH = []) +env.Replace(CPPPATH = []) +env.Replace(FRAMEWORKS = ['CoreFoundation']) + +try: + env['LINKFLAGS'].remove('-stdlib=libc++') +except: pass + +if not (env.GetOption('clean') or env.get('debug')): + try: + env.AppendUnique(CCFLAGS = ['-Os']) + env['CCFLAGS'].remove('-O3') + env['CCFLAGS'].remove('-funroll-loops') + except: pass + +version = env.get('PACKAGE_VERSION') +if version: env.CBDefine('VERSION=' + version) + +src = Glob('fah/screen-agent/*.c') + +tool = env.Program('#/bin/fah-screen-agent', src) + +Return('tool') diff --git a/src/tools.scons b/src/tools.scons new file mode 100644 index 0000000..91f94f8 --- /dev/null +++ b/src/tools.scons @@ -0,0 +1,19 @@ +Import('*') + +tools_scons = Glob('all-*.scons') + +if env['PLATFORM'] == 'win32' or int(env.get('cross_mingw', 0)): + tools_scons += Glob('win-*.scons') +elif env['PLATFORM'] == 'darwin': + tools_scons += Glob('osx-*.scons') +else: + tools_scons += Glob('lin-*.scons') + +Export('env') + +tools = [] +for tscons in tools_scons: + tool = SConscript(tscons, variant_dir = '#/build', duplicate = 0) + if tool: tools += tool + +Return('tools')