λ

swayaway

a drop-in replacement for swayidle(1)

settings

logind.c

5023 bytes raw

/* 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 <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dbus/dbus.h>

#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;
	}
}