# Active power management. One concern, one file: this is the system # side — the power daemon (power-profiles-daemon by default, or TLP), # thermald, and the battery charge limit. The profile *switcher* and the # Waybar *indicator* live home-side (rofi.nix / waybar.nix); they self- # gate on powerprofilesctl being present, so there's no system→home wiring # to keep in sync (the same way the Waybar battery widget auto-hides on # desktops). See the roadmap in README.md. { config, lib, ... }: let cfg = config.nomarchy.system.power; ppd = cfg.backend == "ppd"; tlp = cfg.backend == "tlp"; chargeLimit = cfg.laptop && cfg.batteryChargeLimit != null; in { config = lib.mkIf cfg.enable { # PPD and TLP both drive CPU/platform power — running both fights. # `backend` selects exactly one; this assertion only fires if a # downstream force-enables the other service directly. assertions = [{ assertion = !(config.services.power-profiles-daemon.enable && config.services.tlp.enable); message = '' nomarchy: power-profiles-daemon and TLP are mutually exclusive. Pick one with nomarchy.system.power.backend ("ppd" or "tlp"). ''; }]; # Unprivileged profile switching (the menu/Waybar) goes through # polkit, already enabled distro-wide in default.nix. services.power-profiles-daemon.enable = lib.mkDefault ppd; services.tlp.enable = lib.mkDefault tlp; # thermald is Intel-only, so off unless asked (the installer enables # it on a GenuineIntel CPU). Sits happily next to either backend. services.thermald.enable = lib.mkDefault cfg.thermal.enable; # Battery charge limit via sysfs. PPD can't cap charge at all, and # TLP's own knob only applies under TLP — so a tiny oneshot writes # the threshold directly, independent of the backend. Re-applied on # AC state changes by the udev rule below (some firmwares reset the # threshold when the charger is unplugged). `[ -w ]` skips paths that # don't exist, so this is a clean no-op on hardware without the control. systemd.services.nomarchy-battery-charge-limit = lib.mkIf chargeLimit { description = "Cap battery charging at ${toString cfg.batteryChargeLimit}%"; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; script = '' for thresh in /sys/class/power_supply/BAT*/charge_control_end_threshold; do [ -w "$thresh" ] && echo ${toString cfg.batteryChargeLimit} > "$thresh" done exit 0 ''; }; # Re-apply the threshold whenever the mains adapter changes state: the # boot oneshot above runs once, but some firmwares clear the limit when # the charger is unplugged, so it must be re-asserted on the event. # Matched by ATTR{type}=="Mains" (vendor-neutral — the kernel name # AC/AC0/ADP1/ACAD varies); --no-block so the RUN+= returns at once # (systemd kills long-running udev workers); restart (not try-restart) # so it re-applies even if the boot run was inactive. services.udev.extraRules = lib.mkIf chargeLimit '' SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="${config.systemd.package}/bin/systemctl --no-block restart nomarchy-battery-charge-limit.service" ''; }; }