swayaway
a drop-in replacement for swayidle(1)
idle.c
4756 bytes raw
1#include "swayaway.h"
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <time.h>
7
8int64_t mono_ms(void)
9{
10 struct timespec ts;
11 clock_gettime(CLOCK_MONOTONIC, &ts);
12 return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
13}
14
15void idle_add_timeout(int seconds, char *idle_cmd, char *resume_cmd)
16{
17 struct timeout_cmd *tc = calloc(1, sizeof(*tc));
18 if (!tc) {
19 sway_log("out of memory registering timeout");
20 exit(1);
21 }
22 tc->seconds = seconds;
23 tc->idle_cmd = idle_cmd;
24 tc->resume_cmd = resume_cmd;
25 tc->fired = false;
26 tc->next = NULL;
27
28 // append so commands run in configuration order
29 struct timeout_cmd **p = &state.timeouts;
30 while (*p) {
31 p = &(*p)->next;
32 }
33 *p = tc;
34 sway_log_debug("registered timeout %ds%s", seconds,
35 resume_cmd ? " with resume command" : "");
36}
37
38void idle_add_resume_cmd(char *cmd)
39{
40 struct resume_cmd *rc = calloc(1, sizeof(*rc));
41 if (!rc) {
42 sway_log("out of memory registering resume command");
43 exit(1);
44 }
45 rc->cmd = cmd;
46 rc->next = NULL;
47 struct resume_cmd **p = &state.resume_cmds;
48 while (*p) {
49 p = &(*p)->next;
50 }
51 *p = rc;
52}
53
54bool idle_has_work(void)
55{
56 return state.timeouts != NULL || state.resume_cmds != NULL ||
57 state.before_sleep_cmd || state.after_resume_cmd;
58}
59
60// inhibited = one or more ScreenSaver cookies are held
61void idle_recompute_inhibited(void)
62{
63 bool new_val = state.screensaver_inhibited;
64 if (new_val == state.inhibited) {
65 return;
66 }
67 state.inhibited = new_val;
68 if (new_val) {
69 sway_log("idle timeouts suppressed by inhibitor");
70 // don't accumulate idle time while inhibited
71 } else {
72 sway_log("inhibitors released, resetting idle clock");
73 /* swayidle restarts idle counting when an inhibitor goes away;
74 * copy that by resetting the clock and clearing fired flags */
75 state.idle_since_ms = mono_ms();
76 struct timeout_cmd *tc;
77 for (tc = state.timeouts; tc; tc = tc->next) {
78 tc->fired = false;
79 }
80 idle_check_timeouts();
81 }
82}
83
84void idle_on_idled(void)
85{
86 sway_log_debug("compositor reports idle");
87 state.is_idle = true;
88 state.idle_since_ms = mono_ms();
89 idle_check_timeouts();
90}
91
92void idle_on_resumed(void)
93{
94 sway_log_debug("compositor reports activity");
95 state.is_idle = false;
96 screensaver_emit_active();
97
98 struct timeout_cmd *tc;
99 for (tc = state.timeouts; tc; tc = tc->next) {
100 if (tc->fired && tc->resume_cmd) {
101 cmd_exec(tc->resume_cmd);
102 }
103 tc->fired = false;
104 }
105 struct resume_cmd *rc;
106 for (rc = state.resume_cmds; rc; rc = rc->next) {
107 cmd_exec(rc->cmd);
108 }
109}
110
111/* fire every timeout whose deadline has passed; called after every poll
112 * iteration and whenever idle/inhibit state changes */
113void idle_check_timeouts(void)
114{
115 if (!state.is_idle || state.inhibited) {
116 return;
117 }
118 int64_t now = mono_ms();
119 struct timeout_cmd *tc;
120 for (tc = state.timeouts; tc; tc = tc->next) {
121 if (tc->fired || tc->seconds <= 0) {
122 continue;
123 }
124 if (now - state.idle_since_ms >= (int64_t)tc->seconds * 1000) {
125 sway_log_debug("timeout %ds fired", tc->seconds);
126 tc->fired = true;
127 if (tc->idle_cmd) {
128 cmd_exec(tc->idle_cmd);
129 }
130 }
131 }
132 screensaver_emit_active(); // may transition active -> true
133}
134
135/* the screensaver is "active" once at least one timeout command has fired
136 * during the current idle period (mirroring XScreenSaver's GetActive) */
137bool idle_any_timeout_fired(void)
138{
139 struct timeout_cmd *tc;
140 for (tc = state.timeouts; tc; tc = tc->next) {
141 if (tc->fired) {
142 return true;
143 }
144 }
145 return false;
146}
147
148/* milliseconds until the next timeout should fire, or -1 if nothing is
149 * pending; used as the poll() timeout so it wakes up exactly on time */
150int idle_next_timeout_ms(void)
151{
152 if (!state.is_idle || state.inhibited) {
153 return -1;
154 }
155 int64_t now = mono_ms();
156 int64_t best = -1;
157 struct timeout_cmd *tc;
158 for (tc = state.timeouts; tc; tc = tc->next) {
159 if (tc->fired || tc->seconds <= 0) {
160 continue;
161 }
162 int64_t deadline = state.idle_since_ms + (int64_t)tc->seconds * 1000;
163 int64_t remaining = deadline - now;
164 if (remaining < 0) {
165 remaining = 0;
166 }
167 if (best < 0 || remaining < best) {
168 best = remaining;
169 }
170 }
171 if (best < 0) {
172 return -1;
173 }
174 if (best > INT32_MAX) {
175 return INT32_MAX;
176 }
177 return (int)best;
178}
179
180void idle_force_idle(void)
181{
182 sway_log("SIGUSR1: forcing idle state");
183 if (state.inhibited) {
184 sway_log_debug("idle timeouts suppressed by inhibitor, not forcing");
185 return;
186 }
187 state.is_idle = true;
188 state.idle_since_ms = mono_ms();
189 struct timeout_cmd *tc;
190 for (tc = state.timeouts; tc; tc = tc->next) {
191 tc->fired = false;
192 }
193 idle_check_timeouts();
194}
195
196// SIGINT/SIGTERM: run pending resume commands, then terminate
197void idle_terminate(void)
198{
199 sway_log("terminating");
200 if (state.is_idle) {
201 idle_on_resumed();
202 }
203 state.running = false;
204}