summaryrefslogtreecommitdiffstats
path: root/swayidle
diff options
context:
space:
mode:
authorLibravatar Mattias Eriksson <snaggen@mayam.com>2018-04-17 09:54:02 +0200
committerLibravatar Mattias Eriksson <snaggen@mayam.com>2018-05-13 00:30:09 +0200
commit8fbafbfab5671d56dd469f2205b7906c4a7f7c7c (patch)
treeab4eab0020d97dc5091b72479c383989ccc84729 /swayidle
parentMerge pull request #1967 from emersion/remove-xdg-popup-unmap (diff)
downloadsway-8fbafbfab5671d56dd469f2205b7906c4a7f7c7c.tar.gz
sway-8fbafbfab5671d56dd469f2205b7906c4a7f7c7c.tar.zst
sway-8fbafbfab5671d56dd469f2205b7906c4a7f7c7c.zip
Idle handling for dpms/lockscreen et al
Swayidle handles idle events and allows for dpms and lockscreen handling. It also handles systemd sleep events, and can raise a lockscreen on sleep Fixes #541
Diffstat (limited to 'swayidle')
-rw-r--r--swayidle/main.c414
-rw-r--r--swayidle/meson.build17
-rw-r--r--swayidle/swayidle.1.scd61
-rw-r--r--swayidle/swayidle.1.txt67
4 files changed, 559 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
28typedef void (*timer_callback_func)(void *data);
29
30static struct org_kde_kwin_idle *idle_manager = NULL;
31static struct wl_seat *seat = NULL;
32bool debug = false;
33
34struct 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
43struct swayidle_cmd {
44 timer_callback_func callback;
45 char *param;
46};
47
48struct swayidle_cmd *lock_cmd = NULL;
49
50struct swayidle_timeout_cmd {
51 uint32_t timeout;
52 struct swayidle_cmd *idle_cmd;
53 struct swayidle_cmd *resume_cmd;
54};
55
56static 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)
72static int lock_fd = -1;
73static int ongoing_fd = -1;
74
75static 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
84void 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
115static 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
144static 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
152void 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
182static 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
192static void handle_global_remove(void *data, struct wl_registry *registry,
193 uint32_t name) {
194}
195
196static const struct wl_registry_listener registry_listener = {
197 .global = handle_global,
198 .global_remove = handle_global_remove,
199};
200
201static 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
209static 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
217static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener = {
218 .idle = handle_idle,
219 .resumed = handle_resume,
220};
221
222struct 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
235int 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
267int 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
283int 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
328void 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
338void sig_handler(int signal) {
339 sway_terminate(0);
340}
341
342static int display_event(int fd, uint32_t mask, void *data) {
343 wl_display_dispatch(state.display);
344 return 0;
345}
346
347void 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
363int 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, &registry_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}
diff --git a/swayidle/meson.build b/swayidle/meson.build
new file mode 100644
index 00000000..09c482d6
--- /dev/null
+++ b/swayidle/meson.build
@@ -0,0 +1,17 @@
1threads = dependency('threads')
2
3executable(
4 'swayidle', [
5 'main.c',
6 ],
7 include_directories: [sway_inc],
8 dependencies: [
9 client_protos,
10 wayland_client,
11 wayland_server,
12 wlroots,
13 swayidle_deps,
14 ],
15 link_with: [lib_sway_common, lib_sway_client],
16 install: true
17)
diff --git a/swayidle/swayidle.1.scd b/swayidle/swayidle.1.scd
new file mode 100644
index 00000000..5cd4a7fd
--- /dev/null
+++ b/swayidle/swayidle.1.scd
@@ -0,0 +1,61 @@
1swayidle (1)
2
3# NAME
4
5swayidle - Idle manager for Wayland
6
7# SYNOPSIS
8
9*swayidle* [options] [events...]
10
11# OPTIONS
12
13*-h*
14 Show help message and quit.
15
16*-d*
17 Enable debug output.
18
19# DESCRIPTION
20
21swayidle listens for idle activity on your Wayland compositor and executes tasks
22on various idle-related events. You can specify any number of events at the
23command line.
24
25# EVENTS
26
27*timeout* <timeout> <timeout command> [resume <resume command>]
28 Execute _timeout command_ if there is no activity for <timeout> seconds.
29
30 If you specify "resume <resume command>", _resume command_ will be run when
31 there is activity again.
32
33*before-sleep* <command>
34 If built with systemd support, executes _command_ before systemd puts the
35 computer to sleep.
36
37All commands are executed in a shell.
38
39# EXAMPLE
40
41```
42 swayidle \
43 timeout 300 'swaylock -c 000000' \
44 timeout 600 'swaymsg "output * dpms off"' \
45 resume 'swaymsg "output * dpms on"' \
46 before-sleep 'swaylock -c 000000'
47```
48
49This will lock your screen after 300 seconds of inactivity, then turn off your
50displays after another 600 seconds, and turn your screens back on when resumed.
51It will also lock your screen before your computer goes to sleep.
52
53# AUTHORS
54
55Maintained by Drew DeVault <sir@cmpwn.com>, who is assisted by other open
56source contributors. For more information about sway development, see
57https://github.com/swaywm/sway.
58
59# SEE ALSO
60
61*sway*(5) *swaymsg*(1) *swaygrab*(1) *sway-input*(5) *sway-bar*(5)
diff --git a/swayidle/swayidle.1.txt b/swayidle/swayidle.1.txt
new file mode 100644
index 00000000..a32e6fd5
--- /dev/null
+++ b/swayidle/swayidle.1.txt
@@ -0,0 +1,67 @@
1/////
2vim:set ts=4 sw=4 tw=82 noet:
3/////
4:quotes.~:
5
6swayidle (1)
7============
8
9Name
10----
11swayidle - Idle manager for Wayland
12
13Synopsis
14--------
15'swayidle' [options] [events...]
16
17Options
18-------
19
20*-h*::
21 Show help message and quit.
22
23*-d*::
24 Enable debug output.
25
26Description
27-----------
28
29swayidle listens for idle activity on your Wayland compositor and executes tasks
30on various idle-related events. You can specify any number of events at the
31command line.
32
33Events
34------
35
36*timeout* <timeout> <timeout command> [resume <resume command>]::
37 Execute <timeout command> if there is no activity for <timeout> seconds.
38 +
39 If you specify "resume <resume command>", <resume command> will be run when
40 there is activity again.
41
42*before-sleep* <command>::
43 If built with systemd support, executes <command> before systemd puts the
44 computer to sleep.
45
46All commands are executed in a shell.
47
48Example
49-------
50
51 swayidle \
52 timeout 300 'swaylock -c 000000' \
53 timeout 600 'swaymsg "output * dpms off"' \
54 resume 'swaymsg "output * dpms on"' \
55 before-sleep 'swaylock -c 000000'
56
57This will lock your screen after 300 seconds of inactivity, then turn off your
58displays after another 600 seconds, and turn your screens back on when resumed.
59It will also lock your screen before your computer goes to sleep.
60
61Authors
62-------
63
64Maintained by Drew DeVault <sir@cmpwn.com>, who is assisted by other open
65source contributors. For more information about sway development, see
66<https://github.com/swaywm/sway>.
67