All checks were successful
Check / eval (push) Successful in 3m3s
Latitude 5310 QA:
- Clock tooltip: drop empty {calendar} popup; plain date+zone
- Charge limit: restart oneshot via polkit + dual write/delay for
Dell unplug race; toast verifies sysfs; looser menu match
- pwvucontrol ships with waybar (right-click volume)
- Print/region→clipboard toasts success
- Polkit (and pinentry) open on current workspace + stayfocused
V0: flake check --no-build green. V3: retest A3/A4/B1/tooltip/polkit.
124 lines
5.7 KiB
Nix
124 lines
5.7 KiB
Nix
# 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, pkgs, ... }:
|
|
|
|
let
|
|
cfg = config.nomarchy.system.power;
|
|
ppd = cfg.backend == "ppd";
|
|
tlp = cfg.backend == "tlp";
|
|
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).
|
|
#
|
|
# Instant menu path (user decision 2026-07-10): the threshold node is
|
|
# group-writable for `users` so nomarchy-menu can echo live without
|
|
# rebuild; this oneshot still owns boot + AC-replug re-apply. Prefer
|
|
# live theme-state.json under /home/*/.nomarchy so a menu change
|
|
# survives reboot before the next sys-rebuild bakes the Nix option.
|
|
systemd.services.nomarchy-battery-charge-limit = lib.mkIf cfg.laptop {
|
|
description = "Apply battery charge end threshold from state or config";
|
|
wantedBy = [ "multi-user.target" ];
|
|
path = [ pkgs.jq pkgs.coreutils ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
};
|
|
script = ''
|
|
set -euo pipefail
|
|
# Baked generation default (empty = full charge / no cap).
|
|
limit=${if cfg.batteryChargeLimit != null then toString cfg.batteryChargeLimit else ""}
|
|
# Prefer the newest live state write (menu path, no rebuild yet).
|
|
for st in /home/*/.nomarchy/theme-state.json; do
|
|
[ -r "$st" ] || continue
|
|
v=$(jq -r '.settings.power.batteryChargeLimit // empty' "$st" 2>/dev/null || true)
|
|
case "$v" in
|
|
""|null|Null) ;;
|
|
*[!0-9]*) ;;
|
|
*) limit=$v ;;
|
|
esac
|
|
done
|
|
[ -n "$limit" ] || limit=100
|
|
# Name-agnostic system batteries (BACKLOG #60).
|
|
for d in /sys/class/power_supply/*/; do
|
|
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
|
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
|
thresh="$d/charge_control_end_threshold"
|
|
[ -e "$thresh" ] || continue
|
|
# Keep the node group-writable for the menu's live path (udev
|
|
# sets this on add; re-assert after firmware recreates attrs).
|
|
chgrp users "$thresh" 2>/dev/null || true
|
|
chmod 0664 "$thresh" 2>/dev/null || true
|
|
# Some firmwares (Dell) reset the threshold on unplug *after*
|
|
# the udev event — write once, wait, write again.
|
|
echo "$limit" > "$thresh" 2>/dev/null || true
|
|
sleep 1
|
|
echo "$limit" > "$thresh" 2>/dev/null || true
|
|
done
|
|
exit 0
|
|
'';
|
|
};
|
|
|
|
# Writable threshold for the logged-in user (menu live apply) + AC
|
|
# re-apply of the oneshot. Laptop only; no-op when the attr is absent.
|
|
# Immediate + delayed restart: Dell/Latitude firmware often stomps the
|
|
# threshold to 100 right after unplug; a single immediate oneshot
|
|
# loses the race (hardware: unplug→100, plug→80). systemd-run hands
|
|
# the delayed restart off so udev's short RUN budget is not held.
|
|
services.udev.extraRules = lib.mkIf cfg.laptop (
|
|
let
|
|
systemctl = "${config.systemd.package}/bin/systemctl";
|
|
systemdRun = "${config.systemd.package}/bin/systemd-run";
|
|
in ''
|
|
SUBSYSTEM=="power_supply", ATTR{type}=="Battery", TEST=="charge_control_end_threshold", GROUP="users", MODE="0664"
|
|
SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="${systemctl} --no-block restart nomarchy-battery-charge-limit.service"
|
|
SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="${systemdRun} --no-block --collect --on-active=2s --timer-property=AccuracySec=200ms ${systemctl} --no-block restart nomarchy-battery-charge-limit.service"
|
|
''
|
|
);
|
|
|
|
# Unprivileged restart of the oneshot so the menu can re-apply as
|
|
# root when the threshold node is not (yet) user-writable.
|
|
security.polkit.extraConfig = lib.mkIf cfg.laptop ''
|
|
polkit.addRule(function(action, subject) {
|
|
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
|
subject.isInGroup("users")) {
|
|
var unit = action.lookup("unit");
|
|
if (unit == "nomarchy-battery-charge-limit.service") {
|
|
return polkit.Result.YES;
|
|
}
|
|
}
|
|
});
|
|
'';
|
|
};
|
|
}
|