λ

swayaway

a drop-in replacement for swayidle(1)

settings

idle.c

4756 bytes raw

#include "swayaway.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int64_t mono_ms(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}

void idle_add_timeout(int seconds, char *idle_cmd, char *resume_cmd)
{
	struct timeout_cmd *tc = calloc(1, sizeof(*tc));
	if (!tc) {
		sway_log("out of memory registering timeout");
		exit(1);
	}
	tc->seconds = seconds;
	tc->idle_cmd = idle_cmd;
	tc->resume_cmd = resume_cmd;
	tc->fired = false;
	tc->next = NULL;

	// append so commands run in configuration order
	struct timeout_cmd **p = &state.timeouts;
	while (*p) {
		p = &(*p)->next;
	}
	*p = tc;
	sway_log_debug("registered timeout %ds%s", seconds,
	               resume_cmd ? " with resume command" : "");
}

void idle_add_resume_cmd(char *cmd)
{
	struct resume_cmd *rc = calloc(1, sizeof(*rc));
	if (!rc) {
		sway_log("out of memory registering resume command");
		exit(1);
	}
	rc->cmd = cmd;
	rc->next = NULL;
	struct resume_cmd **p = &state.resume_cmds;
	while (*p) {
		p = &(*p)->next;
	}
	*p = rc;
}

bool idle_has_work(void)
{
	return state.timeouts != NULL || state.resume_cmds != NULL ||
	       state.before_sleep_cmd || state.after_resume_cmd;
}

// inhibited = one or more ScreenSaver cookies are held
void idle_recompute_inhibited(void)
{
	bool new_val = state.screensaver_inhibited;
	if (new_val == state.inhibited) {
		return;
	}
	state.inhibited = new_val;
	if (new_val) {
		sway_log("idle timeouts suppressed by inhibitor");
		// don't accumulate idle time while inhibited
	} else {
		sway_log("inhibitors released, resetting idle clock");
		/* swayidle restarts idle counting when an inhibitor goes away;
		 * copy that by resetting the clock and clearing fired flags */
		state.idle_since_ms = mono_ms();
		struct timeout_cmd *tc;
		for (tc = state.timeouts; tc; tc = tc->next) {
			tc->fired = false;
		}
		idle_check_timeouts();
	}
}

void idle_on_idled(void)
{
	sway_log_debug("compositor reports idle");
	state.is_idle = true;
	state.idle_since_ms = mono_ms();
	idle_check_timeouts();
}

void idle_on_resumed(void)
{
	sway_log_debug("compositor reports activity");
	state.is_idle = false;
	screensaver_emit_active();

	struct timeout_cmd *tc;
	for (tc = state.timeouts; tc; tc = tc->next) {
		if (tc->fired && tc->resume_cmd) {
			cmd_exec(tc->resume_cmd);
		}
		tc->fired = false;
	}
	struct resume_cmd *rc;
	for (rc = state.resume_cmds; rc; rc = rc->next) {
		cmd_exec(rc->cmd);
	}
}

/* fire every timeout whose deadline has passed; called after every poll
 * iteration and whenever idle/inhibit state changes */
void idle_check_timeouts(void)
{
	if (!state.is_idle || state.inhibited) {
		return;
	}
	int64_t now = mono_ms();
	struct timeout_cmd *tc;
	for (tc = state.timeouts; tc; tc = tc->next) {
		if (tc->fired || tc->seconds <= 0) {
			continue;
		}
		if (now - state.idle_since_ms >= (int64_t)tc->seconds * 1000) {
			sway_log_debug("timeout %ds fired", tc->seconds);
			tc->fired = true;
			if (tc->idle_cmd) {
				cmd_exec(tc->idle_cmd);
			}
		}
	}
	screensaver_emit_active(); // may transition active -> true
}

/* the screensaver is "active" once at least one timeout command has fired
 * during the current idle period (mirroring XScreenSaver's GetActive) */
bool idle_any_timeout_fired(void)
{
	struct timeout_cmd *tc;
	for (tc = state.timeouts; tc; tc = tc->next) {
		if (tc->fired) {
			return true;
		}
	}
	return false;
}

/* milliseconds until the next timeout should fire, or -1 if nothing is
 * pending; used as the poll() timeout so it wakes up exactly on time */
int idle_next_timeout_ms(void)
{
	if (!state.is_idle || state.inhibited) {
		return -1;
	}
	int64_t now = mono_ms();
	int64_t best = -1;
	struct timeout_cmd *tc;
	for (tc = state.timeouts; tc; tc = tc->next) {
		if (tc->fired || tc->seconds <= 0) {
			continue;
		}
		int64_t deadline = state.idle_since_ms + (int64_t)tc->seconds * 1000;
		int64_t remaining = deadline - now;
		if (remaining < 0) {
			remaining = 0;
		}
		if (best < 0 || remaining < best) {
			best = remaining;
		}
	}
	if (best < 0) {
		return -1;
	}
	if (best > INT32_MAX) {
		return INT32_MAX;
	}
	return (int)best;
}

void idle_force_idle(void)
{
	sway_log("SIGUSR1: forcing idle state");
	if (state.inhibited) {
		sway_log_debug("idle timeouts suppressed by inhibitor, not forcing");
		return;
	}
	state.is_idle = true;
	state.idle_since_ms = mono_ms();
	struct timeout_cmd *tc;
	for (tc = state.timeouts; tc; tc = tc->next) {
		tc->fired = false;
	}
	idle_check_timeouts();
}

// SIGINT/SIGTERM: run pending resume commands, then terminate
void idle_terminate(void)
{
	sway_log("terminating");
	if (state.is_idle) {
		idle_on_resumed();
	}
	state.running = false;
}