#include "swayaway.h" #include #include #include #include #include #include #include #include #include #include #include #include struct swayaway state; static void usage(const char *prog) { printf("Usage: %s [options] [events...]\n", prog); printf(" -C \tpath to config file\n"); printf(" -h\t\tthis help menu\n"); printf(" -d\t\tdebug\n"); printf(" -w\t\twait for command to finish\n"); printf(" -S\t\tpick the seat to work with\n"); } static int parse_timeout(int argc, char **argv) { if (argc < 3) { sway_log("too few parameters to timeout: " "timeout [resume ]"); exit(-1); } errno = 0; char *endptr = NULL; long seconds = strtol(argv[1], &endptr, 10); if (errno != 0 || endptr == argv[1] || *endptr != '\0') { sway_log("invalid timeout '%s': must be a number of seconds", argv[1]); exit(-1); } char *idle_cmd = NULL; char *resume_cmd = NULL; int consumed = 3; if (argc >= 4 && strcmp(argv[2], "resume") == 0) { /* legacy mode syntax: `timeout N resume CMD` runs on timeout AND on * resume, which was the behavior before swayidle 1.8 */ idle_cmd = strdup(argv[3]); resume_cmd = strdup(argv[3]); consumed = 4; } else { idle_cmd = strdup(argv[2]); if (argc >= 5 && strcmp(argv[3], "resume") == 0) { resume_cmd = strdup(argv[4]); consumed = 5; } } idle_add_timeout(seconds > 0 ? (int)seconds : -1, idle_cmd, resume_cmd); return consumed; } static int parse_resume(int argc, char **argv) { // legacy top-level event: `resume CMD` runs on every activity if (argc < 2) { sway_log("too few parameters to resume: resume "); exit(-1); } idle_add_resume_cmd(strdup(argv[1])); return 2; } static int parse_before_sleep(int argc, char **argv) { if (argc < 2) { sway_log("too few parameters to before-sleep: before-sleep "); exit(-1); } state.before_sleep_cmd = strdup(argv[1]); sway_log_debug("before-sleep: %s", state.before_sleep_cmd); return 2; } static int parse_after_resume(int argc, char **argv) { if (argc < 2) { sway_log("too few parameters to after-resume: after-resume "); exit(-1); } state.after_resume_cmd = strdup(argv[1]); sway_log_debug("after-resume: %s", state.after_resume_cmd); return 2; } static int dispatch_event(int argc, char **argv) { const char *name = argv[0]; if (strcmp(name, "timeout") == 0) { return parse_timeout(argc, argv); } if (strcmp(name, "resume") == 0) { return parse_resume(argc, argv); } if (strcmp(name, "before-sleep") == 0) { return parse_before_sleep(argc, argv); } if (strcmp(name, "after-resume") == 0) { return parse_after_resume(argc, argv); } if (strcmp(name, "lock") == 0 || strcmp(name, "unlock") == 0 || strcmp(name, "idlehint") == 0) { /* swayidle implements these through the logind Session interface; * swayaway intentionally has no logind integration */ sway_log("'%s' is not supported: swayaway has no logind session " "integration", name); exit(-1); } sway_log("unsupported event '%s'", name); return -1; } static int parse_args(int argc, char *argv[], char **config_path) { int c; while ((c = getopt(argc, argv, "C:hdwS:")) != -1) { switch (c) { case 'C': free(*config_path); *config_path = strdup(optarg); break; case 'd': state.debug = true; break; case 'w': state.wait = true; break; case 'S': free(state.seat_name); state.seat_name = strdup(optarg); break; case 'h': case '?': usage(argv[0]); return 1; } } for (int i = optind; i < argc;) { int consumed = dispatch_event(argc - i, &argv[i]); if (consumed <= 0) { return 1; } i += consumed; } return 0; } static char *get_config_path(void) { /* try swayaway's own paths first, then swayidle's, so an existing * swayidle config works as a drop-in */ const char *paths[] = { "$XDG_CONFIG_HOME/swayaway/config", "$HOME/.config/swayaway/config", "$HOME/.swayaway/config", "$XDG_CONFIG_HOME/swayidle/config", "$HOME/.config/swayidle/config", "$HOME/.swayidle/config", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { wordexp_t p; if (wordexp(paths[i], &p, 0) != 0) { continue; } char *path = strdup(p.we_wordv[0]); wordfree(&p); if (path && access(path, R_OK) == 0) { return path; } free(path); } return NULL; } static int load_config(const char *path) { FILE *f = fopen(path, "r"); if (!f) { return -ENOENT; } char *line = NULL; size_t n = 0; ssize_t nread; size_t lineno = 0; while ((nread = getline(&line, &n, f)) != -1) { lineno++; if (nread > 0 && line[nread - 1] == '\n') { line[nread - 1] = '\0'; } if (strlen(line) == 0 || line[0] == '#') { continue; } wordexp_t p; if (wordexp(line, &p, 0) != 0) { sway_log("shell expansion error on line %zu", lineno); free(line); fclose(f); return -EINVAL; } if (p.we_wordc < 1) { wordfree(&p); continue; } if (dispatch_event((int)p.we_wordc, p.we_wordv) <= 0) { sway_log("error on line %zu of %s", lineno, path); wordfree(&p); free(line); fclose(f); return -EINVAL; } wordfree(&p); } free(line); fclose(f); return 0; } int main(int argc, char *argv[]) { state.sleep_lock_fd = -1; state.running = true; char *config_path = NULL; if (parse_args(argc, argv, &config_path) != 0) { free(config_path); return 1; } log_init(state.debug); if (!config_path) { config_path = get_config_path(); } if (config_path) { int r = load_config(config_path); if (r == -EINVAL) { sway_log("config file %s has errors, exiting", config_path); free(config_path); return 1; } else if (r == 0) { sway_log("loaded config from %s", config_path); } } free(config_path); // block the signals handled via signalfd; these turn into poll events sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGUSR1); sigprocmask(SIG_BLOCK, &mask, NULL); int sigfd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); if (sigfd < 0) { sway_log("signalfd failed: %s", strerror(errno)); return 1; } if (wayland_init() != 0) { return 1; } screensaver_init(); if (state.before_sleep_cmd || state.after_resume_cmd) { logind_init(); } if (!idle_has_work()) { sway_log("no events configured; nothing to do, exiting"); return 0; } wayland_create_tracking_notification(); // the wayland fd, the two optional bus fds, and the signalfd struct pollfd fds[4]; int wl_idx = 0; int session_idx = -1; int system_idx = -1; int sig_idx; while (state.running) { int timeout = idle_next_timeout_ms(); int nfds = 0; fds[nfds].fd = wl_display_get_fd(state.display); fds[nfds].events = POLLIN; wl_idx = nfds++; if (state.session_conn) { fds[nfds].fd = state.session_fd; fds[nfds].events = POLLIN; session_idx = nfds++; } else { session_idx = -1; } if (state.system_conn) { fds[nfds].fd = state.system_fd; fds[nfds].events = POLLIN; system_idx = nfds++; } else { system_idx = -1; } fds[nfds].fd = sigfd; fds[nfds].events = POLLIN; sig_idx = nfds++; int r = poll(fds, nfds, timeout); if (r < 0) { if (errno == EINTR) { continue; } sway_log("poll failed: %s", strerror(errno)); break; } if (fds[wl_idx].revents) { if (fds[wl_idx].revents & (POLLERR | POLLHUP)) { sway_log("wayland connection closed"); break; } if (fds[wl_idx].revents & POLLIN) { /* non-blocking read: avoid wl_display_dispatch's internal * poll on spurious wakeups */ if (wl_display_prepare_read(state.display) == -1) { wl_display_dispatch_pending(state.display); } else { wl_display_read_events(state.display); wl_display_dispatch_pending(state.display); } } } if (session_idx >= 0 && fds[session_idx].revents) { if (fds[session_idx].revents & (POLLERR | POLLHUP)) { sway_log("session bus closed"); break; } dbus_connection_read_write_dispatch(state.session_conn, 0); } if (system_idx >= 0 && fds[system_idx].revents) { if (fds[system_idx].revents & (POLLERR | POLLHUP)) { sway_log("system bus closed"); break; } dbus_connection_read_write_dispatch(state.system_conn, 0); } if (fds[sig_idx].revents & POLLIN) { struct signalfd_siginfo si; while (read(sigfd, &si, sizeof(si)) == sizeof(si)) { if (si.ssi_signo == SIGINT || si.ssi_signo == SIGTERM) { idle_terminate(); } else if (si.ssi_signo == SIGUSR1) { idle_force_idle(); } } } idle_check_timeouts(); } logind_release_sleep_lock(); return 0; }