#include "swayaway.h" #include #include #include #include int64_t mono_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } void idle_add_timeout(int seconds, char *idle_cmd, char *resume_cmd) { struct timeout_cmd *tc = calloc(1, sizeof(*tc)); if (!tc) { sway_log("out of memory registering timeout"); exit(1); } tc->seconds = seconds; tc->idle_cmd = idle_cmd; tc->resume_cmd = resume_cmd; tc->fired = false; tc->next = NULL; // append so commands run in configuration order struct timeout_cmd **p = &state.timeouts; while (*p) { p = &(*p)->next; } *p = tc; sway_log_debug("registered timeout %ds%s", seconds, resume_cmd ? " with resume command" : ""); } void idle_add_resume_cmd(char *cmd) { struct resume_cmd *rc = calloc(1, sizeof(*rc)); if (!rc) { sway_log("out of memory registering resume command"); exit(1); } rc->cmd = cmd; rc->next = NULL; struct resume_cmd **p = &state.resume_cmds; while (*p) { p = &(*p)->next; } *p = rc; } bool idle_has_work(void) { return state.timeouts != NULL || state.resume_cmds != NULL || state.before_sleep_cmd || state.after_resume_cmd; } // inhibited = one or more ScreenSaver cookies are held void idle_recompute_inhibited(void) { bool new_val = state.screensaver_inhibited; if (new_val == state.inhibited) { return; } state.inhibited = new_val; if (new_val) { sway_log("idle timeouts suppressed by inhibitor"); // don't accumulate idle time while inhibited } else { sway_log("inhibitors released, resetting idle clock"); /* swayidle restarts idle counting when an inhibitor goes away; * copy that by resetting the clock and clearing fired flags */ state.idle_since_ms = mono_ms(); struct timeout_cmd *tc; for (tc = state.timeouts; tc; tc = tc->next) { tc->fired = false; } idle_check_timeouts(); } } void idle_on_idled(void) { sway_log_debug("compositor reports idle"); state.is_idle = true; state.idle_since_ms = mono_ms(); idle_check_timeouts(); } void idle_on_resumed(void) { sway_log_debug("compositor reports activity"); state.is_idle = false; screensaver_emit_active(); struct timeout_cmd *tc; for (tc = state.timeouts; tc; tc = tc->next) { if (tc->fired && tc->resume_cmd) { cmd_exec(tc->resume_cmd); } tc->fired = false; } struct resume_cmd *rc; for (rc = state.resume_cmds; rc; rc = rc->next) { cmd_exec(rc->cmd); } } /* fire every timeout whose deadline has passed; called after every poll * iteration and whenever idle/inhibit state changes */ void idle_check_timeouts(void) { if (!state.is_idle || state.inhibited) { return; } int64_t now = mono_ms(); struct timeout_cmd *tc; for (tc = state.timeouts; tc; tc = tc->next) { if (tc->fired || tc->seconds <= 0) { continue; } if (now - state.idle_since_ms >= (int64_t)tc->seconds * 1000) { sway_log_debug("timeout %ds fired", tc->seconds); tc->fired = true; if (tc->idle_cmd) { cmd_exec(tc->idle_cmd); } } } screensaver_emit_active(); // may transition active -> true } /* the screensaver is "active" once at least one timeout command has fired * during the current idle period (mirroring XScreenSaver's GetActive) */ bool idle_any_timeout_fired(void) { struct timeout_cmd *tc; for (tc = state.timeouts; tc; tc = tc->next) { if (tc->fired) { return true; } } return false; } /* milliseconds until the next timeout should fire, or -1 if nothing is * pending; used as the poll() timeout so it wakes up exactly on time */ int idle_next_timeout_ms(void) { if (!state.is_idle || state.inhibited) { return -1; } int64_t now = mono_ms(); int64_t best = -1; struct timeout_cmd *tc; for (tc = state.timeouts; tc; tc = tc->next) { if (tc->fired || tc->seconds <= 0) { continue; } int64_t deadline = state.idle_since_ms + (int64_t)tc->seconds * 1000; int64_t remaining = deadline - now; if (remaining < 0) { remaining = 0; } if (best < 0 || remaining < best) { best = remaining; } } if (best < 0) { return -1; } if (best > INT32_MAX) { return INT32_MAX; } return (int)best; } void idle_force_idle(void) { sway_log("SIGUSR1: forcing idle state"); if (state.inhibited) { sway_log_debug("idle timeouts suppressed by inhibitor, not forcing"); return; } state.is_idle = true; state.idle_since_ms = mono_ms(); struct timeout_cmd *tc; for (tc = state.timeouts; tc; tc = tc->next) { tc->fired = false; } idle_check_timeouts(); } // SIGINT/SIGTERM: run pending resume commands, then terminate void idle_terminate(void) { sway_log("terminating"); if (state.is_idle) { idle_on_resumed(); } state.running = false; }