pax_global_header00006660000000000000000000000064140502361510014507gustar00rootroot0000000000000052 comment=d5e4dff69e4d29c2fea2be739f11dcee9a082658 swayidle-1.7/000077500000000000000000000000001405023615100131775ustar00rootroot00000000000000swayidle-1.7/.build.yml000066400000000000000000000003641405023615100151020ustar00rootroot00000000000000image: alpine/edge packages: - meson - wayland-dev - wayland-protocols - scdoc sources: - https://github.com/swaywm/swayidle tasks: - setup: | cd swayidle meson build - build: | cd swayidle ninja -C build swayidle-1.7/.gitignore000066400000000000000000000000061405023615100151630ustar00rootroot00000000000000build swayidle-1.7/LICENSE000066400000000000000000000020451405023615100142050ustar00rootroot00000000000000Copyright (c) 2016-2018 Drew DeVault Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. swayidle-1.7/README.md000066400000000000000000000021151405023615100144550ustar00rootroot00000000000000# swayidle This is sway's idle management daemon, swayidle. It is compatible with any Wayland compositor which implements the KDE [idle](https://github.com/swaywm/sway/blob/master/protocols/idle.xml) protocol. See the man page, `swayidle(1)`, for instructions on configuring swayidle. ## Release Signatures Releases are signed with [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) and published [on GitHub](https://github.com/swaywm/swayidle/releases). swayidle releases are managed independently of sway releases. ## Installation ### From Packages Swayidle is available in many distributions. Try installing the "swayidle" package for yours. If you're interested in packaging swayidle for your distribution, stop by the IRC channel or shoot an email to sir@cmpwn.com for advice. ### Compiling from Source Install dependencies: * meson \* * wayland * wayland-protocols \* * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* * git \* _\*Compile-time dep_ Run these commands: meson build ninja -C build sudo ninja -C build install swayidle-1.7/completions/000077500000000000000000000000001405023615100155335ustar00rootroot00000000000000swayidle-1.7/completions/bash/000077500000000000000000000000001405023615100164505ustar00rootroot00000000000000swayidle-1.7/completions/bash/swayidle000066400000000000000000000017351405023615100202220ustar00rootroot00000000000000# swaymsg(1) completion _swayidle() { local cur prev _get_comp_words_by_ref -n : cur prev local prev2=${COMP_WORDS[COMP_CWORD-2]} local prev3=${COMP_WORDS[COMP_CWORD-3]} events=( 'timeout' 'before-sleep' ) short=( -h -d -w ) if [ "$prev" = timeout ]; then # timeout return elif [ "$prev2" = timeout ]; then # timeout COMPREPLY=($(compgen -c -- "$cur")) return elif [ "$prev3" = timeout ]; then # timeout [resume ] COMPREPLY=(resume) # optional argument; no return here as user may skip 'resume' fi case "$prev" in resume) COMPREPLY=($(compgen -c -- "$cur")) return ;; before-sleep) COMPREPLY=($(compgen -c -- "$cur")) return ;; esac COMPREPLY+=($(compgen -W "${events[*]}" -- "$cur")) COMPREPLY+=($(compgen -W "${short[*]}" -- "$cur")) } && complete -F _swayidle swayidle swayidle-1.7/completions/fish/000077500000000000000000000000001405023615100164645ustar00rootroot00000000000000swayidle-1.7/completions/fish/swayidle.fish000066400000000000000000000011011405023615100211510ustar00rootroot00000000000000# swayidle set -l all_events timeout before-sleep after-resume lock unlock idlehint set -l cmd_events before-sleep after-resume lock unlock set -l time_events idlehint timeout complete -c swayidle --arguments "$all_events" complete -c swayidle --condition "__fish_seen_subcommand_from $cmd_events" --require-parameter complete -c swayidle --condition "__fish_seen_subcommand_from $time_events" --exclusive complete -c swayidle -s h --description 'show help' complete -c swayidle -s d --description 'debug' complete -c swayidle -s w --description 'wait for command to finish' swayidle-1.7/completions/zsh/000077500000000000000000000000001405023615100163375ustar00rootroot00000000000000swayidle-1.7/completions/zsh/_swayidle000066400000000000000000000014211405023615100202400ustar00rootroot00000000000000#compdef swayidle # # Completion script for swayidle # local events=('timeout:Execute timeout command if there is no activity for timeout seconds' 'before-sleep:Execute before-sleep command before sleep') local resume=('resume:Execute command when there is activity again') if (($#words <= 2)); then _describe -t "events" 'swayidle' events _arguments -C \ '(-h --help)'{-h,--help}'[Show help message and quit]' \ '(-d)'-d'[Enable debug output]' \ '(-w)'-w'[Wait for command to finish executing before continuing]' elif [[ "$words[-3]" == before-sleep || "$words[-3]" == resume ]]; then _describe -t "events" 'swayidle' events elif [[ "$words[-4]" == timeout ]]; then _describe -t "events" 'swayidle' events _describe -t "resume" 'swayidle' resume fi swayidle-1.7/idle.xml000066400000000000000000000045621405023615100146450ustar00rootroot00000000000000 . ]]> This interface allows to monitor user idle time on a given seat. The interface allows to register timers which trigger after no user activity was registered on the seat for a given interval. It notifies when user activity resumes. This is useful for applications wanting to perform actions when the user is not interacting with the system, e.g. chat applications setting the user as away, power management features to dim screen, etc.. swayidle-1.7/main.c000066400000000000000000000656751405023615100143120ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "idle-client-protocol.h" #if HAVE_SYSTEMD #include #include #elif HAVE_ELOGIND #include #include #endif static struct org_kde_kwin_idle *idle_manager = NULL; static struct wl_seat *seat = NULL; struct swayidle_state { struct wl_display *display; struct wl_event_loop *event_loop; struct wl_list timeout_cmds; // struct swayidle_timeout_cmd * struct wl_list seats; char *seat_name; char *before_sleep_cmd; char *after_resume_cmd; char *logind_lock_cmd; char *logind_unlock_cmd; bool logind_idlehint; bool timeouts_enabled; bool wait; } state; struct swayidle_timeout_cmd { struct wl_list link; int timeout, registered_timeout; struct org_kde_kwin_idle_timeout *idle_timer; char *idle_cmd; char *resume_cmd; bool idlehint; bool resume_pending; }; struct seat { struct wl_list link; struct wl_seat *proxy; char *name; uint32_t capabilities; }; enum log_importance { LOG_DEBUG = 1, LOG_INFO = 2, LOG_ERROR = 3, }; static enum log_importance verbosity = LOG_INFO; static void swayidle_log(enum log_importance importance, const char *fmt, ...) { if (importance < verbosity) { return; } va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); } static void swayidle_log_errno( enum log_importance importance, const char *fmt, ...) { if (importance < verbosity) { return; } va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, ": %s\n", strerror(errno)); } static void swayidle_init() { memset(&state, 0, sizeof(state)); wl_list_init(&state.timeout_cmds); wl_list_init(&state.seats); } static void swayidle_finish() { struct swayidle_timeout_cmd *cmd; struct swayidle_timeout_cmd *tmp; wl_list_for_each_safe(cmd, tmp, &state.timeout_cmds, link) { wl_list_remove(&cmd->link); free(cmd->idle_cmd); free(cmd->resume_cmd); free(cmd); } free(state.after_resume_cmd); free(state.before_sleep_cmd); } void sway_terminate(int exit_code) { wl_display_disconnect(state.display); wl_event_loop_destroy(state.event_loop); swayidle_finish(); exit(exit_code); } static void cmd_exec(char *param) { swayidle_log(LOG_DEBUG, "Cmd exec %s", param); pid_t pid = fork(); if (pid == 0) { if (!state.wait) { pid = fork(); } if (pid == 0) { char *const cmd[] = { "sh", "-c", param, NULL, }; execvp(cmd[0], cmd); swayidle_log_errno(LOG_ERROR, "execve failed!"); exit(1); } else if (pid < 0) { swayidle_log_errno(LOG_ERROR, "fork failed"); exit(1); } exit(0); } else if (pid < 0) { swayidle_log_errno(LOG_ERROR, "fork failed"); } else { swayidle_log(LOG_DEBUG, "Spawned process %s", param); waitpid(pid, NULL, 0); } } #if HAVE_SYSTEMD || HAVE_ELOGIND #define DBUS_LOGIND_SERVICE "org.freedesktop.login1" #define DBUS_LOGIND_PATH "/org/freedesktop/login1" #define DBUS_LOGIND_MANAGER_INTERFACE "org.freedesktop.login1.Manager" #define DBUS_LOGIND_SESSION_INTERFACE "org.freedesktop.login1.Session" static void enable_timeouts(void); static void disable_timeouts(void); static int sleep_lock_fd = -1; static struct sd_bus *bus = NULL; static char *session_name = NULL; static void acquire_inhibitor_lock(const char *type, const char *mode, int *fd) { sd_bus_message *msg = NULL; sd_bus_error error = SD_BUS_ERROR_NULL; char why[35]; sprintf(why, "Swayidle is preventing %s", type); int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, DBUS_LOGIND_MANAGER_INTERFACE, "Inhibit", &error, &msg, "ssss", type, "swayidle", why, mode); if (ret < 0) { swayidle_log(LOG_ERROR, "Failed to send %s inhibit signal: %s", type, error.message); goto cleanup; } ret = sd_bus_message_read(msg, "h", fd); if (ret < 0) { errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to parse D-Bus response for %s inhibit", type); goto cleanup; } *fd = fcntl(*fd, F_DUPFD_CLOEXEC, 3); if (*fd >= 0) { swayidle_log(LOG_DEBUG, "Got %s lock: %d", type, *fd); } else { swayidle_log_errno(LOG_ERROR, "Failed to copy %s lock fd", type); } cleanup: sd_bus_error_free(&error); sd_bus_message_unref(msg); } static void release_inhibitor_lock(int fd) { if (fd >= 0) { swayidle_log(LOG_DEBUG, "Releasing inhibitor lock %d", fd); close(fd); } } static void set_idle_hint(bool hint) { swayidle_log(LOG_DEBUG, "SetIdleHint %d", hint); sd_bus_message *msg = NULL; sd_bus_error error = SD_BUS_ERROR_NULL; int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, session_name, DBUS_LOGIND_SESSION_INTERFACE, "SetIdleHint", &error, &msg, "b", hint); if (ret < 0) { swayidle_log(LOG_ERROR, "Failed to send SetIdleHint signal: %s", error.message); } sd_bus_error_free(&error); sd_bus_message_unref(msg); } static bool get_logind_idle_inhibit(void) { const char *locks; bool res; sd_bus_message *reply = NULL; int ret = sd_bus_get_property(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, DBUS_LOGIND_MANAGER_INTERFACE, "BlockInhibited", NULL, &reply, "s"); if (ret < 0) { goto error; } ret = sd_bus_message_read_basic(reply, 's', &locks); if (ret < 0) { goto error; } res = strstr(locks, "idle") != NULL; sd_bus_message_unref(reply); return res; error: sd_bus_message_unref(reply); errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to parse get BlockInhibited property"); return false; } static int prepare_for_sleep(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { /* "b" apparently reads into an int, not a bool */ int going_down = 1; int ret = sd_bus_message_read(msg, "b", &going_down); if (ret < 0) { errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to parse D-Bus response for Inhibit: %s"); } swayidle_log(LOG_DEBUG, "PrepareForSleep signal received %d", going_down); if (!going_down) { acquire_inhibitor_lock("sleep", "delay", &sleep_lock_fd); if (state.after_resume_cmd) { cmd_exec(state.after_resume_cmd); } if (state.logind_idlehint) { set_idle_hint(false); } return 0; } if (state.before_sleep_cmd) { cmd_exec(state.before_sleep_cmd); } swayidle_log(LOG_DEBUG, "Prepare for sleep done"); release_inhibitor_lock(sleep_lock_fd); return 0; } static int handle_lock(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { swayidle_log(LOG_DEBUG, "Lock signal received"); if (state.logind_lock_cmd) { cmd_exec(state.logind_lock_cmd); } swayidle_log(LOG_DEBUG, "Lock command done"); return 0; } static int handle_unlock(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { swayidle_log(LOG_DEBUG, "Unlock signal received"); if (state.logind_idlehint) { set_idle_hint(false); } if (state.logind_unlock_cmd) { cmd_exec(state.logind_unlock_cmd); } swayidle_log(LOG_DEBUG, "Unlock command done"); return 0; } static int handle_property_changed(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *name; swayidle_log(LOG_DEBUG, "PropertiesChanged signal received"); int ret = sd_bus_message_read_basic(msg, 's', &name); if (ret < 0) { goto error; } if (!strcmp(name, DBUS_LOGIND_MANAGER_INTERFACE)) { swayidle_log(LOG_DEBUG, "Got PropertyChanged: %s", name); ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); if (ret < 0) { goto error; } const char *prop; while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { ret = sd_bus_message_read_basic(msg, 's', &prop); if (ret < 0) { goto error; } if (!strcmp(prop, "BlockInhibited")) { if (get_logind_idle_inhibit()) { swayidle_log(LOG_DEBUG, "Logind idle inhibitor found"); disable_timeouts(); } else { swayidle_log(LOG_DEBUG, "Logind idle inhibitor not found"); enable_timeouts(); } return 0; } else { ret = sd_bus_message_skip(msg, "v"); if (ret < 0) { goto error; } } ret = sd_bus_message_exit_container(msg); if (ret < 0) { goto error; } } } if (ret < 0) { goto error; } return 0; error: errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to parse D-Bus response for PropertyChanged"); return 0; } static int dbus_event(int fd, uint32_t mask, void *data) { sd_bus *bus = data; if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { sway_terminate(0); } int count = 0; if (mask & WL_EVENT_READABLE) { count = sd_bus_process(bus, NULL); } if (mask & WL_EVENT_WRITABLE) { sd_bus_flush(bus); } if (mask == 0) { sd_bus_flush(bus); } if (count < 0) { swayidle_log_errno(LOG_ERROR, "sd_bus_process failed, exiting"); sway_terminate(0); } return count; } static void set_session(void) { sd_bus_message *msg = NULL; sd_bus_error error = SD_BUS_ERROR_NULL; const char *session_name_tmp; int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, DBUS_LOGIND_MANAGER_INTERFACE, "GetSession", &error, &msg, "s", "auto"); if (ret < 0) { swayidle_log(LOG_DEBUG, "GetSession failed: %s", error.message); sd_bus_error_free(&error); sd_bus_message_unref(msg); ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, DBUS_LOGIND_MANAGER_INTERFACE, "GetSessionByPID", &error, &msg, "u", getpid()); if (ret < 0) { swayidle_log(LOG_DEBUG, "GetSessionByPID failed: %s", error.message); swayidle_log(LOG_ERROR, "Failed to find session"); goto cleanup; } } ret = sd_bus_message_read(msg, "o", &session_name_tmp); if (ret < 0) { swayidle_log(LOG_ERROR, "Failed to read session name"); goto cleanup; } session_name = strdup(session_name_tmp); swayidle_log(LOG_DEBUG, "Using session: %s", session_name); cleanup: sd_bus_error_free(&error); sd_bus_message_unref(msg); } static void connect_to_bus(void) { int ret = sd_bus_default_system(&bus); if (ret < 0) { errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to open D-Bus connection"); return; } struct wl_event_source *source = wl_event_loop_add_fd(state.event_loop, sd_bus_get_fd(bus), WL_EVENT_READABLE, dbus_event, bus); wl_event_source_check(source); set_session(); } static void setup_sleep_listener(void) { int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, DBUS_LOGIND_MANAGER_INTERFACE, "PrepareForSleep", prepare_for_sleep, NULL); if (ret < 0) { errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : sleep"); return; } acquire_inhibitor_lock("sleep", "delay", &sleep_lock_fd); } static void setup_lock_listener(void) { int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE, session_name, DBUS_LOGIND_SESSION_INTERFACE, "Lock", handle_lock, NULL); if (ret < 0) { errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : lock"); return; } } static void setup_unlock_listener(void) { int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE, session_name, DBUS_LOGIND_SESSION_INTERFACE, "Unlock", handle_unlock, NULL); if (ret < 0) { errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : unlock"); return; } } static void setup_property_changed_listener(void) { int ret = sd_bus_match_signal(bus, NULL, NULL, DBUS_LOGIND_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", handle_property_changed, NULL); if (ret < 0) { errno = -ret; swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : property changed"); return; } } #endif static void seat_handle_capabilities(void *data, struct wl_seat *seat, uint32_t capabilities) { struct seat *self = data; self->capabilities = capabilities; } static void seat_handle_name(void *data, struct wl_seat *seat, const char *name) { struct seat *self = data; self->name = strdup(name); } static const struct wl_seat_listener wl_seat_listener = { .name = seat_handle_name, .capabilities = seat_handle_capabilities, }; static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0) { idle_manager = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1); } else if (strcmp(interface, wl_seat_interface.name) == 0) { struct seat *s = calloc(1, sizeof(struct seat)); s->proxy = wl_registry_bind(registry, name, &wl_seat_interface, 2); wl_seat_add_listener(s->proxy, &wl_seat_listener, s); wl_list_insert(&state.seats, &s->link); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { // Who cares } static const struct wl_registry_listener registry_listener = { .global = handle_global, .global_remove = handle_global_remove, }; static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener; static void destroy_cmd_timer(struct swayidle_timeout_cmd *cmd) { if (cmd->idle_timer != NULL) { org_kde_kwin_idle_timeout_destroy(cmd->idle_timer); cmd->idle_timer = NULL; } } static void register_timeout(struct swayidle_timeout_cmd *cmd, int timeout) { destroy_cmd_timer(cmd); if (timeout < 0) { swayidle_log(LOG_DEBUG, "Not registering idle timeout"); return; } swayidle_log(LOG_DEBUG, "Register with timeout: %d", timeout); cmd->idle_timer = org_kde_kwin_idle_get_idle_timeout(idle_manager, seat, timeout); org_kde_kwin_idle_timeout_add_listener(cmd->idle_timer, &idle_timer_listener, cmd); cmd->registered_timeout = timeout; } static void enable_timeouts(void) { if (state.timeouts_enabled) { return; } #if HAVE_SYSTEMD || HAVE_ELOGIND if (get_logind_idle_inhibit()) { swayidle_log(LOG_INFO, "Not enabling timeouts: idle inhibitor found"); return; } #endif swayidle_log(LOG_DEBUG, "Enable idle timeouts"); state.timeouts_enabled = true; struct swayidle_timeout_cmd *cmd; wl_list_for_each(cmd, &state.timeout_cmds, link) { register_timeout(cmd, cmd->timeout); } } #if HAVE_SYSTEMD || HAVE_ELOGIND static void disable_timeouts(void) { if (!state.timeouts_enabled) { return; } swayidle_log(LOG_DEBUG, "Disable idle timeouts"); state.timeouts_enabled = false; struct swayidle_timeout_cmd *cmd; wl_list_for_each(cmd, &state.timeout_cmds, link) { destroy_cmd_timer(cmd); } if (state.logind_idlehint) { set_idle_hint(false); } } #endif static void handle_idle(void *data, struct org_kde_kwin_idle_timeout *timer) { struct swayidle_timeout_cmd *cmd = data; cmd->resume_pending = true; swayidle_log(LOG_DEBUG, "idle state"); #if HAVE_SYSTEMD || HAVE_ELOGIND if (cmd->idlehint) { set_idle_hint(true); } else #endif if (cmd->idle_cmd) { cmd_exec(cmd->idle_cmd); } } static void handle_resume(void *data, struct org_kde_kwin_idle_timeout *timer) { struct swayidle_timeout_cmd *cmd = data; cmd->resume_pending = false; swayidle_log(LOG_DEBUG, "active state"); if (cmd->registered_timeout != cmd->timeout) { register_timeout(cmd, cmd->timeout); } #if HAVE_SYSTEMD || HAVE_ELOGIND if (cmd->idlehint) { set_idle_hint(false); } else #endif if (cmd->resume_cmd) { cmd_exec(cmd->resume_cmd); } } static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener = { .idle = handle_idle, .resumed = handle_resume, }; static char *parse_command(int argc, char **argv) { if (argc < 1) { swayidle_log(LOG_ERROR, "Missing command"); return NULL; } swayidle_log(LOG_DEBUG, "Command: %s", argv[0]); return strdup(argv[0]); } static struct swayidle_timeout_cmd *build_timeout_cmd(int argc, char **argv) { errno = 0; char *endptr; int seconds = strtoul(argv[1], &endptr, 10); if (errno != 0 || *endptr != '\0') { swayidle_log(LOG_ERROR, "Invalid %s parameter '%s', it should be a " "numeric value representing seconds", argv[0], argv[1]); exit(-1); } struct swayidle_timeout_cmd *cmd = calloc(1, sizeof(struct swayidle_timeout_cmd)); cmd->idlehint = false; cmd->resume_pending = false; if (seconds > 0) { cmd->timeout = seconds * 1000; } else { cmd->timeout = -1; } return cmd; } static int parse_timeout(int argc, char **argv) { if (argc < 3) { swayidle_log(LOG_ERROR, "Too few parameters to timeout command. " "Usage: timeout "); exit(-1); } struct swayidle_timeout_cmd *cmd = build_timeout_cmd(argc, argv); swayidle_log(LOG_DEBUG, "Register idle timeout at %d ms", cmd->timeout); swayidle_log(LOG_DEBUG, "Setup idle"); cmd->idle_cmd = parse_command(argc - 2, &argv[2]); int result = 3; if (argc >= 5 && !strcmp("resume", argv[3])) { swayidle_log(LOG_DEBUG, "Setup resume"); cmd->resume_cmd = parse_command(argc - 4, &argv[4]); result = 5; } wl_list_insert(&state.timeout_cmds, &cmd->link); return result; } static int parse_sleep(int argc, char **argv) { #if !HAVE_SYSTEMD && !HAVE_ELOGIND swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled " "with neither systemd nor elogind support.", "before-sleep"); exit(-1); #endif if (argc < 2) { swayidle_log(LOG_ERROR, "Too few parameters to before-sleep command. " "Usage: before-sleep "); exit(-1); } state.before_sleep_cmd = parse_command(argc - 1, &argv[1]); if (state.before_sleep_cmd) { swayidle_log(LOG_DEBUG, "Setup sleep lock: %s", state.before_sleep_cmd); } return 2; } static int parse_resume(int argc, char **argv) { #if !HAVE_SYSTEMD && !HAVE_ELOGIND swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled " "with neither systemd nor elogind support.", "after-resume"); exit(-1); #endif if (argc < 2) { swayidle_log(LOG_ERROR, "Too few parameters to after-resume command. " "Usage: after-resume "); exit(-1); } state.after_resume_cmd = parse_command(argc - 1, &argv[1]); if (state.after_resume_cmd) { swayidle_log(LOG_DEBUG, "Setup resume hook: %s", state.after_resume_cmd); } return 2; } static int parse_lock(int argc, char **argv) { #if !HAVE_SYSTEMD && !HAVE_ELOGIND swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled" " with neither systemd nor elogind support.", "lock"); exit(-1); #endif if (argc < 2) { swayidle_log(LOG_ERROR, "Too few parameters to lock command. " "Usage: lock "); exit(-1); } state.logind_lock_cmd = parse_command(argc - 1, &argv[1]); if (state.logind_lock_cmd) { swayidle_log(LOG_DEBUG, "Setup lock hook: %s", state.logind_lock_cmd); } return 2; } static int parse_unlock(int argc, char **argv) { #if !HAVE_SYSTEMD && !HAVE_ELOGIND swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled" " with neither systemd nor elogind support.", "unlock"); exit(-1); #endif if (argc < 2) { swayidle_log(LOG_ERROR, "Too few parameters to unlock command. " "Usage: unlock "); exit(-1); } state.logind_unlock_cmd = parse_command(argc - 1, &argv[1]); if (state.logind_unlock_cmd) { swayidle_log(LOG_DEBUG, "Setup unlock hook: %s", state.logind_unlock_cmd); } return 2; } static int parse_idlehint(int argc, char **argv) { #if !HAVE_SYSTEMD && !HAVE_ELOGIND swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled" " with neither systemd nor elogind support.", "idlehint"); exit(-1); #endif if (state.logind_idlehint) { swayidle_log(LOG_ERROR, "Cannot add multiple idlehint events"); exit(-1); } if (argc < 2) { swayidle_log(LOG_ERROR, "Too few parameters to idlehint command. " "Usage: idlehint "); exit(-1); } struct swayidle_timeout_cmd *cmd = build_timeout_cmd(argc, argv); cmd->idlehint = true; swayidle_log(LOG_DEBUG, "Register idlehint timeout at %d ms", cmd->timeout); wl_list_insert(&state.timeout_cmds, &cmd->link); state.logind_idlehint = true; return 2; } 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': verbosity = LOG_DEBUG; break; case 'w': state.wait = true; break; case 'S': state.seat_name = strdup(optarg); break; case 'h': case '?': printf("Usage: %s [OPTIONS]\n", argv[0]); printf(" -h\tthis help menu\n"); printf(" -C\tpath to config file\n"); printf(" -d\tdebug\n"); printf(" -w\twait for command to finish\n"); printf(" -S\tpick the seat to work with\n"); return 1; default: return 1; } } int i = optind; while (i < argc) { if (!strcmp("timeout", argv[i])) { swayidle_log(LOG_DEBUG, "Got timeout"); i += parse_timeout(argc - i, &argv[i]); } else if (!strcmp("before-sleep", argv[i])) { swayidle_log(LOG_DEBUG, "Got before-sleep"); i += parse_sleep(argc - i, &argv[i]); } else if (!strcmp("after-resume", argv[i])) { swayidle_log(LOG_DEBUG, "Got after-resume"); i += parse_resume(argc - i, &argv[i]); } else if (!strcmp("lock", argv[i])) { swayidle_log(LOG_DEBUG, "Got lock"); i += parse_lock(argc - i, &argv[i]); } else if (!strcmp("unlock", argv[i])) { swayidle_log(LOG_DEBUG, "Got unlock"); i += parse_unlock(argc - i, &argv[i]); } else if (!strcmp("idlehint", argv[i])) { swayidle_log(LOG_DEBUG, "Got idlehint"); i += parse_idlehint(argc - i, &argv[i]); } else { swayidle_log(LOG_ERROR, "Unsupported command '%s'", argv[i]); return 1; } } return 0; } static int handle_signal(int sig, void *data) { struct swayidle_timeout_cmd *cmd; switch (sig) { case SIGINT: case SIGTERM: swayidle_log(LOG_DEBUG, "Got SIGTERM"); wl_list_for_each(cmd, &state.timeout_cmds, link) { if (cmd->resume_pending) { handle_resume(cmd, cmd->idle_timer); } } sway_terminate(0); return 0; case SIGUSR1: swayidle_log(LOG_DEBUG, "Got SIGUSR1"); wl_list_for_each(cmd, &state.timeout_cmds, link) { register_timeout(cmd, 0); } return 1; } abort(); // not reached } static int display_event(int fd, uint32_t mask, void *data) { if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { sway_terminate(0); } int count = 0; if (mask & WL_EVENT_READABLE) { count = wl_display_dispatch(state.display); } if (mask & WL_EVENT_WRITABLE) { wl_display_flush(state.display); } if (mask == 0) { count = wl_display_dispatch_pending(state.display); wl_display_flush(state.display); } if (count < 0) { swayidle_log_errno(LOG_ERROR, "wl_display_dispatch failed, exiting"); sway_terminate(0); } return count; } static char *get_config_path(void) { static char *config_paths[3] = { "$XDG_CONFIG_HOME/swayidle/config", "$HOME/.swayidle/config", SYSCONFDIR "/swayidle/config", }; char *config_home = getenv("XDG_CONFIG_HOME"); if (!config_home || config_home[0] == '\n') { config_paths[0] = "$HOME/.config/swayidle/config"; } wordexp_t p; char *path; for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { if (wordexp(config_paths[i], &p, 0) == 0) { 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 *config_path) { FILE *f = fopen(config_path, "r"); if (!f) { return -ENOENT; } size_t lineno = 0; char *line = NULL; size_t n = 0; ssize_t nread; while ((nread = getline(&line, &n, f)) != -1) { lineno++; if (line[nread-1] == '\n') { line[nread-1] = '\0'; } if (strlen(line) == 0 || line[0] == '#') { continue; } size_t i = 0; while (line[i] != '\0' && line[i] != ' ') { i++; } wordexp_t p; wordexp(line, &p, 0); if (strncmp("timeout", line, i) == 0) { parse_timeout(p.we_wordc, p.we_wordv); } else if (strncmp("before-sleep", line, i) == 0) { parse_sleep(p.we_wordc, p.we_wordv); } else if (strncmp("after-resume", line, i) == 0) { parse_resume(p.we_wordc, p.we_wordv); } else if (strncmp("lock", line, i) == 0) { parse_lock(p.we_wordc, p.we_wordv); } else if (strncmp("unlock", line, i) == 0) { parse_unlock(p.we_wordc, p.we_wordv); } else if (strncmp("idlehint", line, i) == 0) { parse_idlehint(p.we_wordc, p.we_wordv); } else { line[i] = 0; swayidle_log(LOG_ERROR, "Unexpected keyword \"%s\" in line %lu", line, lineno); free(line); return -EINVAL; } wordfree(&p); } free(line); fclose(f); return 0; } int main(int argc, char *argv[]) { swayidle_init(); char *config_path = NULL; if (parse_args(argc, argv, &config_path) != 0) { swayidle_finish(); free(config_path); return -1; } if (!config_path) { config_path = get_config_path(); } int config_load = load_config(config_path); if (config_load == -ENOENT) { swayidle_log(LOG_DEBUG, "No config file found."); } else if (config_load == -EINVAL) { swayidle_log(LOG_ERROR, "Config file %s has errors, exiting.", config_path); exit(-1); } else { swayidle_log(LOG_DEBUG, "Loaded config at %s", config_path); } free(config_path); state.event_loop = wl_event_loop_create(); wl_event_loop_add_signal(state.event_loop, SIGINT, handle_signal, NULL); wl_event_loop_add_signal(state.event_loop, SIGTERM, handle_signal, NULL); wl_event_loop_add_signal(state.event_loop, SIGUSR1, handle_signal, NULL); state.display = wl_display_connect(NULL); if (state.display == NULL) { swayidle_log(LOG_ERROR, "Unable to connect to the compositor. " "If your compositor is running, check or set the " "WAYLAND_DISPLAY environment variable."); swayidle_finish(); return -3; } struct wl_registry *registry = wl_display_get_registry(state.display); wl_registry_add_listener(registry, ®istry_listener, NULL); wl_display_roundtrip(state.display); wl_display_roundtrip(state.display); struct seat *seat_i; wl_list_for_each(seat_i, &state.seats, link) { if (state.seat_name == NULL || strcmp(seat_i->name, state.seat_name) == 0) { seat = seat_i->proxy; } } if (idle_manager == NULL) { swayidle_log(LOG_ERROR, "Display doesn't support idle protocol"); swayidle_finish(); return -4; } if (seat == NULL) { if (state.seat_name != NULL) { swayidle_log(LOG_ERROR, "Seat %s not found", state.seat_name); } else { swayidle_log(LOG_ERROR, "No seat found"); } swayidle_finish(); return -5; } bool should_run = !wl_list_empty(&state.timeout_cmds); #if HAVE_SYSTEMD || HAVE_ELOGIND connect_to_bus(); setup_property_changed_listener(); if (state.before_sleep_cmd || state.after_resume_cmd) { should_run = true; setup_sleep_listener(); } if (state.logind_lock_cmd) { should_run = true; setup_lock_listener(); } if (state.logind_unlock_cmd) { should_run = true; setup_unlock_listener(); } if (state.logind_idlehint) { set_idle_hint(false); } #endif if (!should_run) { swayidle_log(LOG_INFO, "No command specified! Nothing to do, will exit"); sway_terminate(0); } enable_timeouts(); wl_display_roundtrip(state.display); struct wl_event_source *source = wl_event_loop_add_fd(state.event_loop, wl_display_get_fd(state.display), WL_EVENT_READABLE, display_event, NULL); wl_event_source_check(source); while (wl_event_loop_dispatch(state.event_loop, -1) != 1) { // This space intentionally left blank } sway_terminate(0); } swayidle-1.7/meson.build000066400000000000000000000071421405023615100153450ustar00rootroot00000000000000project( 'swayidle', 'c', version: '1.7', license: 'MIT', meson_version: '>=0.48.0', default_options: [ 'c_std=c11', 'warning_level=2', 'werror=true', ], ) add_project_arguments( [ '-Wno-unused-parameter', '-Wno-unused-result', '-Wundef', '-Wvla', ], language: 'c', ) sysconfdir = get_option('sysconfdir') prefix = get_option('prefix') add_project_arguments( '-DSYSCONFDIR="@0@"'.format(join_paths(prefix, sysconfdir)), language : 'c') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.14') wayland_server = dependency('wayland-server') bash_comp = dependency('bash-completion', required: false) fish_comp = dependency('fish', required: false) logind = dependency('lib' + get_option('logind-provider'), required: get_option('logind')) scdoc = find_program('scdoc', required: get_option('man-pages')) wayland_scanner = find_program('wayland-scanner') wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') if wayland_server.version().version_compare('>=1.14.91') code_type = 'private-code' else code_type = 'code' endif wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: [code_type, '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) client_protos_src = wayland_scanner_code.process('idle.xml') client_protos_headers = wayland_scanner_client.process('idle.xml') lib_client_protos = static_library( 'client_protos', [client_protos_src, client_protos_headers], dependencies: [wayland_client] ) # for the include directory client_protos = declare_dependency( link_with: lib_client_protos, sources: client_protos_headers, ) swayidle_deps = [ client_protos, wayland_client, wayland_server, ] conf_data = configuration_data() conf_data.set10('HAVE_SYSTEMD', false) conf_data.set10('HAVE_ELOGIND', false) if logind.found() swayidle_deps += logind conf_data.set10('HAVE_' + get_option('logind-provider').to_upper(), true) endif configure_file(output: 'config.h', configuration: conf_data) executable( 'swayidle', [ 'main.c', ], dependencies: swayidle_deps, install: true, ) if scdoc.found() sh = find_program('sh') mandir = get_option('mandir') man_files = [ 'swayidle.1.scd', ] foreach filename : man_files topic = filename.split('.')[-3].split('/')[-1] section = filename.split('.')[-2] output = '@0@.@1@'.format(topic, section) custom_target( output, input: filename, output: output, command: [ sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.path(), output) ], install: true, install_dir: '@0@/man@1@'.format(mandir, section) ) endforeach endif datadir = get_option('datadir') if get_option('zsh-completions') zsh_files = files( 'completions/zsh/_swayidle', ) zsh_install_dir = datadir + '/zsh/site-functions' install_data(zsh_files, install_dir: zsh_install_dir) endif if get_option('bash-completions') bash_files = files( 'completions/bash/swayidle', ) if bash_comp.found() bash_install_dir = bash_comp.get_pkgconfig_variable('completionsdir') else bash_install_dir = datadir + '/bash-completion/completions' endif install_data(bash_files, install_dir: bash_install_dir) endif if get_option('fish-completions') fish_files = files( 'completions/fish/swayidle.fish', ) if fish_comp.found() fish_install_dir = fish_comp.get_pkgconfig_variable('completionsdir') else fish_install_dir = datadir + '/fish/vendor_completions.d' endif install_data(fish_files, install_dir: fish_install_dir) endif swayidle-1.7/meson_options.txt000066400000000000000000000012061405023615100166330ustar00rootroot00000000000000option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('logind-provider', type: 'combo', choices: ['systemd', 'elogind'], value: 'systemd', description: 'Provider of logind support library') option('zsh-completions', type: 'boolean', value: true, description: 'Install zsh shell completions.') option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') swayidle-1.7/swayidle.1.scd000066400000000000000000000064621405023615100156620ustar00rootroot00000000000000swayidle(1) # NAME swayidle - Idle manager for Wayland # SYNOPSIS *swayidle* [options] [events...] # OPTIONS *-C* The config file to use. By default, the following paths are checked in the following order: $XDG_CONFIG_HOME/swayidle/config, $HOME/swayidle/config Config file entries are events as described in the EVENTS section. Specifying events in the config and as arguments is not mutually exclusive. *-h* Show help message and quit. *-d* Enable debug output. *-w* Wait for command to finish executing before continuing, helpful for ensuring that a *before-sleep* command has finished before the system goes to sleep. Note: using this option causes swayidle to block until the command finishes. *-S* Specify which seat to use. By default, if no name is specified, an arbitrary seat will be picked instead. # DESCRIPTION swayidle listens for idle activity on your Wayland compositor and executes tasks on various idle-related events. You can specify any number of events at the command line and in the config file. # EVENTS *timeout* [resume ] Execute _timeout command_ if there is no activity for seconds. If you specify "resume ", _resume command_ will be run when there is activity again. *before-sleep* If built with systemd support, executes _command_ before systemd puts the computer to sleep. Note: this only delays sleeping up to the limit set in *logind.conf(5)* by the option InhibitDelayMaxSec. A command that has not finished by then will continue running after resuming from sleep. *after-resume* If built with systemd support, executes _command_ after logind signals that the computer resumed from sleep. *lock* If built with systemd support, executes _command_ when logind signals that the session should be locked *unlock* If built with systemd support, executes _command_ when logind signals that the session should be unlocked *idlehint* If built with systemd support, set IdleHint to indicate an idle logind/elogind session after seconds. Adding an idlehint event will also cause swayidle to call SetIdleHint(false) when run, on resume, unlock, etc. All commands are executed in a shell. # SIGNALS swayidle responds to the following signals: *SIGTERM, SIGINT* Run all pending resume commands. When finished swayidle will terminate. *SIGUSR1* Immediately enter idle state. # EXAMPLE ``` swayidle -w \\ timeout 300 'swaylock -f -c 000000' \\ timeout 600 'swaymsg "output * dpms off"' \\ resume 'swaymsg "output * dpms on"' \\ before-sleep 'swaylock -f -c 000000' ``` This will lock your screen after 300 seconds of inactivity, then turn off your displays after another 300 seconds, and turn your screens back on when resumed. It will also lock your screen before your computer goes to sleep. To make sure swayidle waits for swaylock to lock the screen before it releases the inhibition lock, the *-w* options is used in swayidle, and *-f* in swaylock. # AUTHORS Maintained by Drew DeVault , who is assisted by other open source contributors. For more information about swayidle development, see https://github.com/swaywm/swayidle. # SEE ALSO *sway*(5) *swaymsg*(1) *sway-input*(5) *sway-output*(5) *sway-bar*(5) *loginctl*(1)