Files
Kigi-CLI/crates/codegen/kigi-system-power/src/linux.rs
T
ZacharyZhang-NY a02b555e66 docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
2026-07-23 16:55:39 -04:00

94 lines
3.7 KiB
Rust

//! Linux system sleep/wake via systemd-logind's `PrepareForSleep` D-Bus
//! signal, with a `delay` inhibitor lock so we get a short window to react
//! before the system actually sleeps.
//!
//! Uses the `zbus` blocking API on a dedicated thread so we don't require the
//! caller to run any particular async runtime. If the system bus or logind is
//! unavailable (non-systemd distro, container, permission error), `start`
//! returns `None` and the caller degrades gracefully.
use std::thread;
use super::{PowerCallback, PowerEvent};
const DEST: &str = "org.freedesktop.login1";
const PATH: &str = "/org/freedesktop/login1";
const IFACE: &str = "org.freedesktop.login1.Manager";
/// Linux listener handle.
///
/// There is deliberately no clean stop: the worker thread parks on a blocking
/// logind signal iterator, which cannot be interrupted without a signal
/// arriving, so dropping this neither joins nor cancels it. The thread (and its
/// D-Bus connection + sleep-delay inhibitor fd) live until process exit. That
/// is acceptable for the only intended use — a single process-lifetime listener
/// whose callback holds a `Weak` ref and no-ops once the owner is gone. (macOS
/// can `CFRunLoopStop` from `Drop` and so joins; Linux cannot — hence the
/// asymmetry, and why there is no `Drop` impl here.)
pub(crate) struct Listener;
impl Listener {
pub(crate) fn start(callback: PowerCallback) -> Option<Self> {
// Probe synchronously so registration failures return `None` to the
// caller rather than dying silently on the worker thread.
let conn = zbus::blocking::Connection::system().ok()?;
let proxy = zbus::blocking::Proxy::new(&conn, DEST, PATH, IFACE).ok()?;
let signals = proxy.receive_signal("PrepareForSleep").ok()?;
thread::Builder::new()
.name("xai-power-listener".into())
.spawn(move || run_thread(proxy, signals, callback))
.ok()?;
Some(Self)
}
}
/// Take a `delay` sleep inhibitor: logind holds off sleep until the returned fd
/// drops. `None` if unavailable — we then react without a pre-sleep window.
fn take_inhibitor(proxy: &zbus::blocking::Proxy<'_>) -> Option<zbus::zvariant::OwnedFd> {
proxy
.call(
"Inhibit",
&("sleep", "kigi", "Pause token refresh across sleep", "delay"),
)
.ok()
}
fn run_thread(
proxy: zbus::blocking::Proxy<'static>,
signals: zbus::blocking::proxy::SignalIterator<'static>,
callback: PowerCallback,
) {
// Hold the delay lock so the first PrepareForSleep(true) gives us a window.
let mut inhibitor = take_inhibitor(&proxy);
for msg in signals {
let Ok(about_to_sleep) = msg.body().deserialize::<bool>() else {
continue;
};
if about_to_sleep {
// The callback may block (bounded) waiting for an in-flight token
// refresh to finish; the `delay` inhibitor is still held across it,
// so that wait holds off the suspend (up to logind's
// `InhibitDelayMaxSec`, default 5 s). Release it only once the
// callback returns so the system can then proceed to sleep.
callback(PowerEvent::WillSleep);
inhibitor = None;
} else {
callback(PowerEvent::DidWake);
// Re-acquire the delay lock for the next sleep cycle.
inhibitor = take_inhibitor(&proxy);
}
}
drop(inhibitor);
}
pub(crate) fn current_power_state() -> crate::PowerState {
// Linux has no "dark wake" equivalent to query (the system is either
// suspended or fully awake); report Unknown so callers fall back to the
// logind `PrepareForSleep` path.
crate::PowerState::Unknown
}