D-Bus Sleep Inhibition API Reference
This document describes the D-Bus API used by rouser for inhibiting system sleep on Linux systems running systemd. It serves as a technical reference for developers extending or debugging inhibition behavior.
Overview
Systemd provides a sleep inhibition mechanism that allows applications to register interest in preventing system suspend/hibernate. When an inhibitor is active, the system delays or prevents sleep operations.
rouser uses this via the org.freedesktop.login1.Manager.Inhibit method on the system D-Bus. Unlike session-bus APIs (e.g., the deprecated FreeDesktop PowerManagement API), systemd login1 works without a graphical session and is actively maintained.
Key Concepts
- Inhibition Lock: A file descriptor that, when kept open, holds sleep inhibited
- cookie: A unique identifier returned by
Inhibit()for tracking individual inhibition requests - what: The type of operation to inhibit (sleep, shutdown, etc.)
- mode: How the inhibitor affects the operation (block, delay, block-weak)
D-Bus Service Details
| Property | Value |
|---|---|
| Bus | System bus (org.freedesktop.login1) |
| Object Path | /org/freedesktop/login1 |
| Interface | org.freedesktop.login1.Manager |
| Method | Inhibit(string what, string mode, string who, string reason) → (handle cookie) |
Inhibition Parameters
what — Colon-separated lock types:
| Lock Type | ACPI State | Description |
|---|---|---|
idle |
S5 (software power off) / idle suspend | Prevents the system from entering an idle/off state |
sleep |
S3 (Suspend-to-RAM) / S4 (Suspend-to-disk) | Prevents sleep/hibernate operations |
mode — How the inhibitor affects the operation:
| Mode | Behavior | Equivalent systemd directive |
|---|---|---|
block |
Completely blocks the operation while lock is held | Default behavior |
delay |
Delays the operation for up to InhibitDelayMaxUSec() |
InhibitDelayMaxUSec= |
block-weak |
Blocks but can be overridden by privileged processes | N/A (internal) |
who — A short string identifying the process (e.g., "rouser"). Appears in systemd-inhibit --list.
reason — A human-readable description of why sleep is inhibited. Appears in inhibitor listings for operators.
Return Values
| Value | Type | Description |
|---|---|---|
fd (handle) |
Unix file descriptor (h) |
Keep open to maintain inhibition; close to release |
cookie |
String | Unique identifier for this lock, used for tracking/debugging |
Lock Types Reference
The what parameter accepts these values individually or combined with colons:
| Value | Description | Use Case |
|---|---|---|
idle |
Prevents idle/off state transitions | Headless servers that should stay awake during background work even when no user is logged in |
sleep |
Prevents S3 (Suspend-to-RAM) and S4 (Suspend-to-disk) | Standard use case for desktop/laptop systems |
Default: rouser defaults to "shutdown:idle". The choice between "sleep" and "shutdown:idle" affects how desktop environments behave:
"sleep"— Simple, good for headless servers. Blocks sleep/hibernate but does not interfere with DE idle timers. Observed issue on KDE: may cause the system to never automatically sleep after inhibition is released, as it interprets the lock as a persistent "don't touch my power management" signal."shutdown:idle"— Conservative default, better for workstations/home-labs running DEs like KDE/GNOME. Prevents powered-off/suspended state transitions while metrics are active but lets the DE respect its configured idle delay (e.g., if KDE is set to sleep after 15 minutes of inactivity, it will put the system to sleep 15 minutes after rouser releases locks).
To change between options:
[inhibitor]
what = "idle:sleep" # Block both idle and sleep
mode = "block"
Note: The
shutdownlock type in the default config does not prevent reboot/shutdown — it prevents idle-to-shutdown transitions. A privileged user can still force shutdown withsystemctl --force reboot.
Rust Implementation (zbus v4)
rouser uses zbus version 4 for D-Bus communication. The inhibition logic is in src/inhibit.rs.
Core Pattern: RAII Wrap
The file descriptor returned by Inhibit() must be kept open while inhibition should be active, and closed when inhibition ends. rouser wraps this using Rust's Drop trait for automatic cleanup:
use zbus::{Connection, Result};
use std::os::unix::io::RawFd;
pub struct SleepInhibitor {
fd: RawFd,
cookie: String,
}
impl SleepInhibitor {
pub async fn new(what: &str, mode: &str, reason: &str) -> Result<Self> {
let connection = Connection::system().await?;
// Call Inhibit() on the system bus
let (fd, cookie): (RawFd, String) = connection.call_method(
Some("org.freedesktop.login1"),
"/org/freedesktop/login1",
Some("org.freedesktop.login1.Manager"),
"Inhibit",
&(what, mode, "rouser", reason),
).await?;
Ok(Self { fd, cookie })
}
}
impl Drop for SleepInhibitor {
fn drop(&mut self) {
// File descriptor is automatically closed when dropped.
// This releases the inhibition lock and allows sleep again.
}
}
When SleepInhibitor goes out of scope (or is explicitly dropped), the file descriptor closes, releasing inhibition immediately — no explicit D-Bus release call needed.
Usage in rouser's Service Loop
The inhibitor is acquired/released within the main service loop (src/service.rs):
- Each tick collects metrics and evaluates thresholds
- If any metric exceeds its threshold continuously for
duration_threshold, a newSleepInhibitoris created - While inhibited, inhibition state persists across ticks (the
SleepInhibitorstruct lives inDataManager) - When all metrics stay below their thresholds for
cooldown_duration, the inhibitor is dropped and sleep is released
State-Change Logging
Following best practices, rouser only logs INFO-level messages when the inhibition state transitions — not on every polling tick:
// After evaluating state in a tick:
let current_inhibited = self.state.is_inhibited();
if !self.previous_inhibited_state && current_inhibited {
info!("Sleep inhibited: at least one metric above threshold");
} else if self.previous_inhibited_state && !current_inhibited {
info!("Releasing sleep inhibition: all metrics below thresholds for cooldown duration");
}
self.previous_inhibited_state = current_inhibited;
This prevents log spam (previously, "Sleep inhibited" was logged every 5 seconds while active). See AGENTS.md → Logging Conventions for the state-change-only logging rule.
Debugging and Verification
Check Active Inhibitors
# List all current inhibitors system-wide
systemd-inhibit --list
# Show detailed info about a specific inhibitor
systemd-inhibit --list --what=sleep
# From rouser's perspective — check if your user has inhibition permission
loginctl list-sessions | grep $(whoami)
Check systemd-logind Status
# Verify the login1 D-Bus service is running
systemctl status systemd-logind
# Check for errors in logind journal
journalctl -u systemd-logind --since "5 min ago"
Common Errors
| Error | Cause | Solution |
|---|---|---|
AccessDenied (org.freedesktop.login1) |
User lacks permission to inhibit via polkit | Add polkit rule for the user, or run as root. See Security Best Practices. |
Failed (org.freedesktop.DBus.Error.ServiceUnknown) |
systemd-logind not running on system bus | Ensure systemd is active: systemctl --user start systemd-logind or check host-level service. |
No inhibitors showing in systemd-inhibit --list |
Inhibition was released before command ran | Check timing — inhibition may have been released between polling cycles. Run rouser with debug logging to track state transitions. |
Deprecated APIs (Do Not Use)
FreeDesktop PowerManagement API
The old /org/freedesktop/PowerManagement.Inhibit API is obsolete and should never be used:
- Deprecated around 2014 (systemd 183 era)
- Specs no longer available in current freedesktop.org documentation
- Does not work reliably on modern desktop environments
- KDE Powerdevil ignores inhibitors from unprivileged users regardless of the API used
Always use org.freedesktop.login1.Manager.Inhibit instead. See AGENTS.md → Lessons Learned for more context on why this approach was chosen over alternatives.