/* this is deliberately the ONLY thing swayaway does on the system bus: * it listens for org.freedesktop.login1.Manager.PrepareForSleep so that * `before-sleep` and `after-resume` commands can be run around suspend * it is implemented with plain libdbus-1 method calls and signal matches, * so there is no elogind, no libsystemd, and no dependency on logind * actually running. (if there is no logind, these events simply never fire) */ #include "swayaway.h" #include #include #include #include #include #include #include #define LOGIND_SERVICE "org.freedesktop.login1" #define LOGIND_PATH "/org/freedesktop/login1" #define LOGIND_MANAGER "org.freedesktop.login1.Manager" static void handle_prepare_for_sleep(DBusConnection *conn, DBusMessage *msg) { (void)conn; dbus_bool_t going_down = TRUE; DBusError err; dbus_error_init(&err); if (!dbus_message_get_args(msg, &err, DBUS_TYPE_BOOLEAN, &going_down, DBUS_TYPE_INVALID)) { sway_log_debug("bad PrepareForSleep: %s", err.message ? err.message : "unknown error"); dbus_error_free(&err); return; } sway_log_debug("PrepareForSleep %s", going_down ? "going down" : "resumed"); if (going_down) { if (state.before_sleep_cmd) { cmd_exec(state.before_sleep_cmd); } // releasing the delay lock lets logind actually suspend now logind_release_sleep_lock(); } else { if (state.after_resume_cmd) { cmd_exec(state.after_resume_cmd); } logind_acquire_sleep_lock(); } } static DBusHandlerResult filter(DBusConnection *conn, DBusMessage *msg, void *ud) { (void)ud; if (dbus_message_is_signal(msg, LOGIND_MANAGER, "PrepareForSleep")) { handle_prepare_for_sleep(conn, msg); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } void logind_init(void) { DBusError err; dbus_error_init(&err); state.system_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); if (state.system_conn == NULL) { sway_log("could not connect to the system bus: %s", err.message ? err.message : "unknown error"); dbus_error_free(&err); return; } dbus_error_free(&err); dbus_connection_set_exit_on_disconnect(state.system_conn, FALSE); dbus_connection_get_unix_fd(state.system_conn, &state.system_fd); dbus_connection_add_filter(state.system_conn, filter, NULL, NULL); DBusError match_err; dbus_error_init(&match_err); dbus_bus_add_match(state.system_conn, "type='signal',interface='org.freedesktop.login1.Manager'," "member='PrepareForSleep',path='/org/freedesktop/login1'", &match_err); if (dbus_error_is_set(&match_err)) { sway_log("could not add PrepareForSleep match: %s", match_err.message); } dbus_error_free(&match_err); logind_acquire_sleep_lock(); } /* hold a logind "sleep" delay inhibitor so logind waits for the before-sleep * command to finish * * inhibit returns a unix fd that keeps the lock alive until closed * without logind this fails harmlessly and sleep events simply won't fire. */ void logind_acquire_sleep_lock(void) { if (state.system_conn == NULL || state.sleep_lock_fd >= 0) { return; } if (!state.before_sleep_cmd && !state.after_resume_cmd) { return; } DBusMessage *call = dbus_message_new_method_call(LOGIND_SERVICE, LOGIND_PATH, LOGIND_MANAGER, "Inhibit"); if (!call) { return; } const char *type = "sleep"; const char *who = "swayaway"; const char *why = "Swayaway is preventing sleep"; const char *mode = "delay"; dbus_message_append_args(call, DBUS_TYPE_STRING, &type, DBUS_TYPE_STRING, &who, DBUS_TYPE_STRING, &why, DBUS_TYPE_STRING, &mode, DBUS_TYPE_INVALID); DBusError err; dbus_error_init(&err); DBusMessage *reply = dbus_connection_send_with_reply_and_block( state.system_conn, call, -1, &err); dbus_message_unref(call); if (reply == NULL) { sway_log_debug("logind Inhibit failed: %s", err.message ? err.message : "unknown error"); dbus_error_free(&err); return; } dbus_error_free(&err); int fd = -1; if (!dbus_message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID)) { sway_log_debug("logind Inhibit reply malformed: %s", err.message ? err.message : "unknown error"); dbus_error_free(&err); dbus_message_unref(reply); return; } dbus_message_unref(reply); int dup_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); close(fd); if (dup_fd >= 0) { state.sleep_lock_fd = dup_fd; sway_log_debug("holding logind sleep delay lock (fd %d)", dup_fd); } else { sway_log("could not duplicate sleep lock fd: %s", strerror(errno)); } } void logind_release_sleep_lock(void) { if (state.sleep_lock_fd >= 0) { sway_log_debug("releasing logind sleep delay lock"); close(state.sleep_lock_fd); state.sleep_lock_fd = -1; } }