summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBirger Schacht <birger@rantanplan.org>2019-07-22 20:58:30 +0100
committerBirger Schacht <birger@rantanplan.org>2019-07-22 20:58:30 +0100
commita2a28298e39aaf818eafee8db127eae9e7b05c58 (patch)
tree23b7916039a708f2aa951bb1e36872ad6aa35d68
parent7e23ffa16097c51d1e984ca365ec9a887d00aa30 (diff)
parent426338a39e5bc228e3b585a615a6b6f06e8f2d17 (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.yml16
-rw-r--r--.gitignore1
-rw-r--r--LICENSE19
-rw-r--r--README.md40
-rw-r--r--completions/bash/swayidle48
-rw-r--r--completions/fish/swayidle.fish3
-rw-r--r--completions/zsh/_swayidle22
-rw-r--r--idle.xml49
-rw-r--r--main.c659
-rw-r--r--meson.build142
-rw-r--r--meson_options.txt6
-rw-r--r--swayidle.1.scd88
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..433ad90
--- /dev/null
+++ b/LICENSE
@@ -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>
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..f3a7e52
--- /dev/null
+++ b/main.c
@@ -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, &registry_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)