diff options
author | Birger Schacht <birger@rantanplan.org> | 2019-07-22 20:58:30 +0100 |
---|---|---|
committer | Birger Schacht <birger@rantanplan.org> | 2019-07-22 20:58:30 +0100 |
commit | a2a28298e39aaf818eafee8db127eae9e7b05c58 (patch) | |
tree | 23b7916039a708f2aa951bb1e36872ad6aa35d68 | |
parent | 7e23ffa16097c51d1e984ca365ec9a887d00aa30 (diff) | |
parent | 426338a39e5bc228e3b585a615a6b6f06e8f2d17 (diff) |
Combine debian/ with upstream source for 1.5
using upstream from git tag 1.5
[dgit (9.2~bpo10+1) baredebian-merge 1.5-1 1.5]
-rw-r--r-- | .build.yml | 16 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | LICENSE | 19 | ||||
-rw-r--r-- | README.md | 40 | ||||
-rw-r--r-- | completions/bash/swayidle | 48 | ||||
-rw-r--r-- | completions/fish/swayidle.fish | 3 | ||||
-rw-r--r-- | completions/zsh/_swayidle | 22 | ||||
-rw-r--r-- | idle.xml | 49 | ||||
-rw-r--r-- | main.c | 659 | ||||
-rw-r--r-- | meson.build | 142 | ||||
-rw-r--r-- | meson_options.txt | 6 | ||||
-rw-r--r-- | swayidle.1.scd | 88 |
12 files changed, 1093 insertions, 0 deletions
diff --git a/.build.yml b/.build.yml new file mode 100644 index 0000000..a2a8387 --- /dev/null +++ b/.build.yml @@ -0,0 +1,16 @@ +image: 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 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build @@ -0,0 +1,19 @@ +Copyright (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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6de83f2 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# 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 diff --git a/completions/bash/swayidle b/completions/bash/swayidle new file mode 100644 index 0000000..a0cdc8b --- /dev/null +++ b/completions/bash/swayidle @@ -0,0 +1,48 @@ +# 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 + ) + + if [ "$prev" = timeout ]; then + # timeout <timeout> + return + elif [ "$prev2" = timeout ]; then + # timeout <timeout> <timeout command> + COMPREPLY=($(compgen -c -- "$cur")) + return + elif [ "$prev3" = timeout ]; then + # timeout <timeout> <timeout command> [resume <resume command>] + 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 diff --git a/completions/fish/swayidle.fish b/completions/fish/swayidle.fish new file mode 100644 index 0000000..7127992 --- /dev/null +++ b/completions/fish/swayidle.fish @@ -0,0 +1,3 @@ +# swayidle +complete -c swayidle -s h --description 'show help' +complete -c swayidle -s d --description 'debug' diff --git a/completions/zsh/_swayidle b/completions/zsh/_swayidle new file mode 100644 index 0000000..b419bc2 --- /dev/null +++ b/completions/zsh/_swayidle @@ -0,0 +1,22 @@ +#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 + _arguments -C \ + '(-h --help)'{-h,--help}'[Show help message and quit]' \ + '(-d)'-d'[Enable debug output]' + _describe -t "events" 'swayidle' events + +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 diff --git a/idle.xml b/idle.xml new file mode 100644 index 0000000..92d9989 --- /dev/null +++ b/idle.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="idle"> + <copyright><![CDATA[ + Copyright (C) 2015 Martin Gräßlin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ]]></copyright> + <interface name="org_kde_kwin_idle" version="1"> + <description summary="User idle time manager"> + 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.. + </description> + <request name="get_idle_timeout"> + <arg name="id" type="new_id" interface="org_kde_kwin_idle_timeout"/> + <arg name="seat" type="object" interface="wl_seat"/> + <arg name="timeout" type="uint" summary="The idle timeout in msec"/> + </request> + </interface> + <interface name="org_kde_kwin_idle_timeout" version="1"> + <request name="release" type="destructor"> + <description summary="release the timeout object"/> + </request> + <request name="simulate_user_activity"> + <description summary="Simulates user activity for this timeout, behaves just like real user activity on the seat"/> + </request> + <event name="idle"> + <description summary="Triggered when there has not been any user activity in the requested idle time interval"/> + </event> + <event name="resumed"> + <description summary="Triggered on the first user activity after an idle event"/> + </event> + </interface> +</protocol> @@ -0,0 +1,659 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> +#include <wayland-client-protocol.h> +#include <wayland-client.h> +#include <wayland-server.h> +#include <wayland-util.h> +#include "config.h" +#include "idle-client-protocol.h" +#if HAVE_SYSTEMD +#include <systemd/sd-bus.h> +#include <systemd/sd-login.h> +#elif HAVE_ELOGIND +#include <elogind/sd-bus.h> +#include <elogind/sd-login.h> +#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 * + char *before_sleep_cmd; + char *after_resume_cmd; + char *logind_lock_cmd; + char *logind_unlock_cmd; + 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; +}; + +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)); +} + +void sway_terminate(int exit_code) { + wl_display_disconnect(state.display); + wl_event_loop_destroy(state.event_loop); + 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 +static int lock_fd = -1; +static struct sd_bus *bus = NULL; +static char *session_name = NULL; + +static void acquire_sleep_lock(void) { + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + int ret = sd_bus_call_method(bus, "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", "Inhibit", + &error, &msg, "ssss", "sleep", "swayidle", + "Setup Up Lock Screen", "delay"); + if (ret < 0) { + swayidle_log(LOG_ERROR, + "Failed to send Inhibit signal: %s", error.message); + goto cleanup; + } + + ret = sd_bus_message_read(msg, "h", &lock_fd); + if (ret < 0) { + errno = -ret; + swayidle_log_errno(LOG_ERROR, + "Failed to parse D-Bus response for Inhibit"); + goto cleanup; + } + + // sd_bus_message_unref closes the file descriptor so we need + // to copy it beforehand + lock_fd = fcntl(lock_fd, F_DUPFD_CLOEXEC, 3); + if (lock_fd >= 0) { + swayidle_log(LOG_INFO, "Got sleep lock: %d", lock_fd); + } else { + swayidle_log_errno(LOG_ERROR, "Failed to copy sleep lock fd"); + } + +cleanup: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); +} + +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_sleep_lock(); + if (state.after_resume_cmd) { + cmd_exec(state.after_resume_cmd); + } + return 0; + } + + if (state.before_sleep_cmd) { + cmd_exec(state.before_sleep_cmd); + } + swayidle_log(LOG_DEBUG, "Prepare for sleep done"); + + swayidle_log(LOG_INFO, "Releasing sleep lock %d", lock_fd); + if (lock_fd >= 0) { + close(lock_fd); + } + lock_fd = -1; + + 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_unlock_cmd) { + cmd_exec(state.logind_unlock_cmd); + } + swayidle_log(LOG_DEBUG, "Unlock command done"); + + 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 connect_to_bus(void) { + int ret = sd_bus_default_system(&bus); + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + pid_t my_pid = getpid(); + const char *session_name_tmp; + 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); + ret = sd_bus_call_method(bus, "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", "GetSessionByPID", + &error, &msg, "u", my_pid); + if (ret < 0) { + swayidle_log(LOG_ERROR, + "Failed to find session name: %s", error.message); + goto cleanup; + } + + ret = sd_bus_message_read(msg, "o", &session_name_tmp); + if (ret < 0) { + swayidle_log(LOG_ERROR, + "Failed to read session name\n"); + goto cleanup; + } + session_name = strdup(session_name_tmp); +cleanup: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); +} + +static void setup_sleep_listener(void) { + int ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.login1", + "/org/freedesktop/login1", "org.freedesktop.login1.Manager", + "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_sleep_lock(); +} + +static void setup_lock_listener(void) { + int ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.login1", + session_name, "org.freedesktop.login1.Session", + "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, "org.freedesktop.login1", + session_name, "org.freedesktop.login1.Session", + "Unlock", handle_unlock, NULL); + if (ret < 0) { + errno = -ret; + swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : unlock"); + return; + } +} +#endif + +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) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); + } +} + +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 register_timeout(struct swayidle_timeout_cmd *cmd, + int timeout) { + if (cmd->idle_timer != NULL) { + org_kde_kwin_idle_timeout_destroy(cmd->idle_timer); + cmd->idle_timer = NULL; + } + 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 handle_idle(void *data, struct org_kde_kwin_idle_timeout *timer) { + struct swayidle_timeout_cmd *cmd = data; + swayidle_log(LOG_DEBUG, "idle state"); + 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; + swayidle_log(LOG_DEBUG, "active state"); + if (cmd->registered_timeout != cmd->timeout) { + register_timeout(cmd, cmd->timeout); + } + 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 int parse_timeout(int argc, char **argv) { + if (argc < 3) { + swayidle_log(LOG_ERROR, "Too few parameters to timeout command. " + "Usage: timeout <seconds> <command>"); + exit(-1); + } + errno = 0; + char *endptr; + int seconds = strtoul(argv[1], &endptr, 10); + if (errno != 0 || *endptr != '\0') { + swayidle_log(LOG_ERROR, "Invalid timeout parameter '%s', it should be a " + "numeric value representing seconds", optarg); + exit(-1); + } + + struct swayidle_timeout_cmd *cmd = + calloc(1, sizeof(struct swayidle_timeout_cmd)); + + if (seconds > 0) { + cmd->timeout = seconds * 1000; + } else { + cmd->timeout = -1; + } + + 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, "before-sleep not supported: swayidle was compiled " + "with neither systemd nor elogind support."); + exit(-1); +#endif + if (argc < 2) { + swayidle_log(LOG_ERROR, "Too few parameters to before-sleep command. " + "Usage: before-sleep <command>"); + 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, "after-resume not supported: swayidle was compiled " + "with neither systemd nor elogind support."); + exit(-1); +#endif + if (argc < 2) { + swayidle_log(LOG_ERROR, "Too few parameters to after-resume command. " + "Usage: after-resume <command>"); + 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, "lock not supported: swayidle was compiled" + " with neither systemd nor elogind support."); + exit(-1); +#endif + if (argc < 2) { + swayidle_log(LOG_ERROR, "Too few parameters to lock command. " + "Usage: lock <command>"); + 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, "unlock not supported: swayidle was compiled" + " with neither systemd nor elogind support."); + exit(-1); +#endif + if (argc < 2) { + swayidle_log(LOG_ERROR, "Too few parameters to unlock command. " + "Usage: unlock <command>"); + 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_args(int argc, char *argv[]) { + int c; + while ((c = getopt(argc, argv, "hdw")) != -1) { + switch (c) { + case 'd': + verbosity = LOG_DEBUG; + break; + case 'w': + state.wait = true; + break; + case 'h': + case '?': + printf("Usage: %s [OPTIONS]\n", argv[0]); + printf(" -h\tthis help menu\n"); + printf(" -d\tdebug\n"); + printf(" -w\twait for command to finish\n"); + return 1; + default: + return 1; + } + } + + wl_list_init(&state.timeout_cmds); + + 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 { + swayidle_log(LOG_ERROR, "Unsupported command '%s'", argv[i]); + return 1; + } + } + + return 0; +} + +static int handle_signal(int sig, void *data) { + switch (sig) { + case SIGINT: + case SIGTERM: + sway_terminate(0); + return 0; + case SIGUSR1: + swayidle_log(LOG_DEBUG, "Got SIGUSR1"); + struct swayidle_timeout_cmd *cmd; + wl_list_for_each(cmd, &state.timeout_cmds, link) { + register_timeout(cmd, 0); + } + return 1; + } + assert(false); // 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; +} + +int main(int argc, char *argv[]) { + if (parse_args(argc, argv) != 0) { + return -1; + } + + 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."); + 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); + + if (idle_manager == NULL) { + swayidle_log(LOG_ERROR, "Display doesn't support idle protocol"); + return -4; + } + if (seat == NULL) { + swayidle_log(LOG_ERROR, "Seat error"); + return -5; + } + + bool should_run = !wl_list_empty(&state.timeout_cmds); +#if HAVE_SYSTEMD || HAVE_ELOGIND + connect_to_bus(); + 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(); + } +#endif + if (!should_run) { + swayidle_log(LOG_INFO, "No command specified! Nothing to do, will exit"); + sway_terminate(0); + } + + struct swayidle_timeout_cmd *cmd; + wl_list_for_each(cmd, &state.timeout_cmds, link) { + register_timeout(cmd, cmd->timeout); + } + + 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); +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..71bb349 --- /dev/null +++ b/meson.build @@ -0,0 +1,142 @@ +project( + 'swayidle', + 'c', + version: '1.5', + 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', +) + +wayland_client = dependency('wayland-client') +wayland_protos = dependency('wayland-protocols', version: '>=1.14') +wayland_server = dependency('wayland-server') +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', + ) + bash_install_dir = datadir + '/bash-completion/completions' + + install_data(bash_files, install_dir: bash_install_dir) +endif + +if get_option('fish-completions') + fish_files = files( + 'completions/fish/swayidle.fish', + ) + fish_install_dir = datadir + '/fish/completions' + + install_data(fish_files, install_dir: fish_install_dir) +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..d40de9c --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,6 @@ +option('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.') diff --git a/swayidle.1.scd b/swayidle.1.scd new file mode 100644 index 0000000..74fe008 --- /dev/null +++ b/swayidle.1.scd @@ -0,0 +1,88 @@ +swayidle(1) + +# NAME + +swayidle - Idle manager for Wayland + +# SYNOPSIS + +*swayidle* [options] [events...] + +# OPTIONS + +*-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. + +# 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. + +Sending SIGUSR1 to swayidle will immediately enter idle state. + +# EVENTS + +*timeout* <timeout> <timeout command> [resume <resume command>] + Execute _timeout command_ if there is no activity for <timeout> seconds. + + If you specify "resume <resume command>", _resume command_ will be run when + there is activity again. + +*before-sleep* <command> + 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* <command> + If built with systemd support, executes _command_ after logind signals that the + computer resumed from sleep. + +*lock* <command> + If built with systemd support, executes _command_ when logind signals that the + session should be locked + +*unlock* <command> + If built with systemd support, executes _command_ when logind signals that the + session should be unlocked + +All commands are executed in a shell. + +# 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 <sir@cmpwn.com>, 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) |