feat(power): laptop power management (power-profiles-daemon + TLP)

Adds a `nomarchy.system.power.*` surface (modules/nixos/power.nix),
replacing the bare `services.upower` (Waybar reporting only) baseline:

- power-profiles-daemon ships by default; switched through polkit (no
  root prompt). TLP is the opt-in `backend = "tlp"` for deeper battery
  tuning — the two are mutually exclusive (asserted).
- thermald behind `power.thermal.enable` (Intel-only).
- `power.batteryChargeLimit` (e.g. 80): a backend-independent sysfs
  oneshot writes charge_control_end_threshold; gated on `power.laptop`.

Switcher + indicator live home-side and self-gate on a battery being
present + powerprofilesctl running, so they hide on desktops and under
TLP — no system->home wiring (like the Waybar battery widget):

- rofi.nix: `nomarchy-menu power-profile` module + a picker entry shown
  only on laptops with PPD.
- waybar.nix: first `custom/*` module — click cycles the profile.

Installer writes `power.laptop = true` (battery probe) and
`power.thermal.enable` (GenuineIntel) into the generated system.nix, and
scaffolds the charge limit commented-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 17:35:42 +01:00
parent 8853f6ae49
commit 00f5311499
7 changed files with 233 additions and 37 deletions

View File

@@ -77,6 +77,20 @@ let
*Shutdown) systemctl poweroff ;;
esac ;;
power-profile)
# power-profiles-daemon profile switcher (the system side ships it
# by default on laptops nomarchy.system.power). Switching goes
# through polkit, so no root prompt.
if ! command -v powerprofilesctl >/dev/null 2>&1; then
notify-send "Power profiles" "power-profiles-daemon isn't running on this machine."
exit 0
fi
cur=$(powerprofilesctl get 2>/dev/null)
choice=$(powerprofilesctl list 2>/dev/null \
| sed -nE 's/^[* ] ([a-z-]+):$/\1/p' \
| rofi -dmenu -p "profile (now: $cur)") || exit 0
[ -n "$choice" ] && powerprofilesctl set "$choice" ;;
theme)
choice=$(nomarchy-theme-sync list | rofi -dmenu -p theme) || exit 0
[ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;;
@@ -148,20 +162,28 @@ let
exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;;
"")
choice=$(printf '%s\n' \
"󰀻 Apps" \
"󰏘 Theme" \
"󰅌 Clipboard" \
"󰃬 Calculator" \
"󰈞 Files" \
"󰖟 Web search" \
"󰖩 Network" \
"󰂯 Bluetooth" \
"󰄄 Capture" \
"󰋖 Keybindings" \
"󰭹 Ask Claude" \
"󰐥 Power" \
| rofi -dmenu -p menu) || exit 0
entries=(
"󰀻 Apps"
"󰏘 Theme"
"󰅌 Clipboard"
"󰃬 Calculator"
"󰈞 Files"
"󰖟 Web search"
"󰖩 Network"
"󰂯 Bluetooth"
"󰄄 Capture"
"󰋖 Keybindings"
"󰭹 Ask Claude"
)
# Power-profile picker only when this is a laptop (battery present)
# running power-profiles-daemon matches the Waybar indicator's
# self-gating; desktops and the TLP backend don't show it.
bats=(/sys/class/power_supply/BAT*)
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
entries+=("󰓅 Power profile")
fi
entries+=("󰐥 Power")
choice=$(printf '%s\n' "''${entries[@]}" | rofi -dmenu -p menu) || exit 0
case "$choice" in
*Apps*) exec rofi -show drun ;;
*Theme*) exec "$0" theme ;;
@@ -174,11 +196,12 @@ let
*Capture*) exec "$0" capture ;;
*Keybindings*) exec "$0" keybinds ;;
*Ask*) exec "$0" ask ;;
*"Power profile"*) exec "$0" power-profile ;;
*Power*) exec "$0" power ;;
esac ;;
*)
echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web|network|bluetooth|capture|keybinds|ask]" >&2
echo "usage: nomarchy-menu [power|power-profile|theme|clipboard|calc|files|web|network|bluetooth|capture|keybinds|ask]" >&2
exit 64 ;;
esac
'';