1 #define _POSIX_C_SOURCE 200809L
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <signal.h>
5 #include <stdarg.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/wait.h>
10 #include <time.h>
11 #include <unistd.h>
12 #include <wayland-client-protocol.h>
13 #include <wayland-client.h>
14 #include <wayland-server.h>
15 #include <wayland-util.h>
16 #include <wordexp.h>
17 #include "config.h"
18 #include "idle-client-protocol.h"
19 #include "log.h"
20 #if HAVE_LIBSYSTEMD
21 #include <systemd/sd-bus.h>
22 #elif HAVE_LIBELOGIND
23 #include <elogind/sd-bus.h>
24 #elif HAVE_BASU
25 #include <basu/sd-bus.h>
26 #endif
27 
28 static struct org_kde_kwin_idle *idle_manager = NULL;
29 static struct wl_seat *seat = NULL;
30 
31 struct swayidle_state {
32 	struct wl_display *display;
33 	struct wl_event_loop *event_loop;
34 	struct wl_list timeout_cmds; // struct swayidle_timeout_cmd *
35 	struct wl_list seats;
36 	char *seat_name;
37 	char *before_sleep_cmd;
38 	char *after_resume_cmd;
39 	char *logind_lock_cmd;
40 	char *logind_unlock_cmd;
41 	bool logind_idlehint;
42 	bool timeouts_enabled;
43 	bool wait;
44 } state;
45 
46 struct swayidle_timeout_cmd {
47 	struct wl_list link;
48 	int timeout, registered_timeout;
49 	struct org_kde_kwin_idle_timeout *idle_timer;
50 	char *idle_cmd;
51 	char *resume_cmd;
52 	bool idlehint;
53 	bool resume_pending;
54 };
55 
56 struct seat {
57 	struct wl_list link;
58 	struct wl_seat *proxy;
59 
60 	char *name;
61 	uint32_t capabilities;
62 };
63 
64 static const char *verbosity_colors[] = {
65 	[LOG_SILENT] = "",
66 	[LOG_ERROR ] = "\x1B[1;31m",
67 	[LOG_INFO  ] = "\x1B[1;34m",
68 	[LOG_DEBUG ] = "\x1B[1;30m",
69 };
70 
71 static enum log_importance log_importance = LOG_INFO;
72 
swayidle_log_init(enum log_importance verbosity)73 void swayidle_log_init(enum log_importance verbosity) {
74 	if (verbosity < LOG_IMPORTANCE_LAST) {
75 		log_importance = verbosity;
76 	}
77 }
78 
_swayidle_log(enum log_importance verbosity,const char * fmt,...)79 void _swayidle_log(enum log_importance verbosity, const char *fmt, ...) {
80 	if (verbosity > log_importance) {
81 		return;
82 	}
83 
84 	va_list args;
85 	va_start(args, fmt);
86 
87 	// prefix the time to the log message
88 	struct tm result;
89 	time_t t = time(NULL);
90 	struct tm *tm_info = localtime_r(&t, &result);
91 	char buffer[26];
92 
93 	// generate time prefix
94 	strftime(buffer, sizeof(buffer), "%F %T - ", tm_info);
95 	fprintf(stderr, "%s", buffer);
96 
97 	unsigned c = (verbosity < LOG_IMPORTANCE_LAST)
98 		? verbosity : LOG_IMPORTANCE_LAST - 1;
99 
100 	if (isatty(STDERR_FILENO)) {
101 		fprintf(stderr, "%s", verbosity_colors[c]);
102 	}
103 
104 	vfprintf(stderr, fmt, args);
105 
106 	if (isatty(STDERR_FILENO)) {
107 		fprintf(stderr, "\x1B[0m");
108 	}
109 	fprintf(stderr, "\n");
110 
111 	va_end(args);
112 }
113 
swayidle_init()114 static void swayidle_init() {
115 	memset(&state, 0, sizeof(state));
116 	wl_list_init(&state.timeout_cmds);
117 	wl_list_init(&state.seats);
118 }
119 
swayidle_finish()120 static void swayidle_finish() {
121 
122 	struct swayidle_timeout_cmd *cmd;
123 	struct swayidle_timeout_cmd *tmp;
124 	wl_list_for_each_safe(cmd, tmp, &state.timeout_cmds, link) {
125 		wl_list_remove(&cmd->link);
126 		free(cmd->idle_cmd);
127 		free(cmd->resume_cmd);
128 		free(cmd);
129 	}
130 
131 	free(state.after_resume_cmd);
132 	free(state.before_sleep_cmd);
133 }
134 
sway_terminate(int exit_code)135 void sway_terminate(int exit_code) {
136 	wl_display_disconnect(state.display);
137 	wl_event_loop_destroy(state.event_loop);
138 	swayidle_finish();
139 	exit(exit_code);
140 }
141 
cmd_exec(char * param)142 static void cmd_exec(char *param) {
143 	swayidle_log(LOG_DEBUG, "Cmd exec %s", param);
144 	pid_t pid = fork();
145 	if (pid == 0) {
146 		if (!state.wait) {
147 			pid = fork();
148 		}
149 		if (pid == 0) {
150 			sigset_t set;
151 			sigemptyset(&set);
152 			sigprocmask(SIG_SETMASK, &set, NULL);
153 			signal(SIGINT, SIG_DFL);
154 			signal(SIGTERM, SIG_DFL);
155 			signal(SIGUSR1, SIG_DFL);
156 
157 			char *const cmd[] = { "sh", "-c", param, NULL, };
158 			execvp(cmd[0], cmd);
159 			swayidle_log_errno(LOG_ERROR, "execve failed!");
160 			exit(1);
161 		} else if (pid < 0) {
162 			swayidle_log_errno(LOG_ERROR, "fork failed");
163 			exit(1);
164 		}
165 		exit(0);
166 	} else if (pid < 0) {
167 		swayidle_log_errno(LOG_ERROR, "fork failed");
168 	} else {
169 		swayidle_log(LOG_DEBUG, "Spawned process %s", param);
170 		if (state.wait) {
171 			swayidle_log(LOG_DEBUG, "Blocking until process exits");
172 		}
173 		int status = 0;
174 		waitpid(pid, &status, 0);
175 		if (state.wait && WIFEXITED(status)) {
176 			swayidle_log(LOG_DEBUG, "Process exit status: %d", WEXITSTATUS(status));
177 		}
178 	}
179 }
180 
181 #if HAVE_LOGIND
182 #define DBUS_LOGIND_SERVICE "org.freedesktop.ConsoleKit"
183 #define DBUS_LOGIND_PATH "/org/freedesktop/ConsoleKit/Manager"
184 #define DBUS_LOGIND_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager"
185 #define DBUS_LOGIND_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session"
186 
187 static void enable_timeouts(void);
188 static void disable_timeouts(void);
189 
190 static int sleep_lock_fd = -1;
191 static struct sd_bus *bus = NULL;
192 static char *session_name = NULL;
193 
acquire_inhibitor_lock(const char * type,const char * mode,int * fd)194 static void acquire_inhibitor_lock(const char *type, const char *mode,
195 	int *fd) {
196 	sd_bus_message *msg = NULL;
197 	sd_bus_error error = SD_BUS_ERROR_NULL;
198 	char why[35];
199 
200 	sprintf(why, "Swayidle is preventing %s", type);
201 	int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH,
202 			DBUS_LOGIND_MANAGER_INTERFACE, "Inhibit", &error, &msg,
203 			"ssss", type, "swayidle", why, mode);
204 	if (ret < 0) {
205 		swayidle_log(LOG_ERROR,
206 				"Failed to send %s inhibit signal: %s", type, error.message);
207 		goto cleanup;
208 	}
209 
210 	ret = sd_bus_message_read(msg, "h", fd);
211 	if (ret < 0) {
212 		errno = -ret;
213 		swayidle_log_errno(LOG_ERROR,
214 				"Failed to parse D-Bus response for %s inhibit", type);
215 		goto cleanup;
216 	}
217 
218 	*fd = fcntl(*fd, F_DUPFD_CLOEXEC, 3);
219 	if (*fd >= 0) {
220 		swayidle_log(LOG_DEBUG, "Got %s lock: %d", type, *fd);
221 	} else {
222 		swayidle_log_errno(LOG_ERROR, "Failed to copy %s lock fd", type);
223 	}
224 
225 cleanup:
226 	sd_bus_error_free(&error);
227 	sd_bus_message_unref(msg);
228 }
229 
release_inhibitor_lock(int fd)230 static void release_inhibitor_lock(int fd) {
231 	if (fd >= 0) {
232 		swayidle_log(LOG_DEBUG, "Releasing inhibitor lock %d", fd);
233 		close(fd);
234 	}
235 }
236 
set_idle_hint(bool hint)237 static void set_idle_hint(bool hint) {
238 	swayidle_log(LOG_DEBUG, "SetIdleHint %d", hint);
239 	sd_bus_message *msg = NULL;
240 	sd_bus_error error = SD_BUS_ERROR_NULL;
241 	int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE,
242 			session_name, DBUS_LOGIND_SESSION_INTERFACE, "SetIdleHint",
243 			&error, &msg, "b", hint);
244 	if (ret < 0) {
245 		swayidle_log(LOG_ERROR,
246 				"Failed to send SetIdleHint signal: %s", error.message);
247 	}
248 
249 	sd_bus_error_free(&error);
250 	sd_bus_message_unref(msg);
251 }
252 
get_logind_idle_inhibit(void)253 static bool get_logind_idle_inhibit(void) {
254 	const char *locks;
255 	bool res;
256 
257 	sd_bus_message *reply = NULL;
258 
259 	int ret = sd_bus_get_property(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH,
260 			DBUS_LOGIND_MANAGER_INTERFACE, "BlockInhibited", NULL, &reply, "s");
261 	if (ret < 0) {
262 		goto error;
263 	}
264 
265 	ret = sd_bus_message_read_basic(reply, 's', &locks);
266 	if (ret < 0) {
267 		goto error;
268 	}
269 
270 	res = strstr(locks, "idle") != NULL;
271 	sd_bus_message_unref(reply);
272 
273 	return res;
274 
275 error:
276 	sd_bus_message_unref(reply);
277 	errno = -ret;
278 	swayidle_log_errno(LOG_ERROR,
279 				"Failed to parse get BlockInhibited property");
280 	return false;
281 }
282 
prepare_for_sleep(sd_bus_message * msg,void * userdata,sd_bus_error * ret_error)283 static int prepare_for_sleep(sd_bus_message *msg, void *userdata,
284 		sd_bus_error *ret_error) {
285 	/* "b" apparently reads into an int, not a bool */
286 	int going_down = 1;
287 	int ret = sd_bus_message_read(msg, "b", &going_down);
288 	if (ret < 0) {
289 		errno = -ret;
290 		swayidle_log_errno(LOG_ERROR,
291 				"Failed to parse D-Bus response for Inhibit");
292 	}
293 	swayidle_log(LOG_DEBUG, "PrepareForSleep signal received %d", going_down);
294 	if (!going_down) {
295 		acquire_inhibitor_lock("sleep", "delay", &sleep_lock_fd);
296 		if (state.after_resume_cmd) {
297 			cmd_exec(state.after_resume_cmd);
298 		}
299 		if (state.logind_idlehint) {
300 			set_idle_hint(false);
301 		}
302 		return 0;
303 	}
304 
305 	if (state.before_sleep_cmd) {
306 		cmd_exec(state.before_sleep_cmd);
307 	}
308 	swayidle_log(LOG_DEBUG, "Prepare for sleep done");
309 
310 	release_inhibitor_lock(sleep_lock_fd);
311 	return 0;
312 }
313 
handle_lock(sd_bus_message * msg,void * userdata,sd_bus_error * ret_error)314 static int handle_lock(sd_bus_message *msg, void *userdata,
315 		sd_bus_error *ret_error) {
316 	swayidle_log(LOG_DEBUG, "Lock signal received");
317 
318 	if (state.logind_lock_cmd) {
319 		cmd_exec(state.logind_lock_cmd);
320 	}
321 	swayidle_log(LOG_DEBUG, "Lock command done");
322 
323 	return 0;
324 }
325 
handle_unlock(sd_bus_message * msg,void * userdata,sd_bus_error * ret_error)326 static int handle_unlock(sd_bus_message *msg, void *userdata,
327 		sd_bus_error *ret_error) {
328 	swayidle_log(LOG_DEBUG, "Unlock signal received");
329 
330 	if (state.logind_idlehint) {
331 		set_idle_hint(false);
332 	}
333 	if (state.logind_unlock_cmd) {
334 		cmd_exec(state.logind_unlock_cmd);
335 	}
336 	swayidle_log(LOG_DEBUG, "Unlock command done");
337 
338 	return 0;
339 }
340 
handle_property_changed(sd_bus_message * msg,void * userdata,sd_bus_error * ret_error)341 static int handle_property_changed(sd_bus_message *msg, void *userdata,
342 		sd_bus_error *ret_error) {
343 	const char *name;
344 	swayidle_log(LOG_DEBUG, "PropertiesChanged signal received");
345 
346 	int ret = sd_bus_message_read_basic(msg, 's', &name);
347 	if (ret < 0) {
348 		goto error;
349 	}
350 
351 	if (!strcmp(name, DBUS_LOGIND_MANAGER_INTERFACE)) {
352 		swayidle_log(LOG_DEBUG, "Got PropertyChanged: %s", name);
353 		ret = sd_bus_message_enter_container(msg, 'a', "{sv}");
354 		if (ret < 0) {
355 			goto error;
356 		}
357 
358 		const char *prop;
359 		while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) {
360 			ret = sd_bus_message_read_basic(msg, 's', &prop);
361 			if (ret < 0) {
362 				goto error;
363 			}
364 
365 			if (!strcmp(prop, "BlockInhibited")) {
366 				if (get_logind_idle_inhibit()) {
367 					swayidle_log(LOG_DEBUG, "Logind idle inhibitor found");
368 					disable_timeouts();
369 				} else {
370 					swayidle_log(LOG_DEBUG, "Logind idle inhibitor not found");
371 					enable_timeouts();
372 				}
373 				return 0;
374 			} else {
375 				ret = sd_bus_message_skip(msg, "v");
376 				if (ret < 0) {
377 					goto error;
378 				}
379 			}
380 
381 			ret = sd_bus_message_exit_container(msg);
382 			if (ret < 0) {
383 				goto error;
384 			}
385 		}
386 	}
387 
388 	if (ret < 0) {
389 		goto error;
390 	}
391 
392 	return 0;
393 
394 error:
395 	errno = -ret;
396 	swayidle_log_errno(LOG_ERROR,
397 				"Failed to parse D-Bus response for PropertyChanged");
398 	return 0;
399 }
400 
dbus_event(int fd,uint32_t mask,void * data)401 static int dbus_event(int fd, uint32_t mask, void *data) {
402 	sd_bus *bus = data;
403 
404 	if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
405 		sway_terminate(0);
406 	}
407 
408 	int count = 0;
409 	if (mask & WL_EVENT_READABLE) {
410 		count = sd_bus_process(bus, NULL);
411 	}
412 	if (mask & WL_EVENT_WRITABLE) {
413 		sd_bus_flush(bus);
414 	}
415 	if (mask == 0) {
416 		sd_bus_flush(bus);
417 	}
418 
419 	if (count < 0) {
420 		swayidle_log_errno(LOG_ERROR, "sd_bus_process failed, exiting");
421 		sway_terminate(0);
422 	}
423 
424 	return count;
425 }
426 
set_session(void)427 static void set_session(void) {
428 	sd_bus_message *msg = NULL;
429 	sd_bus_error error = SD_BUS_ERROR_NULL;
430 	const char *session_name_tmp;
431 
432 	int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH,
433 			DBUS_LOGIND_MANAGER_INTERFACE, "GetSession",
434 			&error, &msg, "s", "auto");
435 	if (ret < 0) {
436 		swayidle_log(LOG_DEBUG,
437 				"GetSession failed: %s", error.message);
438 		sd_bus_error_free(&error);
439 		sd_bus_message_unref(msg);
440 
441 		ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH,
442 				DBUS_LOGIND_MANAGER_INTERFACE, "GetSessionByPID",
443 				&error, &msg, "u", getpid());
444 		if (ret < 0) {
445 			swayidle_log(LOG_DEBUG,
446 					"GetSessionByPID failed: %s", error.message);
447 			swayidle_log(LOG_ERROR,
448 					"Failed to find session");
449 			goto cleanup;
450 		}
451 	}
452 
453 	ret = sd_bus_message_read(msg, "o", &session_name_tmp);
454 	if (ret < 0) {
455 		swayidle_log(LOG_ERROR,
456 				"Failed to read session name");
457 		goto cleanup;
458 	}
459 	session_name = strdup(session_name_tmp);
460 	swayidle_log(LOG_DEBUG, "Using session: %s", session_name);
461 
462 cleanup:
463 	sd_bus_error_free(&error);
464 	sd_bus_message_unref(msg);
465 }
466 
connect_to_bus(void)467 static void connect_to_bus(void) {
468 	int ret = sd_bus_default_system(&bus);
469 	if (ret < 0) {
470 		errno = -ret;
471 		swayidle_log_errno(LOG_ERROR, "Failed to open D-Bus connection");
472 		return;
473 	}
474 	struct wl_event_source *source = wl_event_loop_add_fd(state.event_loop,
475 		sd_bus_get_fd(bus), WL_EVENT_READABLE, dbus_event, bus);
476 	wl_event_source_check(source);
477 	set_session();
478 }
479 
setup_sleep_listener(void)480 static void setup_sleep_listener(void) {
481 	int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE,
482                 DBUS_LOGIND_PATH, DBUS_LOGIND_MANAGER_INTERFACE,
483                 "PrepareForSleep", prepare_for_sleep, NULL);
484 	if (ret < 0) {
485 		errno = -ret;
486 		swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : sleep");
487 		return;
488 	}
489 	acquire_inhibitor_lock("sleep", "delay", &sleep_lock_fd);
490 }
491 
setup_lock_listener(void)492 static void setup_lock_listener(void) {
493 	int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE,
494                 session_name, DBUS_LOGIND_SESSION_INTERFACE,
495                 "Lock", handle_lock, NULL);
496 	if (ret < 0) {
497 		errno = -ret;
498 		swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : lock");
499 		return;
500 	}
501 }
502 
setup_unlock_listener(void)503 static void setup_unlock_listener(void) {
504 	int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE,
505                 session_name, DBUS_LOGIND_SESSION_INTERFACE,
506                 "Unlock", handle_unlock, NULL);
507 	if (ret < 0) {
508 		errno = -ret;
509 		swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : unlock");
510 		return;
511 	}
512 }
513 
setup_property_changed_listener(void)514 static void setup_property_changed_listener(void) {
515 	int ret = sd_bus_match_signal(bus, NULL, NULL,
516                 DBUS_LOGIND_PATH, "org.freedesktop.DBus.Properties",
517                 "PropertiesChanged", handle_property_changed, NULL);
518 	if (ret < 0) {
519 		errno = -ret;
520 		swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : property changed");
521 		return;
522 	}
523 }
524 #endif
525 
seat_handle_capabilities(void * data,struct wl_seat * seat,uint32_t capabilities)526 static void seat_handle_capabilities(void *data, struct wl_seat *seat,
527 		uint32_t capabilities) {
528 	struct seat *self = data;
529 	self->capabilities = capabilities;
530 }
531 
seat_handle_name(void * data,struct wl_seat * seat,const char * name)532 static void seat_handle_name(void *data, struct wl_seat *seat,
533 		const char *name) {
534 	struct seat *self = data;
535 	self->name = strdup(name);
536 }
537 
538 static const struct wl_seat_listener wl_seat_listener = {
539 	.name = seat_handle_name,
540 	.capabilities = seat_handle_capabilities,
541 };
542 
handle_global(void * data,struct wl_registry * registry,uint32_t name,const char * interface,uint32_t version)543 static void handle_global(void *data, struct wl_registry *registry,
544 		uint32_t name, const char *interface, uint32_t version) {
545 	if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0) {
546 		idle_manager =
547 			wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1);
548 	} else if (strcmp(interface, wl_seat_interface.name) == 0) {
549 		struct seat *s = calloc(1, sizeof(struct seat));
550 		s->proxy = wl_registry_bind(registry, name, &wl_seat_interface, 2);
551 
552 		wl_seat_add_listener(s->proxy, &wl_seat_listener, s);
553 		wl_list_insert(&state.seats, &s->link);
554 	}
555 }
556 
handle_global_remove(void * data,struct wl_registry * registry,uint32_t name)557 static void handle_global_remove(void *data, struct wl_registry *registry,
558 		uint32_t name) {
559 	// Who cares
560 }
561 
562 static const struct wl_registry_listener registry_listener = {
563 	.global = handle_global,
564 	.global_remove = handle_global_remove,
565 };
566 
567 static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener;
568 
destroy_cmd_timer(struct swayidle_timeout_cmd * cmd)569 static void destroy_cmd_timer(struct swayidle_timeout_cmd *cmd) {
570 	if (cmd->idle_timer != NULL) {
571 		swayidle_log(LOG_DEBUG, "Release idle timer");
572 		org_kde_kwin_idle_timeout_release(cmd->idle_timer);
573 		cmd->idle_timer = NULL;
574 	}
575 }
576 
register_timeout(struct swayidle_timeout_cmd * cmd,int timeout)577 static void register_timeout(struct swayidle_timeout_cmd *cmd,
578 		int timeout) {
579 	destroy_cmd_timer(cmd);
580 
581 	if (timeout < 0) {
582 		swayidle_log(LOG_DEBUG, "Not registering idle timeout");
583 		return;
584 	}
585 	swayidle_log(LOG_DEBUG, "Register with timeout: %d", timeout);
586 	cmd->idle_timer =
587 		org_kde_kwin_idle_get_idle_timeout(idle_manager, seat, timeout);
588 	org_kde_kwin_idle_timeout_add_listener(cmd->idle_timer,
589 		&idle_timer_listener, cmd);
590 	cmd->registered_timeout = timeout;
591 }
592 
enable_timeouts(void)593 static void enable_timeouts(void) {
594 	if (state.timeouts_enabled) {
595 		return;
596 	}
597 #if HAVE_LOGIND
598 	if (get_logind_idle_inhibit()) {
599 		swayidle_log(LOG_INFO, "Not enabling timeouts: idle inhibitor found");
600 		return;
601 	}
602 #endif
603 	swayidle_log(LOG_DEBUG, "Enable idle timeouts");
604 
605 	state.timeouts_enabled = true;
606 	struct swayidle_timeout_cmd *cmd;
607 	wl_list_for_each(cmd, &state.timeout_cmds, link) {
608 		register_timeout(cmd, cmd->timeout);
609 	}
610 }
611 
612 #if HAVE_LOGIND
disable_timeouts(void)613 static void disable_timeouts(void) {
614 	if (!state.timeouts_enabled) {
615 		return;
616 	}
617 	swayidle_log(LOG_DEBUG, "Disable idle timeouts");
618 
619 	state.timeouts_enabled = false;
620 	struct swayidle_timeout_cmd *cmd;
621 	wl_list_for_each(cmd, &state.timeout_cmds, link) {
622 		destroy_cmd_timer(cmd);
623 	}
624 	if (state.logind_idlehint) {
625 		set_idle_hint(false);
626 	}
627 }
628 #endif
629 
handle_idle(void * data,struct org_kde_kwin_idle_timeout * timer)630 static void handle_idle(void *data, struct org_kde_kwin_idle_timeout *timer) {
631 	struct swayidle_timeout_cmd *cmd = data;
632 	cmd->resume_pending = true;
633 	swayidle_log(LOG_DEBUG, "idle state");
634 #if HAVE_LOGIND
635 	if (cmd->idlehint) {
636 		set_idle_hint(true);
637 	} else
638 #endif
639 	if (cmd->idle_cmd) {
640 		cmd_exec(cmd->idle_cmd);
641 	}
642 }
643 
handle_resume(void * data,struct org_kde_kwin_idle_timeout * timer)644 static void handle_resume(void *data, struct org_kde_kwin_idle_timeout *timer) {
645 	struct swayidle_timeout_cmd *cmd = data;
646 	cmd->resume_pending = false;
647 	swayidle_log(LOG_DEBUG, "active state");
648 	if (cmd->registered_timeout != cmd->timeout) {
649 		register_timeout(cmd, cmd->timeout);
650 	}
651 #if HAVE_LOGIND
652 	if (cmd->idlehint) {
653 		set_idle_hint(false);
654 	} else
655 #endif
656 	if (cmd->resume_cmd) {
657 		cmd_exec(cmd->resume_cmd);
658 	}
659 }
660 
661 static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener = {
662 	.idle = handle_idle,
663 	.resumed = handle_resume,
664 };
665 
parse_command(int argc,char ** argv)666 static char *parse_command(int argc, char **argv) {
667 	if (argc < 1) {
668 		swayidle_log(LOG_ERROR, "Missing command");
669 		return NULL;
670 	}
671 
672 	swayidle_log(LOG_DEBUG, "Command: %s", argv[0]);
673 	return strdup(argv[0]);
674 }
675 
build_timeout_cmd(int argc,char ** argv)676 static struct swayidle_timeout_cmd *build_timeout_cmd(int argc, char **argv) {
677 	errno = 0;
678 	char *endptr;
679 	int seconds = strtoul(argv[1], &endptr, 10);
680 	if (errno != 0 || *endptr != '\0') {
681 		swayidle_log(LOG_ERROR, "Invalid %s parameter '%s', it should be a "
682 				"numeric value representing seconds", argv[0], argv[1]);
683 		exit(-1);
684 	}
685 
686 	struct swayidle_timeout_cmd *cmd =
687 		calloc(1, sizeof(struct swayidle_timeout_cmd));
688 	cmd->idlehint = false;
689 	cmd->resume_pending = false;
690 
691 	if (seconds > 0) {
692 		cmd->timeout = seconds * 1000;
693 	} else {
694 		cmd->timeout = -1;
695 	}
696 
697 	return cmd;
698 }
699 
parse_timeout(int argc,char ** argv)700 static int parse_timeout(int argc, char **argv) {
701 	if (argc < 3) {
702 		swayidle_log(LOG_ERROR, "Too few parameters to timeout command. "
703 				"Usage: timeout <seconds> <command>");
704 		exit(-1);
705 	}
706 
707 	struct swayidle_timeout_cmd *cmd = build_timeout_cmd(argc, argv);
708 
709 	swayidle_log(LOG_DEBUG, "Register idle timeout at %d ms", cmd->timeout);
710 	swayidle_log(LOG_DEBUG, "Setup idle");
711 	cmd->idle_cmd = parse_command(argc - 2, &argv[2]);
712 
713 	int result = 3;
714 	if (argc >= 5 && !strcmp("resume", argv[3])) {
715 		swayidle_log(LOG_DEBUG, "Setup resume");
716 		cmd->resume_cmd = parse_command(argc - 4, &argv[4]);
717 		result = 5;
718 	}
719 	wl_list_insert(&state.timeout_cmds, &cmd->link);
720 	return result;
721 }
722 
parse_sleep(int argc,char ** argv)723 static int parse_sleep(int argc, char **argv) {
724 #if !HAVE_LOGIND
725 	swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled "
726 		       "with neither systemd nor elogind nor basu support.", "before-sleep");
727 	exit(-1);
728 #endif
729 	if (argc < 2) {
730 		swayidle_log(LOG_ERROR, "Too few parameters to before-sleep command. "
731 				"Usage: before-sleep <command>");
732 		exit(-1);
733 	}
734 
735 	state.before_sleep_cmd = parse_command(argc - 1, &argv[1]);
736 	if (state.before_sleep_cmd) {
737 		swayidle_log(LOG_DEBUG, "Setup sleep lock: %s", state.before_sleep_cmd);
738 	}
739 
740 	return 2;
741 }
742 
parse_resume(int argc,char ** argv)743 static int parse_resume(int argc, char **argv) {
744 #if !HAVE_LOGIND
745 	swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled "
746 			"with neither systemd nor elogind nor basu support.", "after-resume");
747 	exit(-1);
748 #endif
749 	if (argc < 2) {
750 		swayidle_log(LOG_ERROR, "Too few parameters to after-resume command. "
751 				"Usage: after-resume <command>");
752 		exit(-1);
753 	}
754 
755 	state.after_resume_cmd = parse_command(argc - 1, &argv[1]);
756 	if (state.after_resume_cmd) {
757 		swayidle_log(LOG_DEBUG, "Setup resume hook: %s", state.after_resume_cmd);
758 	}
759 
760 	return 2;
761 }
762 
parse_lock(int argc,char ** argv)763 static int parse_lock(int argc, char **argv) {
764 #if !HAVE_LOGIND
765 	swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled"
766 			" with neither systemd nor elogind nor basu support.", "lock");
767 	exit(-1);
768 #endif
769 	if (argc < 2) {
770 		swayidle_log(LOG_ERROR, "Too few parameters to lock command. "
771 				"Usage: lock <command>");
772 		exit(-1);
773 	}
774 
775 	state.logind_lock_cmd = parse_command(argc - 1, &argv[1]);
776 	if (state.logind_lock_cmd) {
777 		swayidle_log(LOG_DEBUG, "Setup lock hook: %s", state.logind_lock_cmd);
778 	}
779 
780 	return 2;
781 }
782 
parse_unlock(int argc,char ** argv)783 static int parse_unlock(int argc, char **argv) {
784 #if !HAVE_LOGIND
785 	swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled"
786 			" with neither systemd nor elogind nor basu support.", "unlock");
787 	exit(-1);
788 #endif
789 	if (argc < 2) {
790 		swayidle_log(LOG_ERROR, "Too few parameters to unlock command. "
791 				"Usage: unlock <command>");
792 		exit(-1);
793 	}
794 
795 	state.logind_unlock_cmd = parse_command(argc - 1, &argv[1]);
796 	if (state.logind_unlock_cmd) {
797 		swayidle_log(LOG_DEBUG, "Setup unlock hook: %s", state.logind_unlock_cmd);
798 	}
799 
800 	return 2;
801 }
802 
parse_idlehint(int argc,char ** argv)803 static int parse_idlehint(int argc, char **argv) {
804 #if !HAVE_LOGIND
805 	swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled"
806 			" with neither systemd nor elogind nor basu support.", "idlehint");
807 	exit(-1);
808 #endif
809 	if (state.logind_idlehint) {
810 		swayidle_log(LOG_ERROR, "Cannot add multiple idlehint events");
811 		exit(-1);
812 	}
813 	if (argc < 2) {
814 		swayidle_log(LOG_ERROR, "Too few parameters to idlehint command. "
815 				"Usage: idlehint <seconds>");
816 		exit(-1);
817 	}
818 
819 	struct swayidle_timeout_cmd *cmd = build_timeout_cmd(argc, argv);
820 	cmd->idlehint = true;
821 
822 	swayidle_log(LOG_DEBUG, "Register idlehint timeout at %d ms", cmd->timeout);
823 	wl_list_insert(&state.timeout_cmds, &cmd->link);
824 	state.logind_idlehint = true;
825 	return 2;
826 }
827 
parse_args(int argc,char * argv[],char ** config_path)828 static int parse_args(int argc, char *argv[], char **config_path) {
829 	int c;
830 	while ((c = getopt(argc, argv, "C:hdwS:")) != -1) {
831 		switch (c) {
832 		case 'C':
833 			free(*config_path);
834 			*config_path = strdup(optarg);
835 			break;
836 		case 'd':
837 			swayidle_log_init(LOG_DEBUG);
838 			break;
839 		case 'w':
840 			state.wait = true;
841 			break;
842 		case 'S':
843 			state.seat_name = strdup(optarg);
844 			break;
845 		case 'h':
846 		case '?':
847 			printf("Usage: %s [OPTIONS]\n", argv[0]);
848 			printf("  -h\tthis help menu\n");
849 			printf("  -C\tpath to config file\n");
850 			printf("  -d\tdebug\n");
851 			printf("  -w\twait for command to finish\n");
852 			printf("  -S\tpick the seat to work with\n");
853 			return 1;
854 		default:
855 			return 1;
856 		}
857 	}
858 
859 	int i = optind;
860 	while (i < argc) {
861 		if (!strcmp("timeout", argv[i])) {
862 			swayidle_log(LOG_DEBUG, "Got timeout");
863 			i += parse_timeout(argc - i, &argv[i]);
864 		} else if (!strcmp("before-sleep", argv[i])) {
865 			swayidle_log(LOG_DEBUG, "Got before-sleep");
866 			i += parse_sleep(argc - i, &argv[i]);
867 		} else if (!strcmp("after-resume", argv[i])) {
868 			swayidle_log(LOG_DEBUG, "Got after-resume");
869 			i += parse_resume(argc - i, &argv[i]);
870 		} else if (!strcmp("lock", argv[i])) {
871 			swayidle_log(LOG_DEBUG, "Got lock");
872 			i += parse_lock(argc - i, &argv[i]);
873 		} else if (!strcmp("unlock", argv[i])) {
874 			swayidle_log(LOG_DEBUG, "Got unlock");
875 			i += parse_unlock(argc - i, &argv[i]);
876 		} else if (!strcmp("idlehint", argv[i])) {
877 			swayidle_log(LOG_DEBUG, "Got idlehint");
878 			i += parse_idlehint(argc - i, &argv[i]);
879 		} else {
880 			swayidle_log(LOG_ERROR, "Unsupported command '%s'", argv[i]);
881 			return 1;
882 		}
883 	}
884 
885 	return 0;
886 }
887 
handle_signal(int sig,void * data)888 static int handle_signal(int sig, void *data) {
889 	struct swayidle_timeout_cmd *cmd;
890 	switch (sig) {
891 	case SIGINT:
892 	case SIGTERM:
893 		swayidle_log(LOG_DEBUG, "Got SIGTERM");
894 		wl_list_for_each(cmd, &state.timeout_cmds, link) {
895 			if (cmd->resume_pending) {
896 				handle_resume(cmd, cmd->idle_timer);
897 			}
898 		}
899 		sway_terminate(0);
900 		return 0;
901 	case SIGUSR1:
902 		swayidle_log(LOG_DEBUG, "Got SIGUSR1");
903 		wl_list_for_each(cmd, &state.timeout_cmds, link) {
904 			register_timeout(cmd, 0);
905 		}
906 		return 1;
907 	}
908 	abort(); // not reached
909 }
910 
display_event(int fd,uint32_t mask,void * data)911 static int display_event(int fd, uint32_t mask, void *data) {
912 	if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
913 		sway_terminate(0);
914 	}
915 
916 	int count = 0;
917 	if (mask & WL_EVENT_READABLE) {
918 		count = wl_display_dispatch(state.display);
919 	}
920 	if (mask & WL_EVENT_WRITABLE) {
921 		wl_display_flush(state.display);
922 	}
923 	if (mask == 0) {
924 		count = wl_display_dispatch_pending(state.display);
925 		wl_display_flush(state.display);
926 	}
927 
928 	if (count < 0) {
929 		swayidle_log_errno(LOG_ERROR, "wl_display_dispatch failed, exiting");
930 		sway_terminate(0);
931 	}
932 
933 	return count;
934 }
935 
get_config_path(void)936 static char *get_config_path(void) {
937 	static char *config_paths[3] = {
938 		"$XDG_CONFIG_HOME/swayidle/config",
939 		"$HOME/.swayidle/config",
940 		SYSCONFDIR "/swayidle/config",
941 	};
942 
943 	char *config_home = getenv("XDG_CONFIG_HOME");
944 
945 	if (!config_home || config_home[0] == '\n') {
946 		config_paths[0] = "$HOME/.config/swayidle/config";
947 	}
948 
949 	wordexp_t p;
950 	char *path;
951 	for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) {
952 		if (wordexp(config_paths[i], &p, 0) == 0) {
953 			path = strdup(p.we_wordv[0]);
954 			wordfree(&p);
955 			if (path && access(path, R_OK) == 0) {
956 				return path;
957 			}
958 			free(path);
959 		}
960 	}
961 
962 	return NULL;
963 }
964 
load_config(const char * config_path)965 static int load_config(const char *config_path) {
966 	FILE *f = fopen(config_path, "r");
967 
968 	if (!f) {
969 		return -ENOENT;
970 	}
971 
972 	size_t lineno = 0;
973 	char *line = NULL;
974 	size_t n = 0;
975 	ssize_t nread;
976 	while ((nread = getline(&line, &n, f)) != -1) {
977 		lineno++;
978 		if (line[nread-1] == '\n') {
979 			line[nread-1] = '\0';
980 		}
981 
982 		if (strlen(line) == 0 || line[0] == '#') {
983 			continue;
984 		}
985 
986 		size_t i = 0;
987 		while (line[i] != '\0' && line[i] != ' ') {
988 			i++;
989 		}
990 
991 		wordexp_t p;
992 		wordexp(line, &p, 0);
993 		if (strncmp("timeout", line, i) == 0) {
994 			parse_timeout(p.we_wordc, p.we_wordv);
995 		} else if (strncmp("before-sleep", line, i) == 0) {
996 			parse_sleep(p.we_wordc, p.we_wordv);
997 		} else if (strncmp("after-resume", line, i) == 0) {
998 			parse_resume(p.we_wordc, p.we_wordv);
999 		} else if (strncmp("lock", line, i) == 0) {
1000 			parse_lock(p.we_wordc, p.we_wordv);
1001 		} else if (strncmp("unlock", line, i) == 0) {
1002 			parse_unlock(p.we_wordc, p.we_wordv);
1003 		} else if (strncmp("idlehint", line, i) == 0) {
1004 			parse_idlehint(p.we_wordc, p.we_wordv);
1005 		} else {
1006 			line[i] = 0;
1007 			swayidle_log(LOG_ERROR, "Unexpected keyword \"%s\" in line %zu", line, lineno);
1008 			free(line);
1009 			return -EINVAL;
1010 		}
1011 		wordfree(&p);
1012 	}
1013 	free(line);
1014 	fclose(f);
1015 
1016 	return 0;
1017 }
1018 
1019 
main(int argc,char * argv[])1020 int main(int argc, char *argv[]) {
1021 	swayidle_init();
1022 	char *config_path = NULL;
1023 	if (parse_args(argc, argv, &config_path) != 0) {
1024 		swayidle_finish();
1025 		free(config_path);
1026 		return -1;
1027 	}
1028 
1029 	if (!config_path) {
1030 		config_path = get_config_path();
1031 	}
1032 
1033 	int config_load = load_config(config_path);
1034 
1035 	if (config_load == -ENOENT) {
1036 		swayidle_log(LOG_DEBUG, "No config file found.");
1037 	} else if (config_load == -EINVAL) {
1038 		swayidle_log(LOG_ERROR, "Config file %s has errors, exiting.", config_path);
1039 		exit(-1);
1040 	} else {
1041 		swayidle_log(LOG_DEBUG, "Loaded config at %s", config_path);
1042 	}
1043 
1044 	free(config_path);
1045 
1046 	state.event_loop = wl_event_loop_create();
1047 
1048 	wl_event_loop_add_signal(state.event_loop, SIGINT, handle_signal, NULL);
1049 	wl_event_loop_add_signal(state.event_loop, SIGTERM, handle_signal, NULL);
1050 	wl_event_loop_add_signal(state.event_loop, SIGUSR1, handle_signal, NULL);
1051 
1052 	state.display = wl_display_connect(NULL);
1053 	if (state.display == NULL) {
1054 		swayidle_log(LOG_ERROR, "Unable to connect to the compositor. "
1055 				"If your compositor is running, check or set the "
1056 				"WAYLAND_DISPLAY environment variable.");
1057 		swayidle_finish();
1058 		return -3;
1059 	}
1060 
1061 	struct wl_registry *registry = wl_display_get_registry(state.display);
1062 	wl_registry_add_listener(registry, &registry_listener, NULL);
1063 	wl_display_roundtrip(state.display);
1064 	wl_display_roundtrip(state.display);
1065 
1066 	struct seat *seat_i;
1067 	wl_list_for_each(seat_i, &state.seats, link) {
1068 		if (state.seat_name == NULL || strcmp(seat_i->name, state.seat_name) == 0) {
1069 			seat = seat_i->proxy;
1070 		}
1071 	}
1072 
1073 	if (idle_manager == NULL) {
1074 		swayidle_log(LOG_ERROR, "Display doesn't support idle protocol");
1075 		swayidle_finish();
1076 		return -4;
1077 	}
1078 	if (seat == NULL) {
1079 		if (state.seat_name != NULL) {
1080 			swayidle_log(LOG_ERROR, "Seat %s not found", state.seat_name);
1081 		} else {
1082 			swayidle_log(LOG_ERROR, "No seat found");
1083 		}
1084 		swayidle_finish();
1085 		return -5;
1086 	}
1087 
1088 	bool should_run = !wl_list_empty(&state.timeout_cmds);
1089 #if HAVE_LOGIND
1090 	connect_to_bus();
1091 	setup_property_changed_listener();
1092 	if (state.before_sleep_cmd || state.after_resume_cmd) {
1093 		should_run = true;
1094 		setup_sleep_listener();
1095 	}
1096 	if (state.logind_lock_cmd) {
1097 		should_run = true;
1098 		setup_lock_listener();
1099 	}
1100 	if (state.logind_unlock_cmd) {
1101 		should_run = true;
1102 		setup_unlock_listener();
1103 	}
1104 	if (state.logind_idlehint) {
1105 		set_idle_hint(false);
1106 	}
1107 #endif
1108 	if (!should_run) {
1109 		swayidle_log(LOG_INFO, "No command specified! Nothing to do, will exit");
1110 		sway_terminate(0);
1111 	}
1112 
1113 	enable_timeouts();
1114 	wl_display_roundtrip(state.display);
1115 
1116 	struct wl_event_source *source = wl_event_loop_add_fd(state.event_loop,
1117 		wl_display_get_fd(state.display), WL_EVENT_READABLE,
1118 		display_event, NULL);
1119 	wl_event_source_check(source);
1120 
1121 	while (wl_event_loop_dispatch(state.event_loop, -1) != 1) {
1122 		// This space intentionally left blank
1123 	}
1124 
1125 	sway_terminate(0);
1126 }
1127