├── .build.yml ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── completions ├── bash │ └── swayidle ├── fish │ └── swayidle.fish ├── meson.build └── zsh │ └── _swayidle ├── log.h ├── main.c ├── meson.build ├── meson_options.txt └── swayidle.1.scd /.build.yml: -------------------------------------------------------------------------------- 1 | image: alpine/edge 2 | packages: 3 | - meson 4 | - wayland-dev 5 | - wayland-protocols 6 | - scdoc 7 | sources: 8 | - https://github.com/swaywm/swayidle 9 | tasks: 10 | - setup: | 11 | cd swayidle 12 | meson build 13 | - build: | 14 | cd swayidle 15 | ninja -C build 16 | 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # For the full list of code style requirements, see CONTRIBUTING.md 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | 9 | [*.{c,h,cmake,txt}] 10 | indent_style = tab 11 | indent_size = 4 12 | 13 | [*.{xml,yml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [config] 18 | indent_style = space 19 | indent_size = 4 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2018 Drew DeVault 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swayidle 2 | 3 | This is sway's idle management daemon, swayidle. It is compatible with any 4 | Wayland compositor which implements the 5 | [ext-idle-notify](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/main/staging/ext-idle-notify) 6 | protocol. See the man page, [swayidle(1)](./swayidle.1.scd), for instructions 7 | on configuring swayidle. 8 | 9 | ## Release Signatures 10 | 11 | Releases are signed with [34FF9526](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) 12 | and published [on GitHub](https://github.com/swaywm/swayidle/releases). swayidle 13 | releases are managed independently of sway releases. 14 | 15 | ## Installation 16 | 17 | ### From Packages 18 | 19 | Swayidle is available in many distributions. Try installing the "swayidle" 20 | package for yours. 21 | 22 | ### Compiling from Source 23 | 24 | Install dependencies: 25 | 26 | * meson \* 27 | * wayland 28 | * wayland-protocols \* 29 | * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* 30 | * git \* 31 | 32 | _\* Compile-time dependency_ 33 | 34 | Run these commands: 35 | 36 | meson build/ 37 | ninja -C build/ 38 | sudo ninja -C build/ install 39 | -------------------------------------------------------------------------------- /completions/bash/swayidle: -------------------------------------------------------------------------------- 1 | # swaymsg(1) completion 2 | 3 | _swayidle() 4 | { 5 | local cur prev events short 6 | _get_comp_words_by_ref -n : cur prev 7 | local prev2=${COMP_WORDS[COMP_CWORD-2]} 8 | local prev3=${COMP_WORDS[COMP_CWORD-3]} 9 | 10 | events=( 11 | 'timeout' 12 | 'before-sleep' 13 | ) 14 | 15 | short=( 16 | -h 17 | -d 18 | -w 19 | ) 20 | 21 | if [ "$prev" = timeout ]; then 22 | # timeout 23 | return 24 | elif [ "$prev2" = timeout ]; then 25 | # timeout 26 | COMPREPLY=($(compgen -c -- "$cur")) 27 | return 28 | elif [ "$prev3" = timeout ]; then 29 | # timeout [resume ] 30 | COMPREPLY=(resume) 31 | # optional argument; no return here as user may skip 'resume' 32 | fi 33 | 34 | case "$prev" in 35 | resume) 36 | COMPREPLY=($(compgen -c -- "$cur")) 37 | return 38 | ;; 39 | before-sleep) 40 | COMPREPLY=($(compgen -c -- "$cur")) 41 | return 42 | ;; 43 | esac 44 | 45 | COMPREPLY+=($(compgen -W "${events[*]}" -- "$cur")) 46 | COMPREPLY+=($(compgen -W "${short[*]}" -- "$cur")) 47 | 48 | } && 49 | complete -F _swayidle swayidle 50 | -------------------------------------------------------------------------------- /completions/fish/swayidle.fish: -------------------------------------------------------------------------------- 1 | # swayidle 2 | set -l all_events timeout before-sleep after-resume lock unlock idlehint 3 | set -l cmd_events before-sleep after-resume lock unlock 4 | set -l time_events idlehint timeout 5 | 6 | complete -c swayidle --arguments "$all_events" 7 | complete -c swayidle --condition "__fish_seen_subcommand_from $cmd_events" --require-parameter 8 | complete -c swayidle --condition "__fish_seen_subcommand_from $time_events" --exclusive 9 | 10 | complete -c swayidle -s h --description 'show help' 11 | complete -c swayidle -s d --description 'debug' 12 | complete -c swayidle -s w --description 'wait for command to finish' 13 | -------------------------------------------------------------------------------- /completions/meson.build: -------------------------------------------------------------------------------- 1 | bash_comp = dependency('bash-completion', required: false) 2 | fish_comp = dependency('fish', required: false) 3 | 4 | datadir = get_option('datadir') 5 | 6 | if get_option('zsh-completions') 7 | zsh_files = files( 8 | 'zsh/_swayidle', 9 | ) 10 | zsh_install_dir = datadir + '/zsh/site-functions' 11 | 12 | install_data(zsh_files, install_dir: zsh_install_dir) 13 | endif 14 | 15 | if get_option('bash-completions') 16 | bash_files = files( 17 | 'bash/swayidle', 18 | ) 19 | if bash_comp.found() 20 | bash_install_dir = bash_comp.get_variable('completionsdir') 21 | else 22 | bash_install_dir = datadir + '/bash-completion/completions' 23 | endif 24 | 25 | install_data(bash_files, install_dir: bash_install_dir) 26 | endif 27 | 28 | if get_option('fish-completions') 29 | fish_files = files( 30 | 'fish/swayidle.fish', 31 | ) 32 | if fish_comp.found() 33 | fish_install_dir = fish_comp.get_variable('completionsdir') 34 | else 35 | fish_install_dir = datadir + '/fish/vendor_completions.d' 36 | endif 37 | 38 | install_data(fish_files, install_dir: fish_install_dir) 39 | endif 40 | -------------------------------------------------------------------------------- /completions/zsh/_swayidle: -------------------------------------------------------------------------------- 1 | #compdef swayidle 2 | # 3 | # Completion script for swayidle 4 | # 5 | 6 | local events=('timeout:Execute timeout command if there is no activity for timeout seconds' 7 | 'before-sleep:Execute before-sleep command before sleep') 8 | local resume=('resume:Execute command when there is activity again') 9 | 10 | if (($#words <= 2)); then 11 | _describe -t "events" 'swayidle' events 12 | _arguments -C \ 13 | '(-h --help)'{-h,--help}'[Show help message and quit]' \ 14 | '(-d)'-d'[Enable debug output]' \ 15 | '(-w)'-w'[Wait for command to finish executing before continuing]' 16 | 17 | elif [[ "$words[-3]" == before-sleep || "$words[-3]" == resume ]]; then 18 | _describe -t "events" 'swayidle' events 19 | 20 | elif [[ "$words[-4]" == timeout ]]; then 21 | _describe -t "events" 'swayidle' events 22 | _describe -t "resume" 'swayidle' resume 23 | fi 24 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | #ifndef _SWAYIDLE_LOG_H 2 | #define _SWAYIDLE_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum log_importance { 9 | LOG_SILENT = 0, 10 | LOG_ERROR = 1, 11 | LOG_INFO = 2, 12 | LOG_DEBUG = 3, 13 | LOG_IMPORTANCE_LAST, 14 | }; 15 | 16 | void swayidle_log_init(enum log_importance verbosity); 17 | 18 | #ifdef __GNUC__ 19 | #define _ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) 20 | #else 21 | #define _ATTRIB_PRINTF(start, end) 22 | #endif 23 | 24 | void _swayidle_log(enum log_importance verbosity, const char *format, ...) 25 | _ATTRIB_PRINTF(2, 3); 26 | 27 | #define swayidle_log(verb, fmt, ...) \ 28 | _swayidle_log(verb, "[Line %d] " fmt, __LINE__, ##__VA_ARGS__) 29 | 30 | #define swayidle_log_errno(verb, fmt, ...) \ 31 | swayidle_log(verb, fmt ": %s", ##__VA_ARGS__, strerror(errno)) 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "config.h" 17 | #include "ext-idle-notify-v1-client-protocol.h" 18 | #include "log.h" 19 | #if HAVE_SYSTEMD 20 | #include 21 | #include 22 | #elif HAVE_ELOGIND 23 | #include 24 | #include 25 | #endif 26 | 27 | static struct ext_idle_notifier_v1 *idle_notifier = NULL; 28 | static struct wl_seat *seat = NULL; 29 | 30 | struct swayidle_state { 31 | struct wl_display *display; 32 | struct wl_event_loop *event_loop; 33 | struct wl_list timeout_cmds; // struct swayidle_timeout_cmd * 34 | struct wl_list seats; 35 | char *seat_name; 36 | char *before_sleep_cmd; 37 | char *after_resume_cmd; 38 | char *logind_lock_cmd; 39 | char *logind_unlock_cmd; 40 | bool logind_idlehint; 41 | bool timeouts_enabled; 42 | bool wait; 43 | } state; 44 | 45 | struct swayidle_timeout_cmd { 46 | struct wl_list link; 47 | int timeout, registered_timeout; 48 | struct ext_idle_notification_v1 *idle_notification; 49 | char *idle_cmd; 50 | char *resume_cmd; 51 | bool idlehint; 52 | bool resume_pending; 53 | }; 54 | 55 | struct seat { 56 | struct wl_list link; 57 | struct wl_seat *proxy; 58 | 59 | char *name; 60 | uint32_t capabilities; 61 | }; 62 | 63 | static const char *verbosity_colors[] = { 64 | [LOG_SILENT] = "", 65 | [LOG_ERROR ] = "\x1B[1;31m", 66 | [LOG_INFO ] = "\x1B[1;34m", 67 | [LOG_DEBUG ] = "\x1B[1;30m", 68 | }; 69 | 70 | static enum log_importance log_importance = LOG_INFO; 71 | 72 | void swayidle_log_init(enum log_importance verbosity) { 73 | if (verbosity < LOG_IMPORTANCE_LAST) { 74 | log_importance = verbosity; 75 | } 76 | } 77 | 78 | void _swayidle_log(enum log_importance verbosity, const char *fmt, ...) { 79 | if (verbosity > log_importance) { 80 | return; 81 | } 82 | 83 | va_list args; 84 | va_start(args, fmt); 85 | 86 | // prefix the time to the log message 87 | struct tm result; 88 | time_t t = time(NULL); 89 | struct tm *tm_info = localtime_r(&t, &result); 90 | char buffer[26]; 91 | 92 | // generate time prefix 93 | strftime(buffer, sizeof(buffer), "%F %T - ", tm_info); 94 | fprintf(stderr, "%s", buffer); 95 | 96 | unsigned c = (verbosity < LOG_IMPORTANCE_LAST) 97 | ? verbosity : LOG_IMPORTANCE_LAST - 1; 98 | 99 | if (isatty(STDERR_FILENO)) { 100 | fprintf(stderr, "%s", verbosity_colors[c]); 101 | } 102 | 103 | vfprintf(stderr, fmt, args); 104 | 105 | if (isatty(STDERR_FILENO)) { 106 | fprintf(stderr, "\x1B[0m"); 107 | } 108 | fprintf(stderr, "\n"); 109 | 110 | va_end(args); 111 | } 112 | 113 | static void swayidle_init() { 114 | memset(&state, 0, sizeof(state)); 115 | wl_list_init(&state.timeout_cmds); 116 | wl_list_init(&state.seats); 117 | } 118 | 119 | static void swayidle_finish() { 120 | 121 | struct swayidle_timeout_cmd *cmd; 122 | struct swayidle_timeout_cmd *tmp; 123 | wl_list_for_each_safe(cmd, tmp, &state.timeout_cmds, link) { 124 | wl_list_remove(&cmd->link); 125 | free(cmd->idle_cmd); 126 | free(cmd->resume_cmd); 127 | free(cmd); 128 | } 129 | 130 | free(state.after_resume_cmd); 131 | free(state.before_sleep_cmd); 132 | } 133 | 134 | void sway_terminate(int exit_code) { 135 | wl_display_disconnect(state.display); 136 | wl_event_loop_destroy(state.event_loop); 137 | swayidle_finish(); 138 | exit(exit_code); 139 | } 140 | 141 | static void cmd_exec(char *param) { 142 | swayidle_log(LOG_DEBUG, "Cmd exec %s", param); 143 | pid_t pid = fork(); 144 | if (pid == 0) { 145 | if (!state.wait) { 146 | pid = fork(); 147 | } 148 | if (pid == 0) { 149 | sigset_t set; 150 | sigemptyset(&set); 151 | sigprocmask(SIG_SETMASK, &set, NULL); 152 | signal(SIGINT, SIG_DFL); 153 | signal(SIGTERM, SIG_DFL); 154 | signal(SIGUSR1, SIG_DFL); 155 | 156 | char *const cmd[] = { "sh", "-c", param, NULL, }; 157 | execvp(cmd[0], cmd); 158 | swayidle_log_errno(LOG_ERROR, "execve failed!"); 159 | exit(1); 160 | } else if (pid < 0) { 161 | swayidle_log_errno(LOG_ERROR, "fork failed"); 162 | exit(1); 163 | } 164 | exit(0); 165 | } else if (pid < 0) { 166 | swayidle_log_errno(LOG_ERROR, "fork failed"); 167 | } else { 168 | swayidle_log(LOG_DEBUG, "Spawned process %s", param); 169 | if (state.wait) { 170 | swayidle_log(LOG_DEBUG, "Blocking until process exits"); 171 | } 172 | int status = 0; 173 | waitpid(pid, &status, 0); 174 | if (state.wait && WIFEXITED(status)) { 175 | swayidle_log(LOG_DEBUG, "Process exit status: %d", WEXITSTATUS(status)); 176 | } 177 | } 178 | } 179 | 180 | #if HAVE_SYSTEMD || HAVE_ELOGIND 181 | #define DBUS_LOGIND_SERVICE "org.freedesktop.login1" 182 | #define DBUS_LOGIND_PATH "/org/freedesktop/login1" 183 | #define DBUS_LOGIND_MANAGER_INTERFACE "org.freedesktop.login1.Manager" 184 | #define DBUS_LOGIND_SESSION_INTERFACE "org.freedesktop.login1.Session" 185 | 186 | static void enable_timeouts(void); 187 | static void disable_timeouts(void); 188 | 189 | static int sleep_lock_fd = -1; 190 | static struct sd_bus *bus = NULL; 191 | static char *session_name = NULL; 192 | 193 | static void acquire_inhibitor_lock(const char *type, const char *mode, 194 | int *fd) { 195 | sd_bus_message *msg = NULL; 196 | sd_bus_error error = SD_BUS_ERROR_NULL; 197 | char why[35]; 198 | 199 | sprintf(why, "Swayidle is preventing %s", type); 200 | int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, 201 | DBUS_LOGIND_MANAGER_INTERFACE, "Inhibit", &error, &msg, 202 | "ssss", type, "swayidle", why, mode); 203 | if (ret < 0) { 204 | swayidle_log(LOG_ERROR, 205 | "Failed to send %s inhibit signal: %s", type, error.message); 206 | goto cleanup; 207 | } 208 | 209 | ret = sd_bus_message_read(msg, "h", fd); 210 | if (ret < 0) { 211 | errno = -ret; 212 | swayidle_log_errno(LOG_ERROR, 213 | "Failed to parse D-Bus response for %s inhibit", type); 214 | goto cleanup; 215 | } 216 | 217 | *fd = fcntl(*fd, F_DUPFD_CLOEXEC, 3); 218 | if (*fd >= 0) { 219 | swayidle_log(LOG_DEBUG, "Got %s lock: %d", type, *fd); 220 | } else { 221 | swayidle_log_errno(LOG_ERROR, "Failed to copy %s lock fd", type); 222 | } 223 | 224 | cleanup: 225 | sd_bus_error_free(&error); 226 | sd_bus_message_unref(msg); 227 | } 228 | 229 | static void release_inhibitor_lock(int fd) { 230 | if (fd >= 0) { 231 | swayidle_log(LOG_DEBUG, "Releasing inhibitor lock %d", fd); 232 | close(fd); 233 | } 234 | } 235 | 236 | static void set_idle_hint(bool hint) { 237 | swayidle_log(LOG_DEBUG, "SetIdleHint %d", hint); 238 | sd_bus_message *msg = NULL; 239 | sd_bus_error error = SD_BUS_ERROR_NULL; 240 | int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, 241 | session_name, DBUS_LOGIND_SESSION_INTERFACE, "SetIdleHint", 242 | &error, &msg, "b", hint); 243 | if (ret < 0) { 244 | swayidle_log(LOG_ERROR, 245 | "Failed to send SetIdleHint signal: %s", error.message); 246 | } 247 | 248 | sd_bus_error_free(&error); 249 | sd_bus_message_unref(msg); 250 | } 251 | 252 | static bool get_logind_idle_inhibit(void) { 253 | const char *locks; 254 | bool res; 255 | 256 | sd_bus_message *reply = NULL; 257 | 258 | int ret = sd_bus_get_property(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, 259 | DBUS_LOGIND_MANAGER_INTERFACE, "BlockInhibited", NULL, &reply, "s"); 260 | if (ret < 0) { 261 | goto error; 262 | } 263 | 264 | ret = sd_bus_message_read_basic(reply, 's', &locks); 265 | if (ret < 0) { 266 | goto error; 267 | } 268 | 269 | res = strstr(locks, "idle") != NULL; 270 | sd_bus_message_unref(reply); 271 | 272 | return res; 273 | 274 | error: 275 | sd_bus_message_unref(reply); 276 | errno = -ret; 277 | swayidle_log_errno(LOG_ERROR, 278 | "Failed to parse get BlockInhibited property"); 279 | return false; 280 | } 281 | 282 | static int prepare_for_sleep(sd_bus_message *msg, void *userdata, 283 | sd_bus_error *ret_error) { 284 | /* "b" apparently reads into an int, not a bool */ 285 | int going_down = 1; 286 | int ret = sd_bus_message_read(msg, "b", &going_down); 287 | if (ret < 0) { 288 | errno = -ret; 289 | swayidle_log_errno(LOG_ERROR, 290 | "Failed to parse D-Bus response for Inhibit"); 291 | } 292 | swayidle_log(LOG_DEBUG, "PrepareForSleep signal received %d", going_down); 293 | if (!going_down) { 294 | acquire_inhibitor_lock("sleep", "delay", &sleep_lock_fd); 295 | if (state.after_resume_cmd) { 296 | cmd_exec(state.after_resume_cmd); 297 | } 298 | if (state.logind_idlehint) { 299 | set_idle_hint(false); 300 | } 301 | return 0; 302 | } 303 | 304 | if (state.before_sleep_cmd) { 305 | cmd_exec(state.before_sleep_cmd); 306 | } 307 | swayidle_log(LOG_DEBUG, "Prepare for sleep done"); 308 | 309 | release_inhibitor_lock(sleep_lock_fd); 310 | return 0; 311 | } 312 | 313 | static int handle_lock(sd_bus_message *msg, void *userdata, 314 | sd_bus_error *ret_error) { 315 | swayidle_log(LOG_DEBUG, "Lock signal received"); 316 | 317 | if (state.logind_lock_cmd) { 318 | cmd_exec(state.logind_lock_cmd); 319 | } 320 | swayidle_log(LOG_DEBUG, "Lock command done"); 321 | 322 | return 0; 323 | } 324 | 325 | static int handle_unlock(sd_bus_message *msg, void *userdata, 326 | sd_bus_error *ret_error) { 327 | swayidle_log(LOG_DEBUG, "Unlock signal received"); 328 | 329 | if (state.logind_idlehint) { 330 | set_idle_hint(false); 331 | } 332 | if (state.logind_unlock_cmd) { 333 | cmd_exec(state.logind_unlock_cmd); 334 | } 335 | swayidle_log(LOG_DEBUG, "Unlock command done"); 336 | 337 | return 0; 338 | } 339 | 340 | static int handle_property_changed(sd_bus_message *msg, void *userdata, 341 | sd_bus_error *ret_error) { 342 | const char *name; 343 | swayidle_log(LOG_DEBUG, "PropertiesChanged signal received"); 344 | 345 | int ret = sd_bus_message_read_basic(msg, 's', &name); 346 | if (ret < 0) { 347 | goto error; 348 | } 349 | 350 | if (!strcmp(name, DBUS_LOGIND_MANAGER_INTERFACE)) { 351 | swayidle_log(LOG_DEBUG, "Got PropertyChanged: %s", name); 352 | ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); 353 | if (ret < 0) { 354 | goto error; 355 | } 356 | 357 | const char *prop; 358 | while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { 359 | ret = sd_bus_message_read_basic(msg, 's', &prop); 360 | if (ret < 0) { 361 | goto error; 362 | } 363 | 364 | if (!strcmp(prop, "BlockInhibited")) { 365 | if (get_logind_idle_inhibit()) { 366 | swayidle_log(LOG_DEBUG, "Logind idle inhibitor found"); 367 | disable_timeouts(); 368 | } else { 369 | swayidle_log(LOG_DEBUG, "Logind idle inhibitor not found"); 370 | enable_timeouts(); 371 | } 372 | return 0; 373 | } else { 374 | ret = sd_bus_message_skip(msg, "v"); 375 | if (ret < 0) { 376 | goto error; 377 | } 378 | } 379 | 380 | ret = sd_bus_message_exit_container(msg); 381 | if (ret < 0) { 382 | goto error; 383 | } 384 | } 385 | } 386 | 387 | if (ret < 0) { 388 | goto error; 389 | } 390 | 391 | return 0; 392 | 393 | error: 394 | errno = -ret; 395 | swayidle_log_errno(LOG_ERROR, 396 | "Failed to parse D-Bus response for PropertyChanged"); 397 | return 0; 398 | } 399 | 400 | static int dbus_event(int fd, uint32_t mask, void *data) { 401 | sd_bus *bus = data; 402 | 403 | if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { 404 | sway_terminate(0); 405 | } 406 | 407 | int count = 0; 408 | if (mask & WL_EVENT_READABLE) { 409 | count = sd_bus_process(bus, NULL); 410 | } 411 | if (mask & WL_EVENT_WRITABLE) { 412 | sd_bus_flush(bus); 413 | } 414 | if (mask == 0) { 415 | sd_bus_flush(bus); 416 | } 417 | 418 | if (count < 0) { 419 | swayidle_log_errno(LOG_ERROR, "sd_bus_process failed, exiting"); 420 | sway_terminate(0); 421 | } 422 | 423 | return count; 424 | } 425 | 426 | static void set_session(void) { 427 | sd_bus_message *msg = NULL; 428 | sd_bus_error error = SD_BUS_ERROR_NULL; 429 | const char *session_name_tmp; 430 | 431 | int ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, 432 | DBUS_LOGIND_MANAGER_INTERFACE, "GetSession", 433 | &error, &msg, "s", "auto"); 434 | if (ret < 0) { 435 | swayidle_log(LOG_DEBUG, 436 | "GetSession failed: %s", error.message); 437 | sd_bus_error_free(&error); 438 | sd_bus_message_unref(msg); 439 | 440 | ret = sd_bus_call_method(bus, DBUS_LOGIND_SERVICE, DBUS_LOGIND_PATH, 441 | DBUS_LOGIND_MANAGER_INTERFACE, "GetSessionByPID", 442 | &error, &msg, "u", getpid()); 443 | if (ret < 0) { 444 | swayidle_log(LOG_DEBUG, 445 | "GetSessionByPID failed: %s", error.message); 446 | swayidle_log(LOG_ERROR, 447 | "Failed to find session"); 448 | goto cleanup; 449 | } 450 | } 451 | 452 | ret = sd_bus_message_read(msg, "o", &session_name_tmp); 453 | if (ret < 0) { 454 | swayidle_log(LOG_ERROR, 455 | "Failed to read session name"); 456 | goto cleanup; 457 | } 458 | session_name = strdup(session_name_tmp); 459 | swayidle_log(LOG_DEBUG, "Using session: %s", session_name); 460 | 461 | cleanup: 462 | sd_bus_error_free(&error); 463 | sd_bus_message_unref(msg); 464 | } 465 | 466 | static void connect_to_bus(void) { 467 | int ret = sd_bus_default_system(&bus); 468 | if (ret < 0) { 469 | errno = -ret; 470 | swayidle_log_errno(LOG_ERROR, "Failed to open D-Bus connection"); 471 | return; 472 | } 473 | struct wl_event_source *source = wl_event_loop_add_fd(state.event_loop, 474 | sd_bus_get_fd(bus), WL_EVENT_READABLE, dbus_event, bus); 475 | wl_event_source_check(source); 476 | set_session(); 477 | } 478 | 479 | static void setup_sleep_listener(void) { 480 | int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE, 481 | DBUS_LOGIND_PATH, DBUS_LOGIND_MANAGER_INTERFACE, 482 | "PrepareForSleep", prepare_for_sleep, NULL); 483 | if (ret < 0) { 484 | errno = -ret; 485 | swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : sleep"); 486 | return; 487 | } 488 | acquire_inhibitor_lock("sleep", "delay", &sleep_lock_fd); 489 | } 490 | 491 | static void setup_lock_listener(void) { 492 | int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE, 493 | session_name, DBUS_LOGIND_SESSION_INTERFACE, 494 | "Lock", handle_lock, NULL); 495 | if (ret < 0) { 496 | errno = -ret; 497 | swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : lock"); 498 | return; 499 | } 500 | } 501 | 502 | static void setup_unlock_listener(void) { 503 | int ret = sd_bus_match_signal(bus, NULL, DBUS_LOGIND_SERVICE, 504 | session_name, DBUS_LOGIND_SESSION_INTERFACE, 505 | "Unlock", handle_unlock, NULL); 506 | if (ret < 0) { 507 | errno = -ret; 508 | swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : unlock"); 509 | return; 510 | } 511 | } 512 | 513 | static void setup_property_changed_listener(void) { 514 | int ret = sd_bus_match_signal(bus, NULL, NULL, 515 | DBUS_LOGIND_PATH, "org.freedesktop.DBus.Properties", 516 | "PropertiesChanged", handle_property_changed, NULL); 517 | if (ret < 0) { 518 | errno = -ret; 519 | swayidle_log_errno(LOG_ERROR, "Failed to add D-Bus signal match : property changed"); 520 | return; 521 | } 522 | } 523 | #endif 524 | 525 | static void seat_handle_capabilities(void *data, struct wl_seat *seat, 526 | uint32_t capabilities) { 527 | struct seat *self = data; 528 | self->capabilities = capabilities; 529 | } 530 | 531 | static void seat_handle_name(void *data, struct wl_seat *seat, 532 | const char *name) { 533 | struct seat *self = data; 534 | self->name = strdup(name); 535 | } 536 | 537 | static const struct wl_seat_listener wl_seat_listener = { 538 | .name = seat_handle_name, 539 | .capabilities = seat_handle_capabilities, 540 | }; 541 | 542 | static void handle_global(void *data, struct wl_registry *registry, 543 | uint32_t name, const char *interface, uint32_t version) { 544 | if (strcmp(interface, ext_idle_notifier_v1_interface.name) == 0) { 545 | idle_notifier = 546 | wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1); 547 | } else if (strcmp(interface, wl_seat_interface.name) == 0) { 548 | struct seat *s = calloc(1, sizeof(struct seat)); 549 | s->proxy = wl_registry_bind(registry, name, &wl_seat_interface, 2); 550 | 551 | wl_seat_add_listener(s->proxy, &wl_seat_listener, s); 552 | wl_list_insert(&state.seats, &s->link); 553 | } 554 | } 555 | 556 | static void handle_global_remove(void *data, struct wl_registry *registry, 557 | uint32_t name) { 558 | // Who cares 559 | } 560 | 561 | static const struct wl_registry_listener registry_listener = { 562 | .global = handle_global, 563 | .global_remove = handle_global_remove, 564 | }; 565 | 566 | static const struct ext_idle_notification_v1_listener idle_notification_listener; 567 | 568 | static void destroy_cmd_timer(struct swayidle_timeout_cmd *cmd) { 569 | if (cmd->idle_notification != NULL) { 570 | ext_idle_notification_v1_destroy(cmd->idle_notification); 571 | cmd->idle_notification = NULL; 572 | } 573 | } 574 | 575 | static void register_timeout(struct swayidle_timeout_cmd *cmd, 576 | int timeout) { 577 | destroy_cmd_timer(cmd); 578 | 579 | if (timeout < 0) { 580 | swayidle_log(LOG_DEBUG, "Not registering idle timeout"); 581 | return; 582 | } 583 | swayidle_log(LOG_DEBUG, "Register with timeout: %d", timeout); 584 | cmd->idle_notification = 585 | ext_idle_notifier_v1_get_idle_notification(idle_notifier, timeout, seat); 586 | ext_idle_notification_v1_add_listener(cmd->idle_notification, 587 | &idle_notification_listener, cmd); 588 | cmd->registered_timeout = timeout; 589 | } 590 | 591 | static void enable_timeouts(void) { 592 | if (state.timeouts_enabled) { 593 | return; 594 | } 595 | #if HAVE_SYSTEMD || HAVE_ELOGIND 596 | if (get_logind_idle_inhibit()) { 597 | swayidle_log(LOG_INFO, "Not enabling timeouts: idle inhibitor found"); 598 | return; 599 | } 600 | #endif 601 | swayidle_log(LOG_DEBUG, "Enable idle timeouts"); 602 | 603 | state.timeouts_enabled = true; 604 | struct swayidle_timeout_cmd *cmd; 605 | wl_list_for_each(cmd, &state.timeout_cmds, link) { 606 | register_timeout(cmd, cmd->timeout); 607 | } 608 | } 609 | 610 | #if HAVE_SYSTEMD || HAVE_ELOGIND 611 | static void disable_timeouts(void) { 612 | if (!state.timeouts_enabled) { 613 | return; 614 | } 615 | swayidle_log(LOG_DEBUG, "Disable idle timeouts"); 616 | 617 | state.timeouts_enabled = false; 618 | struct swayidle_timeout_cmd *cmd; 619 | wl_list_for_each(cmd, &state.timeout_cmds, link) { 620 | destroy_cmd_timer(cmd); 621 | } 622 | if (state.logind_idlehint) { 623 | set_idle_hint(false); 624 | } 625 | } 626 | #endif 627 | 628 | static void handle_idled(void *data, struct ext_idle_notification_v1 *notif) { 629 | struct swayidle_timeout_cmd *cmd = data; 630 | cmd->resume_pending = true; 631 | swayidle_log(LOG_DEBUG, "idle state"); 632 | #if HAVE_SYSTEMD || HAVE_ELOGIND 633 | if (cmd->idlehint) { 634 | set_idle_hint(true); 635 | } else 636 | #endif 637 | if (cmd->idle_cmd) { 638 | cmd_exec(cmd->idle_cmd); 639 | } 640 | } 641 | 642 | static void handle_resumed(void *data, struct ext_idle_notification_v1 *notif) { 643 | struct swayidle_timeout_cmd *cmd = data; 644 | cmd->resume_pending = false; 645 | swayidle_log(LOG_DEBUG, "active state"); 646 | if (cmd->registered_timeout != cmd->timeout) { 647 | register_timeout(cmd, cmd->timeout); 648 | } 649 | #if HAVE_SYSTEMD || HAVE_ELOGIND 650 | if (cmd->idlehint) { 651 | set_idle_hint(false); 652 | } else 653 | #endif 654 | if (cmd->resume_cmd) { 655 | cmd_exec(cmd->resume_cmd); 656 | } 657 | } 658 | 659 | static const struct ext_idle_notification_v1_listener idle_notification_listener = { 660 | .idled = handle_idled, 661 | .resumed = handle_resumed, 662 | }; 663 | 664 | static char *parse_command(int argc, char **argv) { 665 | if (argc < 1) { 666 | swayidle_log(LOG_ERROR, "Missing command"); 667 | return NULL; 668 | } 669 | 670 | swayidle_log(LOG_DEBUG, "Command: %s", argv[0]); 671 | return strdup(argv[0]); 672 | } 673 | 674 | static struct swayidle_timeout_cmd *build_timeout_cmd(int argc, char **argv) { 675 | errno = 0; 676 | char *endptr; 677 | int seconds = strtoul(argv[1], &endptr, 10); 678 | if (errno != 0 || *endptr != '\0') { 679 | swayidle_log(LOG_ERROR, "Invalid %s parameter '%s', it should be a " 680 | "numeric value representing seconds", argv[0], argv[1]); 681 | exit(-1); 682 | } 683 | 684 | struct swayidle_timeout_cmd *cmd = 685 | calloc(1, sizeof(struct swayidle_timeout_cmd)); 686 | cmd->idlehint = false; 687 | cmd->resume_pending = false; 688 | 689 | if (seconds > 0) { 690 | cmd->timeout = seconds * 1000; 691 | } else { 692 | cmd->timeout = -1; 693 | } 694 | 695 | return cmd; 696 | } 697 | 698 | static int parse_timeout(int argc, char **argv) { 699 | if (argc < 3) { 700 | swayidle_log(LOG_ERROR, "Too few parameters to timeout command. " 701 | "Usage: timeout "); 702 | exit(-1); 703 | } 704 | 705 | struct swayidle_timeout_cmd *cmd = build_timeout_cmd(argc, argv); 706 | 707 | swayidle_log(LOG_DEBUG, "Register idle timeout at %d ms", cmd->timeout); 708 | swayidle_log(LOG_DEBUG, "Setup idle"); 709 | cmd->idle_cmd = parse_command(argc - 2, &argv[2]); 710 | 711 | int result = 3; 712 | if (argc >= 5 && !strcmp("resume", argv[3])) { 713 | swayidle_log(LOG_DEBUG, "Setup resume"); 714 | cmd->resume_cmd = parse_command(argc - 4, &argv[4]); 715 | result = 5; 716 | } 717 | wl_list_insert(&state.timeout_cmds, &cmd->link); 718 | return result; 719 | } 720 | 721 | static int parse_sleep(int argc, char **argv) { 722 | #if !HAVE_SYSTEMD && !HAVE_ELOGIND 723 | swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled " 724 | "with neither systemd nor elogind support.", "before-sleep"); 725 | exit(-1); 726 | #endif 727 | if (argc < 2) { 728 | swayidle_log(LOG_ERROR, "Too few parameters to before-sleep command. " 729 | "Usage: before-sleep "); 730 | exit(-1); 731 | } 732 | 733 | state.before_sleep_cmd = parse_command(argc - 1, &argv[1]); 734 | if (state.before_sleep_cmd) { 735 | swayidle_log(LOG_DEBUG, "Setup sleep lock: %s", state.before_sleep_cmd); 736 | } 737 | 738 | return 2; 739 | } 740 | 741 | static int parse_resume(int argc, char **argv) { 742 | #if !HAVE_SYSTEMD && !HAVE_ELOGIND 743 | swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled " 744 | "with neither systemd nor elogind support.", "after-resume"); 745 | exit(-1); 746 | #endif 747 | if (argc < 2) { 748 | swayidle_log(LOG_ERROR, "Too few parameters to after-resume command. " 749 | "Usage: after-resume "); 750 | exit(-1); 751 | } 752 | 753 | state.after_resume_cmd = parse_command(argc - 1, &argv[1]); 754 | if (state.after_resume_cmd) { 755 | swayidle_log(LOG_DEBUG, "Setup resume hook: %s", state.after_resume_cmd); 756 | } 757 | 758 | return 2; 759 | } 760 | 761 | static int parse_lock(int argc, char **argv) { 762 | #if !HAVE_SYSTEMD && !HAVE_ELOGIND 763 | swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled" 764 | " with neither systemd nor elogind support.", "lock"); 765 | exit(-1); 766 | #endif 767 | if (argc < 2) { 768 | swayidle_log(LOG_ERROR, "Too few parameters to lock command. " 769 | "Usage: lock "); 770 | exit(-1); 771 | } 772 | 773 | state.logind_lock_cmd = parse_command(argc - 1, &argv[1]); 774 | if (state.logind_lock_cmd) { 775 | swayidle_log(LOG_DEBUG, "Setup lock hook: %s", state.logind_lock_cmd); 776 | } 777 | 778 | return 2; 779 | } 780 | 781 | static int parse_unlock(int argc, char **argv) { 782 | #if !HAVE_SYSTEMD && !HAVE_ELOGIND 783 | swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled" 784 | " with neither systemd nor elogind support.", "unlock"); 785 | exit(-1); 786 | #endif 787 | if (argc < 2) { 788 | swayidle_log(LOG_ERROR, "Too few parameters to unlock command. " 789 | "Usage: unlock "); 790 | exit(-1); 791 | } 792 | 793 | state.logind_unlock_cmd = parse_command(argc - 1, &argv[1]); 794 | if (state.logind_unlock_cmd) { 795 | swayidle_log(LOG_DEBUG, "Setup unlock hook: %s", state.logind_unlock_cmd); 796 | } 797 | 798 | return 2; 799 | } 800 | 801 | static int parse_idlehint(int argc, char **argv) { 802 | #if !HAVE_SYSTEMD && !HAVE_ELOGIND 803 | swayidle_log(LOG_ERROR, "%s not supported: swayidle was compiled" 804 | " with neither systemd nor elogind support.", "idlehint"); 805 | exit(-1); 806 | #endif 807 | if (state.logind_idlehint) { 808 | swayidle_log(LOG_ERROR, "Cannot add multiple idlehint events"); 809 | exit(-1); 810 | } 811 | if (argc < 2) { 812 | swayidle_log(LOG_ERROR, "Too few parameters to idlehint command. " 813 | "Usage: idlehint "); 814 | exit(-1); 815 | } 816 | 817 | struct swayidle_timeout_cmd *cmd = build_timeout_cmd(argc, argv); 818 | cmd->idlehint = true; 819 | 820 | swayidle_log(LOG_DEBUG, "Register idlehint timeout at %d ms", cmd->timeout); 821 | wl_list_insert(&state.timeout_cmds, &cmd->link); 822 | state.logind_idlehint = true; 823 | return 2; 824 | } 825 | 826 | static int parse_args(int argc, char *argv[], char **config_path) { 827 | int c; 828 | while ((c = getopt(argc, argv, "C:hdwS:")) != -1) { 829 | switch (c) { 830 | case 'C': 831 | free(*config_path); 832 | *config_path = strdup(optarg); 833 | break; 834 | case 'd': 835 | swayidle_log_init(LOG_DEBUG); 836 | break; 837 | case 'w': 838 | state.wait = true; 839 | break; 840 | case 'S': 841 | state.seat_name = strdup(optarg); 842 | break; 843 | case 'h': 844 | case '?': 845 | printf("Usage: %s [OPTIONS]\n", argv[0]); 846 | printf(" -h\tthis help menu\n"); 847 | printf(" -C\tpath to config file\n"); 848 | printf(" -d\tdebug\n"); 849 | printf(" -w\twait for command to finish\n"); 850 | printf(" -S\tpick the seat to work with\n"); 851 | return 1; 852 | default: 853 | return 1; 854 | } 855 | } 856 | 857 | int i = optind; 858 | while (i < argc) { 859 | if (!strcmp("timeout", argv[i])) { 860 | swayidle_log(LOG_DEBUG, "Got timeout"); 861 | i += parse_timeout(argc - i, &argv[i]); 862 | } else if (!strcmp("before-sleep", argv[i])) { 863 | swayidle_log(LOG_DEBUG, "Got before-sleep"); 864 | i += parse_sleep(argc - i, &argv[i]); 865 | } else if (!strcmp("after-resume", argv[i])) { 866 | swayidle_log(LOG_DEBUG, "Got after-resume"); 867 | i += parse_resume(argc - i, &argv[i]); 868 | } else if (!strcmp("lock", argv[i])) { 869 | swayidle_log(LOG_DEBUG, "Got lock"); 870 | i += parse_lock(argc - i, &argv[i]); 871 | } else if (!strcmp("unlock", argv[i])) { 872 | swayidle_log(LOG_DEBUG, "Got unlock"); 873 | i += parse_unlock(argc - i, &argv[i]); 874 | } else if (!strcmp("idlehint", argv[i])) { 875 | swayidle_log(LOG_DEBUG, "Got idlehint"); 876 | i += parse_idlehint(argc - i, &argv[i]); 877 | } else { 878 | swayidle_log(LOG_ERROR, "Unsupported command '%s'", argv[i]); 879 | return 1; 880 | } 881 | } 882 | 883 | return 0; 884 | } 885 | 886 | static int handle_signal(int sig, void *data) { 887 | struct swayidle_timeout_cmd *cmd; 888 | switch (sig) { 889 | case SIGINT: 890 | case SIGTERM: 891 | swayidle_log(LOG_DEBUG, "Got SIGTERM"); 892 | wl_list_for_each(cmd, &state.timeout_cmds, link) { 893 | if (cmd->resume_pending) { 894 | handle_resumed(cmd, NULL); 895 | } 896 | } 897 | sway_terminate(0); 898 | return 0; 899 | case SIGUSR1: 900 | swayidle_log(LOG_DEBUG, "Got SIGUSR1"); 901 | wl_list_for_each(cmd, &state.timeout_cmds, link) { 902 | register_timeout(cmd, 0); 903 | } 904 | return 1; 905 | } 906 | abort(); // not reached 907 | } 908 | 909 | static int display_event(int fd, uint32_t mask, void *data) { 910 | if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { 911 | sway_terminate(0); 912 | } 913 | 914 | int count = 0; 915 | if (mask & WL_EVENT_READABLE) { 916 | count = wl_display_dispatch(state.display); 917 | } 918 | if (mask & WL_EVENT_WRITABLE) { 919 | wl_display_flush(state.display); 920 | } 921 | if (mask == 0) { 922 | count = wl_display_dispatch_pending(state.display); 923 | wl_display_flush(state.display); 924 | } 925 | 926 | if (count < 0) { 927 | swayidle_log_errno(LOG_ERROR, "wl_display_dispatch failed, exiting"); 928 | sway_terminate(0); 929 | } 930 | 931 | return count; 932 | } 933 | 934 | static char *get_config_path(void) { 935 | static char *config_paths[3] = { 936 | "$XDG_CONFIG_HOME/swayidle/config", 937 | "$HOME/.swayidle/config", 938 | SYSCONFDIR "/swayidle/config", 939 | }; 940 | 941 | char *config_home = getenv("XDG_CONFIG_HOME"); 942 | 943 | if (!config_home || config_home[0] == '\n') { 944 | config_paths[0] = "$HOME/.config/swayidle/config"; 945 | } 946 | 947 | wordexp_t p; 948 | char *path; 949 | for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { 950 | if (wordexp(config_paths[i], &p, 0) == 0) { 951 | path = strdup(p.we_wordv[0]); 952 | wordfree(&p); 953 | if (path && access(path, R_OK) == 0) { 954 | return path; 955 | } 956 | free(path); 957 | } 958 | } 959 | 960 | return NULL; 961 | } 962 | 963 | static int load_config(const char *config_path) { 964 | FILE *f = fopen(config_path, "r"); 965 | if (!f) { 966 | return -ENOENT; 967 | } 968 | 969 | size_t lineno = 0; 970 | char *line = NULL; 971 | size_t n = 0; 972 | ssize_t nread; 973 | while ((nread = getline(&line, &n, f)) != -1) { 974 | lineno++; 975 | if (line[nread-1] == '\n') { 976 | line[nread-1] = '\0'; 977 | } 978 | 979 | if (strlen(line) == 0 || line[0] == '#') { 980 | continue; 981 | } 982 | 983 | size_t i = 0; 984 | while (line[i] != '\0' && line[i] != ' ') { 985 | i++; 986 | } 987 | 988 | wordexp_t p; 989 | wordexp(line, &p, 0); 990 | if (strncmp("timeout", line, i) == 0) { 991 | parse_timeout(p.we_wordc, p.we_wordv); 992 | } else if (strncmp("before-sleep", line, i) == 0) { 993 | parse_sleep(p.we_wordc, p.we_wordv); 994 | } else if (strncmp("after-resume", line, i) == 0) { 995 | parse_resume(p.we_wordc, p.we_wordv); 996 | } else if (strncmp("lock", line, i) == 0) { 997 | parse_lock(p.we_wordc, p.we_wordv); 998 | } else if (strncmp("unlock", line, i) == 0) { 999 | parse_unlock(p.we_wordc, p.we_wordv); 1000 | } else if (strncmp("idlehint", line, i) == 0) { 1001 | parse_idlehint(p.we_wordc, p.we_wordv); 1002 | } else { 1003 | line[i] = 0; 1004 | swayidle_log(LOG_ERROR, "Unexpected keyword \"%s\" in line %zu", line, lineno); 1005 | free(line); 1006 | return -EINVAL; 1007 | } 1008 | wordfree(&p); 1009 | } 1010 | free(line); 1011 | fclose(f); 1012 | 1013 | return 0; 1014 | } 1015 | 1016 | 1017 | int main(int argc, char *argv[]) { 1018 | swayidle_init(); 1019 | char *config_path = NULL; 1020 | if (parse_args(argc, argv, &config_path) != 0) { 1021 | swayidle_finish(); 1022 | free(config_path); 1023 | return -1; 1024 | } 1025 | 1026 | if (!config_path) { 1027 | config_path = get_config_path(); 1028 | } 1029 | 1030 | int config_load = -ENOENT; 1031 | if (config_path) { 1032 | config_load = load_config(config_path); 1033 | } 1034 | if (config_load == -ENOENT) { 1035 | swayidle_log(LOG_DEBUG, "No config file found."); 1036 | } else if (config_load == -EINVAL) { 1037 | swayidle_log(LOG_ERROR, "Config file %s has errors, exiting.", config_path); 1038 | exit(-1); 1039 | } else { 1040 | swayidle_log(LOG_DEBUG, "Loaded config at %s", config_path); 1041 | } 1042 | 1043 | free(config_path); 1044 | 1045 | state.event_loop = wl_event_loop_create(); 1046 | 1047 | wl_event_loop_add_signal(state.event_loop, SIGINT, handle_signal, NULL); 1048 | wl_event_loop_add_signal(state.event_loop, SIGTERM, handle_signal, NULL); 1049 | wl_event_loop_add_signal(state.event_loop, SIGUSR1, handle_signal, NULL); 1050 | 1051 | state.display = wl_display_connect(NULL); 1052 | if (state.display == NULL) { 1053 | swayidle_log(LOG_ERROR, "Unable to connect to the compositor. " 1054 | "If your compositor is running, check or set the " 1055 | "WAYLAND_DISPLAY environment variable."); 1056 | swayidle_finish(); 1057 | return -3; 1058 | } 1059 | 1060 | struct wl_registry *registry = wl_display_get_registry(state.display); 1061 | wl_registry_add_listener(registry, ®istry_listener, NULL); 1062 | wl_display_roundtrip(state.display); 1063 | wl_display_roundtrip(state.display); 1064 | 1065 | struct seat *seat_i; 1066 | wl_list_for_each(seat_i, &state.seats, link) { 1067 | if (state.seat_name == NULL || strcmp(seat_i->name, state.seat_name) == 0) { 1068 | seat = seat_i->proxy; 1069 | } 1070 | } 1071 | 1072 | if (idle_notifier == NULL) { 1073 | swayidle_log(LOG_ERROR, "Compositor doesn't support idle protocol"); 1074 | swayidle_finish(); 1075 | return -4; 1076 | } 1077 | if (seat == NULL) { 1078 | if (state.seat_name != NULL) { 1079 | swayidle_log(LOG_ERROR, "Seat %s not found", state.seat_name); 1080 | } else { 1081 | swayidle_log(LOG_ERROR, "No seat found"); 1082 | } 1083 | swayidle_finish(); 1084 | return -5; 1085 | } 1086 | 1087 | bool should_run = !wl_list_empty(&state.timeout_cmds); 1088 | #if HAVE_SYSTEMD || HAVE_ELOGIND 1089 | connect_to_bus(); 1090 | setup_property_changed_listener(); 1091 | if (state.before_sleep_cmd || state.after_resume_cmd) { 1092 | should_run = true; 1093 | setup_sleep_listener(); 1094 | } 1095 | if (state.logind_lock_cmd) { 1096 | should_run = true; 1097 | setup_lock_listener(); 1098 | } 1099 | if (state.logind_unlock_cmd) { 1100 | should_run = true; 1101 | setup_unlock_listener(); 1102 | } 1103 | if (state.logind_idlehint) { 1104 | set_idle_hint(false); 1105 | } 1106 | #endif 1107 | if (!should_run) { 1108 | swayidle_log(LOG_INFO, "No command specified! Nothing to do, will exit"); 1109 | sway_terminate(0); 1110 | } 1111 | 1112 | enable_timeouts(); 1113 | wl_display_roundtrip(state.display); 1114 | 1115 | struct wl_event_source *source = wl_event_loop_add_fd(state.event_loop, 1116 | wl_display_get_fd(state.display), WL_EVENT_READABLE, 1117 | display_event, NULL); 1118 | wl_event_source_check(source); 1119 | 1120 | while (wl_event_loop_dispatch(state.event_loop, -1) != 1) { 1121 | // This space intentionally left blank 1122 | } 1123 | 1124 | sway_terminate(0); 1125 | } 1126 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'swayidle', 3 | 'c', 4 | version: '1.8.0', 5 | license: 'MIT', 6 | meson_version: '>=0.59.0', 7 | default_options: [ 8 | 'c_std=c11', 9 | 'warning_level=2', 10 | 'werror=true', 11 | ], 12 | ) 13 | 14 | add_project_arguments([ 15 | '-D_POSIX_C_SOURCE=200809L', 16 | 17 | '-Wno-unused-parameter', 18 | '-Wno-unused-result', 19 | '-Wundef', 20 | '-Wvla', 21 | ], language: 'c') 22 | 23 | wayland_client = dependency('wayland-client') 24 | wayland_protos = dependency('wayland-protocols', version: '>=1.27') 25 | wayland_server = dependency('wayland-server') 26 | logind = dependency('lib' + get_option('logind-provider'), required: get_option('logind')) 27 | scdoc = find_program('scdoc', required: get_option('man-pages')) 28 | wayland_scanner = dependency('wayland-scanner', native: true, version: '>=1.14.91') 29 | wayland_scanner_prog = find_program(wayland_scanner.get_variable('wayland_scanner'), native: true) 30 | 31 | wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') 32 | 33 | wayland_scanner_code = generator( 34 | wayland_scanner_prog, 35 | output: '@BASENAME@-protocol.c', 36 | arguments: ['private-code', '@INPUT@', '@OUTPUT@'], 37 | ) 38 | 39 | wayland_scanner_client = generator( 40 | wayland_scanner_prog, 41 | output: '@BASENAME@-client-protocol.h', 42 | arguments: ['client-header', '@INPUT@', '@OUTPUT@'], 43 | ) 44 | 45 | protos = [ 46 | wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', 47 | ] 48 | 49 | protos_src = [] 50 | foreach xml : protos 51 | protos_src += wayland_scanner_code.process(xml) 52 | protos_src += wayland_scanner_client.process(xml) 53 | endforeach 54 | 55 | conf_data = configuration_data() 56 | conf_data.set_quoted('SYSCONFDIR', get_option('prefix') / get_option('sysconfdir')) 57 | conf_data.set10('HAVE_SYSTEMD', false) 58 | conf_data.set10('HAVE_ELOGIND', false) 59 | 60 | if logind.found() 61 | conf_data.set10('HAVE_' + get_option('logind-provider').to_upper(), true) 62 | endif 63 | 64 | config_header = configure_file(output: 'config.h', configuration: conf_data) 65 | 66 | executable( 67 | 'swayidle', [ 68 | 'main.c', 69 | protos_src, 70 | config_header, 71 | ], 72 | dependencies: [ 73 | wayland_client, 74 | wayland_server, 75 | logind, 76 | ], 77 | install: true, 78 | ) 79 | 80 | if scdoc.found() 81 | mandir = get_option('mandir') 82 | man_files = [ 83 | 'swayidle.1.scd', 84 | ] 85 | foreach filename : man_files 86 | topic = filename.split('.')[-3].split('/')[-1] 87 | section = filename.split('.')[-2] 88 | output = '@0@.@1@'.format(topic, section) 89 | 90 | custom_target( 91 | output, 92 | input: filename, 93 | output: output, 94 | command: scdoc, 95 | feed: true, 96 | capture: true, 97 | install: true, 98 | install_dir: '@0@/man@1@'.format(mandir, section) 99 | ) 100 | endforeach 101 | endif 102 | 103 | subdir('completions') 104 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 2 | option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') 3 | option('logind-provider', type: 'combo', choices: ['systemd', 'elogind'], value: 'systemd', description: 'Provider of logind support library') 4 | option('zsh-completions', type: 'boolean', value: true, description: 'Install zsh shell completions.') 5 | option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') 6 | option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') 7 | -------------------------------------------------------------------------------- /swayidle.1.scd: -------------------------------------------------------------------------------- 1 | swayidle(1) 2 | 3 | # NAME 4 | 5 | swayidle - Idle manager for Wayland 6 | 7 | # SYNOPSIS 8 | 9 | *swayidle* [options] [events...] 10 | 11 | # OPTIONS 12 | 13 | *-C* 14 | The config file to use. By default, the following paths are checked in the following order: $XDG_CONFIG_HOME/swayidle/config, $HOME/.swayidle/config. 15 | Config file entries are events as described in the EVENTS section. 16 | Specifying events in the config and as arguments is not mutually exclusive. 17 | 18 | *-h* 19 | Show help message and quit. 20 | 21 | *-d* 22 | Enable debug output. 23 | 24 | *-w* 25 | Wait for command to finish executing before continuing, helpful for ensuring 26 | that a *before-sleep* command has finished before the system goes to sleep. 27 | 28 | Note: using this option causes swayidle to block until the command finishes. 29 | 30 | *-S* 31 | Specify which seat to use. By default, if no name is specified, an arbitrary seat will be picked instead. 32 | 33 | # DESCRIPTION 34 | 35 | swayidle listens for idle activity on your Wayland compositor and executes tasks 36 | on various idle-related events. You can specify any number of events at the 37 | command line and in the config file. 38 | 39 | # EVENTS 40 | 41 | *timeout* [resume ] 42 | Execute _timeout command_ if there is no activity for seconds. 43 | 44 | If you specify "resume ", _resume command_ will be run when 45 | there is activity again. 46 | 47 | *before-sleep* 48 | If built with systemd support, executes _command_ before systemd puts the 49 | computer to sleep. 50 | 51 | Note: this only delays sleeping up to the limit set in *logind.conf(5)* by 52 | the option InhibitDelayMaxSec. A command that has not finished by then will 53 | continue running after resuming from sleep. 54 | 55 | *after-resume* 56 | If built with systemd support, executes _command_ after logind signals that the 57 | computer resumed from sleep. 58 | 59 | *lock* 60 | If built with systemd support, executes _command_ when logind signals that the 61 | session should be locked 62 | 63 | *unlock* 64 | If built with systemd support, executes _command_ when logind signals that the 65 | session should be unlocked 66 | 67 | *idlehint* 68 | If built with systemd support, set IdleHint to indicate an idle logind/elogind 69 | session after seconds. Adding an idlehint event will also cause 70 | swayidle to call SetIdleHint(false) when run, on resume, unlock, etc. 71 | 72 | All commands are executed in a shell. 73 | 74 | # SIGNALS 75 | 76 | swayidle responds to the following signals: 77 | 78 | *SIGTERM, SIGINT* 79 | Run all pending resume commands. When finished swayidle will terminate. 80 | 81 | *SIGUSR1* 82 | Immediately enter idle state. 83 | 84 | # EXAMPLE 85 | 86 | ``` 87 | swayidle -w \\ 88 | timeout 300 'swaylock -f -c 000000' \\ 89 | timeout 600 'swaymsg "output * power off"' \\ 90 | resume 'swaymsg "output * power on"' \\ 91 | before-sleep 'swaylock -f -c 000000' 92 | ``` 93 | 94 | This will lock your screen after 300 seconds of inactivity, then turn off your 95 | displays after another 300 seconds, and turn your screens back on when resumed. 96 | It will also lock your screen before your computer goes to sleep. 97 | 98 | To make sure swayidle waits for swaylock to lock the screen before it releases the 99 | inhibition lock, the *-w* options is used in swayidle, and *-f* in swaylock. 100 | 101 | # AUTHORS 102 | 103 | Maintained by Drew DeVault , who is assisted by other open 104 | source contributors. For more information about swayidle development, see 105 | https://github.com/swaywm/swayidle. 106 | 107 | # SEE ALSO 108 | 109 | *sway*(5) *swaymsg*(1) *sway-input*(5) *sway-output*(5) *sway-bar*(5) *loginctl*(1) 110 | --------------------------------------------------------------------------------