Power Management

Power Management & Idle Orchestration

A universal, role-aware power management system orchestrating system sleep, hardware suspend, and desktop idle behaviors across diverse hosts.

This repository features a unified power management architecture centered at modules/services/system/power-management.nix. It dynamically adjusts CPU governors, sleep delays, lid behaviors, and Wayland idle daemons based on the host’s explicitly defined profile role and hardware tags.


🏗️ Architecture

Instead of hardcoding power behavior per host, the system evaluates the profile configuration (config.modules.profiles):

  1. Role (modules.profiles.role): Determines if a system is a server, board, workstation/pc, workstation/laptop, or portable.
  2. Hardware (modules.profiles.hardware): Identifies specific hardware form factors (e.g., pc/laptop), enabling or disabling device-specific daemon optimizations.
  3. Caffeine State: A dynamic, runtime-toggleable state to temporarily suspend sleep behaviors.

Evaluation Logic

The core logic dynamically asserts canSleep and isLaptop:

  • Servers (role = "server") & Workstation Servers (role = "workstation/server"): canSleep is set to false. The system will never attempt to suspend or hibernate, regardless of timeouts. Desktop locks still occur if active.
  • SBCs (role = "board"): canSleep is set to false. Uses a powersave CPU governor to reduce thermals, but remains continually active (useful for services like AdGuard Home).
  • Laptops & Portable Media (role = "workstation/laptop" or role = "portable"): canSleep is set to true. Enables power-profiles-daemon, explicitly disables tlp to prevent conflicts, handles lid switches (suspend on close, lock on external power).
  • Workstations (role = "workstation/pc"): canSleep is set to true. Uses the performance CPU governor and participates in standard idle/sleep timeouts.

💤 Idle & Suspend Flow

When canSleep evaluates to true, the system employs a synchronized suspend-then-hibernate strategy.

Desktop Idle Timeouts

Wayland idle managers (swayidle for standard compositors, hypridle for Hyprland/Niri) are configured under modules.services.desktop.

  1. Lock Screen (30-40 min): The system issues a hey hook idle --on lock event.
  2. Auto-Sleep (30-40 min): A timeout listener is fired. Instead of natively commanding systemd to sleep, it invokes a safety check against the user’s Caffeine status. If Caffeine is off, systemctl suspend-then-hibernate is triggered.

Systemd Hook Orchestration

Instead of binding visual lockdown effects directly to the idle timer (which causes desyncs during manual sleeps or lid-closes), the system uses systemd sleep hooks:

  • before-sleep Event: When systemctl suspend is triggered, systemd emits a before-sleep signal.
  • Zsh Bridge: The idle daemon catches this signal and runs hey hook idle --on sleep (via bin/hooks/idle.zsh).
  • Preparation: The script pauses active media (playerctl -a pause), emits a shutdown sound, disables active VRR/tearing, and fires the screen locker before the hardware halts.

☕ Caffeine Mode (Presentation Mode)

Users can actively inhibit the auto-sleep behavior by toggling Caffeine Mode.

  • Toggle Command: hey .toggle-caffeine (Bound to Mod+Shift+I on Hyprland and Niri).
  • Mechanism: The script alters a persistent tmpfs state variable (hey vars set caffeine true) and touches a flat-file /tmp/caffeine_state for external UI modules (like DMS Shell) to read.
  • Execution Check: The idle daemon’s auto-sleep command natively reads this state: if [ "$(${heyBin} vars get caffeine)" != "true" ]; then systemctl suspend-then-hibernate; fi. If true, it silently exits. The screen will still automatically lock, but the hardware will remain awake.

🔧 Resolving Conflicts (TLP vs. PPD)

We strictly standardize on power-profiles-daemon for modern laptops. Tools like powertop (auto-tune service) and tlp are actively disabled (services.tlp.enable = mkForce false;) to prevent complex USB-suspension bugs and governor overriding conflicts.