diff options
Diffstat (limited to 'swayidle/main.c')
-rw-r--r-- | swayidle/main.c | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/swayidle/main.c b/swayidle/main.c new file mode 100644 index 00000000..0eb9e202 --- /dev/null +++ b/swayidle/main.c | |||
@@ -0,0 +1,414 @@ | |||
1 | #define _XOPEN_SOURCE 500 | ||
2 | #include <getopt.h> | ||
3 | #include <signal.h> | ||
4 | #include <pthread.h> | ||
5 | #include <stdio.h> | ||
6 | #include <stdlib.h> | ||
7 | #include <errno.h> | ||
8 | #include <string.h> | ||
9 | #include <unistd.h> | ||
10 | #include <wayland-client-protocol.h> | ||
11 | #include <wayland-client.h> | ||
12 | #include <wayland-util.h> | ||
13 | #include <wlr/config.h> | ||
14 | #include <wlr/util/log.h> | ||
15 | #include <wlr/types/wlr_output_layout.h> | ||
16 | #include <wlr/types/wlr_output.h> | ||
17 | #include "idle-client-protocol.h" | ||
18 | #include "config.h" | ||
19 | #include "list.h" | ||
20 | #ifdef SWAY_IDLE_HAS_SYSTEMD | ||
21 | #include <systemd/sd-bus.h> | ||
22 | #include <systemd/sd-login.h> | ||
23 | #elif defined(SWAY_IDLE_HAS_ELOGIND) | ||
24 | #include <elogind/sd-bus.h> | ||
25 | #include <elogind/sd-login.h> | ||
26 | #endif | ||
27 | |||
28 | typedef void (*timer_callback_func)(void *data); | ||
29 | |||
30 | static struct org_kde_kwin_idle *idle_manager = NULL; | ||
31 | static struct wl_seat *seat = NULL; | ||
32 | bool debug = false; | ||
33 | |||
34 | struct swayidle_state { | ||
35 | struct wl_display *display; | ||
36 | struct org_kde_kwin_idle_timeout *idle_timer; | ||
37 | struct org_kde_kwin_idle_timeout *lock_timer; | ||
38 | struct wlr_output_layout *layout; | ||
39 | struct wl_event_loop *event_loop; | ||
40 | list_t *timeout_cmds; | ||
41 | } state; | ||
42 | |||
43 | struct swayidle_cmd { | ||
44 | timer_callback_func callback; | ||
45 | char *param; | ||
46 | }; | ||
47 | |||
48 | struct swayidle_cmd *lock_cmd = NULL; | ||
49 | |||
50 | struct swayidle_timeout_cmd { | ||
51 | uint32_t timeout; | ||
52 | struct swayidle_cmd *idle_cmd; | ||
53 | struct swayidle_cmd *resume_cmd; | ||
54 | }; | ||
55 | |||
56 | static void cmd_exec(void *data) { | ||
57 | if (data == NULL) { | ||
58 | return; | ||
59 | } | ||
60 | char *param = (char *)data; | ||
61 | wlr_log(L_DEBUG, "Cmd exec %s", param); | ||
62 | int pid = fork(); | ||
63 | if (pid == 0) { | ||
64 | char *const cmd[] = { "sh", "-c", param, NULL, }; | ||
65 | execvp(cmd[0], cmd); | ||
66 | exit(1); | ||
67 | } | ||
68 | wlr_log(L_DEBUG, "Spawned process %d", pid); | ||
69 | } | ||
70 | |||
71 | #if defined(SWAY_IDLE_HAS_SYSTEMD) || defined(SWAY_IDLE_HAS_ELOGIND) | ||
72 | static int lock_fd = -1; | ||
73 | static int ongoing_fd = -1; | ||
74 | |||
75 | static int release_lock(void *data) { | ||
76 | wlr_log(L_INFO, "Releasing sleep lock %d", ongoing_fd); | ||
77 | if (ongoing_fd >= 0) { | ||
78 | close(ongoing_fd); | ||
79 | } | ||
80 | ongoing_fd = -1; | ||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | void acquire_sleep_lock() { | ||
85 | sd_bus_message *msg = NULL; | ||
86 | sd_bus_error error = SD_BUS_ERROR_NULL; | ||
87 | struct sd_bus *bus; | ||
88 | int ret = sd_bus_default_system(&bus); | ||
89 | |||
90 | if (ret < 0) { | ||
91 | wlr_log(L_ERROR, "Failed to open D-Bus connection: %s", | ||
92 | strerror(-ret)); | ||
93 | return; | ||
94 | } | ||
95 | |||
96 | ret = sd_bus_call_method(bus, "org.freedesktop.login1", | ||
97 | "/org/freedesktop/login1", | ||
98 | "org.freedesktop.login1.Manager", "Inhibit", | ||
99 | &error, &msg, "ssss", "sleep", "swayidle", | ||
100 | "Setup Up Lock Screen", "delay"); | ||
101 | if (ret < 0) { | ||
102 | wlr_log(L_ERROR, "Failed to send Inhibit signal: %s", | ||
103 | strerror(-ret)); | ||
104 | } else { | ||
105 | ret = sd_bus_message_read(msg, "h", &lock_fd); | ||
106 | if (ret < 0) { | ||
107 | wlr_log(L_ERROR, | ||
108 | "Failed to parse D-Bus response for Inhibit: %s", | ||
109 | strerror(-ret)); | ||
110 | } | ||
111 | } | ||
112 | wlr_log(L_INFO, "Got sleep lock: %d", lock_fd); | ||
113 | } | ||
114 | |||
115 | static int prepare_for_sleep(sd_bus_message *msg, void *userdata, | ||
116 | sd_bus_error *ret_error) { | ||
117 | bool going_down = true; | ||
118 | int ret = sd_bus_message_read(msg, "b", &going_down); | ||
119 | if (ret < 0) { | ||
120 | wlr_log(L_ERROR, "Failed to parse D-Bus response for Inhibit: %s", | ||
121 | strerror(-ret)); | ||
122 | } | ||
123 | wlr_log(L_DEBUG, "PrepareForSleep signal received %d", going_down); | ||
124 | if (!going_down) { | ||
125 | acquire_sleep_lock(); | ||
126 | return 0; | ||
127 | } | ||
128 | |||
129 | ongoing_fd = lock_fd; | ||
130 | |||
131 | if (lock_cmd && lock_cmd->callback) { | ||
132 | lock_cmd->callback(lock_cmd->param); | ||
133 | } | ||
134 | |||
135 | if (ongoing_fd >= 0) { | ||
136 | struct wl_event_source *source = | ||
137 | wl_event_loop_add_timer(state.event_loop, release_lock, NULL); | ||
138 | wl_event_source_timer_update(source, 1000); | ||
139 | } | ||
140 | wlr_log(L_DEBUG, "Prepare for sleep done"); | ||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static int dbus_event(int fd, uint32_t mask, void *data) { | ||
145 | sd_bus *bus = data; | ||
146 | while (sd_bus_process(bus, NULL) > 0) { | ||
147 | // Do nothing. | ||
148 | } | ||
149 | return 1; | ||
150 | } | ||
151 | |||
152 | void setup_sleep_listener() { | ||
153 | struct sd_bus *bus; | ||
154 | |||
155 | int ret = sd_bus_default_system(&bus); | ||
156 | if (ret < 0) { | ||
157 | wlr_log(L_ERROR, "Failed to open D-Bus connection: %s", | ||
158 | strerror(-ret)); | ||
159 | return; | ||
160 | } | ||
161 | |||
162 | char str[256]; | ||
163 | const char *fmt = "type='signal'," | ||
164 | "sender='org.freedesktop.login1'," | ||
165 | "interface='org.freedesktop.login1.%s'," | ||
166 | "member='%s'," "path='%s'"; | ||
167 | |||
168 | snprintf(str, sizeof(str), fmt, "Manager", "PrepareForSleep", | ||
169 | "/org/freedesktop/login1"); | ||
170 | ret = sd_bus_add_match(bus, NULL, str, prepare_for_sleep, NULL); | ||
171 | if (ret < 0) { | ||
172 | wlr_log(L_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); | ||
173 | return; | ||
174 | } | ||
175 | acquire_sleep_lock(); | ||
176 | |||
177 | wl_event_loop_add_fd(state.event_loop, sd_bus_get_fd(bus), | ||
178 | WL_EVENT_READABLE, dbus_event, bus); | ||
179 | } | ||
180 | #endif | ||
181 | |||
182 | static void handle_global(void *data, struct wl_registry *registry, | ||
183 | uint32_t name, const char *interface, uint32_t version) { | ||
184 | if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0) { | ||
185 | idle_manager = | ||
186 | wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1); | ||
187 | } else if (strcmp(interface, wl_seat_interface.name) == 0) { | ||
188 | seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); | ||
189 | } | ||
190 | } | ||
191 | |||
192 | static void handle_global_remove(void *data, struct wl_registry *registry, | ||
193 | uint32_t name) { | ||
194 | } | ||
195 | |||
196 | static const struct wl_registry_listener registry_listener = { | ||
197 | .global = handle_global, | ||
198 | .global_remove = handle_global_remove, | ||
199 | }; | ||
200 | |||
201 | static void handle_idle(void *data, struct org_kde_kwin_idle_timeout *timer) { | ||
202 | struct swayidle_timeout_cmd *cmd = data; | ||
203 | wlr_log(L_DEBUG, "idle state"); | ||
204 | if (cmd && cmd->idle_cmd && cmd->idle_cmd->callback) { | ||
205 | cmd->idle_cmd->callback(cmd->idle_cmd->param); | ||
206 | } | ||
207 | } | ||
208 | |||
209 | static void handle_resume(void *data, struct org_kde_kwin_idle_timeout *timer) { | ||
210 | struct swayidle_timeout_cmd *cmd = data; | ||
211 | wlr_log(L_DEBUG, "active state"); | ||
212 | if (cmd && cmd->resume_cmd && cmd->resume_cmd->callback) { | ||
213 | cmd->resume_cmd->callback(cmd->resume_cmd->param); | ||
214 | } | ||
215 | } | ||
216 | |||
217 | static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener = { | ||
218 | .idle = handle_idle, | ||
219 | .resumed = handle_resume, | ||
220 | }; | ||
221 | |||
222 | struct swayidle_cmd *parse_command(int argc, char **argv) { | ||
223 | if (argc < 1) { | ||
224 | wlr_log(L_ERROR, "Too few parameters for command in parse_command"); | ||
225 | return NULL; | ||
226 | } | ||
227 | |||
228 | struct swayidle_cmd *cmd = calloc(1, sizeof(struct swayidle_cmd)); | ||
229 | wlr_log(L_DEBUG, "Command: %s", argv[0]); | ||
230 | cmd->callback = cmd_exec; | ||
231 | cmd->param = argv[0]; | ||
232 | return cmd; | ||
233 | } | ||
234 | |||
235 | int parse_timeout(int argc, char **argv) { | ||
236 | if (argc < 3) { | ||
237 | wlr_log(L_ERROR, "Too few parameters to timeout command. " | ||
238 | "Usage: timeout <seconds> <command>"); | ||
239 | exit(-1); | ||
240 | } | ||
241 | errno = 0; | ||
242 | char *endptr; | ||
243 | int seconds = strtoul(argv[1], &endptr, 10); | ||
244 | if (errno != 0 || *endptr != '\0') { | ||
245 | wlr_log(L_ERROR, "Invalid timeout parameter '%s', it should be a " | ||
246 | "numeric value representing seconds", optarg); | ||
247 | exit(-1); | ||
248 | } | ||
249 | struct swayidle_timeout_cmd *cmd = | ||
250 | calloc(1, sizeof(struct swayidle_timeout_cmd)); | ||
251 | cmd->timeout = seconds * 1000; | ||
252 | |||
253 | wlr_log(L_DEBUG, "Register idle timeout at %d ms", cmd->timeout); | ||
254 | wlr_log(L_DEBUG, "Setup idle"); | ||
255 | cmd->idle_cmd = parse_command(argc - 2, &argv[2]); | ||
256 | |||
257 | int result = 3; | ||
258 | if (argc >= 5 && !strcmp("resume", argv[3])) { | ||
259 | wlr_log(L_DEBUG, "Setup resume"); | ||
260 | cmd->resume_cmd = parse_command(argc - 4, &argv[4]); | ||
261 | result = 5; | ||
262 | } | ||
263 | list_add(state.timeout_cmds, cmd); | ||
264 | return result; | ||
265 | } | ||
266 | |||
267 | int parse_sleep(int argc, char **argv) { | ||
268 | if (argc < 2) { | ||
269 | wlr_log(L_ERROR, "Too few parameters to before-sleep command. " | ||
270 | "Usage: before-sleep <command>"); | ||
271 | exit(-1); | ||
272 | } | ||
273 | |||
274 | lock_cmd = parse_command(argc - 1, &argv[1]); | ||
275 | if (lock_cmd) { | ||
276 | wlr_log(L_DEBUG, "Setup sleep lock: %s", lock_cmd->param); | ||
277 | } | ||
278 | |||
279 | return 2; | ||
280 | } | ||
281 | |||
282 | |||
283 | int parse_args(int argc, char *argv[]) { | ||
284 | int c; | ||
285 | |||
286 | while ((c = getopt(argc, argv, "hs:d")) != -1) { | ||
287 | switch(c) { | ||
288 | case 'd': | ||
289 | debug = true; | ||
290 | break; | ||
291 | case 'h': | ||
292 | case '?': | ||
293 | printf("Usage: %s [OPTIONS]\n", argv[0]); | ||
294 | printf(" -d\tdebug\n"); | ||
295 | printf(" -h\tthis help menu\n"); | ||
296 | return 1; | ||
297 | default: | ||
298 | return 1; | ||
299 | } | ||
300 | } | ||
301 | |||
302 | if (debug) { | ||
303 | wlr_log_init(L_DEBUG, NULL); | ||
304 | wlr_log(L_DEBUG, "Loglevel debug"); | ||
305 | } else { | ||
306 | wlr_log_init(L_INFO, NULL); | ||
307 | } | ||
308 | |||
309 | |||
310 | state.timeout_cmds = create_list(); | ||
311 | |||
312 | int i = optind; | ||
313 | while (i < argc) { | ||
314 | if (!strcmp("timeout", argv[i])) { | ||
315 | wlr_log(L_DEBUG, "Got timeout"); | ||
316 | i += parse_timeout(argc - i, &argv[i]); | ||
317 | } else if (!strcmp("before-sleep", argv[i])) { | ||
318 | wlr_log(L_DEBUG, "Got before-sleep"); | ||
319 | i += parse_sleep(argc - i, &argv[i]); | ||
320 | } else { | ||
321 | wlr_log(L_ERROR, "Unsupported command '%s'", argv[i]); | ||
322 | exit(-1); | ||
323 | } | ||
324 | } | ||
325 | return 0; | ||
326 | } | ||
327 | |||
328 | void sway_terminate(int exit_code) { | ||
329 | if (state.event_loop) { | ||
330 | wl_event_loop_destroy(state.event_loop); | ||
331 | } | ||
332 | if (state.display) { | ||
333 | wl_display_disconnect(state.display); | ||
334 | } | ||
335 | exit(exit_code); | ||
336 | } | ||
337 | |||
338 | void sig_handler(int signal) { | ||
339 | sway_terminate(0); | ||
340 | } | ||
341 | |||
342 | static int display_event(int fd, uint32_t mask, void *data) { | ||
343 | wl_display_dispatch(state.display); | ||
344 | return 0; | ||
345 | } | ||
346 | |||
347 | void register_idle_timeout(void *item) { | ||
348 | struct swayidle_timeout_cmd *cmd = item; | ||
349 | if (cmd == NULL || !cmd->timeout) { | ||
350 | wlr_log(L_ERROR, "Invalid idle cmd, will not register"); | ||
351 | return; | ||
352 | } | ||
353 | state.idle_timer = | ||
354 | org_kde_kwin_idle_get_idle_timeout(idle_manager, seat, cmd->timeout); | ||
355 | if (state.idle_timer != NULL) { | ||
356 | org_kde_kwin_idle_timeout_add_listener(state.idle_timer, | ||
357 | &idle_timer_listener, cmd); | ||
358 | } else { | ||
359 | wlr_log(L_ERROR, "Could not create idle timer"); | ||
360 | } | ||
361 | } | ||
362 | |||
363 | int main(int argc, char *argv[]) { | ||
364 | signal(SIGINT, sig_handler); | ||
365 | signal(SIGTERM, sig_handler); | ||
366 | |||
367 | if (parse_args(argc, argv) != 0) { | ||
368 | return -1; | ||
369 | } | ||
370 | |||
371 | state.display = wl_display_connect(NULL); | ||
372 | if (state.display == NULL) { | ||
373 | wlr_log(L_ERROR, "Failed to create display"); | ||
374 | return -3; | ||
375 | } | ||
376 | |||
377 | struct wl_registry *registry = wl_display_get_registry(state.display); | ||
378 | wl_registry_add_listener(registry, ®istry_listener, NULL); | ||
379 | wl_display_roundtrip(state.display); | ||
380 | state.layout = wlr_output_layout_create(); | ||
381 | state.event_loop = wl_event_loop_create(); | ||
382 | |||
383 | if (idle_manager == NULL) { | ||
384 | wlr_log(L_ERROR, "Display doesn't support idle protocol"); | ||
385 | return -4; | ||
386 | } | ||
387 | if (seat == NULL) { | ||
388 | wlr_log(L_ERROR, "Seat error"); | ||
389 | return -5; | ||
390 | } | ||
391 | |||
392 | bool should_run = state.timeout_cmds->length > 0; | ||
393 | #if defined(SWAY_IDLE_HAS_SYSTEMD) || defined(SWAY_IDLE_HAS_ELOGIND) | ||
394 | if (lock_cmd) { | ||
395 | should_run = true; | ||
396 | setup_sleep_listener(); | ||
397 | } | ||
398 | #endif | ||
399 | if (!should_run) { | ||
400 | wlr_log(L_INFO, "No command specified! Nothing to do, will exit"); | ||
401 | sway_terminate(0); | ||
402 | } | ||
403 | list_foreach(state.timeout_cmds, register_idle_timeout); | ||
404 | |||
405 | wl_display_roundtrip(state.display); | ||
406 | |||
407 | wl_event_loop_add_fd(state.event_loop, wl_display_get_fd(state.display), | ||
408 | WL_EVENT_READABLE, display_event, NULL); | ||
409 | |||
410 | while (wl_event_loop_dispatch(state.event_loop, 0) != -1) { | ||
411 | ; //Intentionally left blank; | ||
412 | } | ||
413 | sway_terminate(0); | ||
414 | } | ||