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:
@@ -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
|
||||
'';
|
||||
|
||||
@@ -7,11 +7,39 @@
|
||||
# <themesDir>/<slug>/waybar.css (and optionally waybar.jsonc) which
|
||||
# replace the generated style/layout entirely. Probed at eval time —
|
||||
# pure, it's flake source.
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
t = config.nomarchy.theme;
|
||||
|
||||
# Power-profile indicator (power-profiles-daemon). Both scripts self-
|
||||
# gate: they exit silently unless this is a laptop (a battery is
|
||||
# present) running PPD, so the module hides itself on desktops and
|
||||
# under the TLP backend — no system→home coupling. Speedometer glyphs
|
||||
# (slow/medium/fast) map to power-saver/balanced/performance.
|
||||
powerProfileStatus = pkgs.writeShellScript "nomarchy-powerprofile-status" ''
|
||||
case "$(ls /sys/class/power_supply/ 2>/dev/null)" in *BAT*) ;; *) exit 0 ;; esac
|
||||
command -v powerprofilesctl >/dev/null 2>&1 || exit 0
|
||||
prof=$(powerprofilesctl get 2>/dev/null) || exit 0
|
||||
case "$prof" in
|
||||
power-saver) icon="" ;;
|
||||
performance) icon="" ;;
|
||||
*) icon="" ;;
|
||||
esac
|
||||
printf '{"text":"%s","tooltip":"Power profile: %s","class":"%s"}\n' "$icon" "$prof" "$prof"
|
||||
'';
|
||||
# Cycle to the next profile this machine actually offers (wraps around).
|
||||
powerProfileCycle = pkgs.writeShellScript "nomarchy-powerprofile-cycle" ''
|
||||
command -v powerprofilesctl >/dev/null 2>&1 || exit 0
|
||||
cur=$(powerprofilesctl get 2>/dev/null) || exit 0
|
||||
avail=$(powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p')
|
||||
next=$(printf '%s\n' "$avail" | awk -v cur="$cur" '
|
||||
{ a[NR] = $0 }
|
||||
END { for (i = 1; i <= NR; i++) if (a[i] == cur) { print a[i % NR + 1]; f = 1 }
|
||||
if (!f && NR) print a[1] }')
|
||||
[ -n "$next" ] && powerprofilesctl set "$next"
|
||||
'';
|
||||
|
||||
# Per-theme override probe.
|
||||
assetDir = config.nomarchy.themesDir + "/${t.slug}";
|
||||
styleOverride = assetDir + "/waybar.css";
|
||||
@@ -38,7 +66,7 @@ let
|
||||
|
||||
modules-left = [ "hyprland/workspaces" "hyprland/window" ];
|
||||
modules-center = [ "clock" ];
|
||||
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "battery" ];
|
||||
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" "battery" ];
|
||||
|
||||
"hyprland/workspaces" = {
|
||||
format = "{icon}";
|
||||
@@ -83,6 +111,13 @@ let
|
||||
format-icons = [ "" "" "" "" "" ];
|
||||
};
|
||||
|
||||
"custom/powerprofile" = {
|
||||
exec = "${powerProfileStatus}";
|
||||
return-type = "json";
|
||||
interval = 5;
|
||||
on-click = "${powerProfileCycle}";
|
||||
};
|
||||
|
||||
tray.spacing = 8;
|
||||
};
|
||||
|
||||
@@ -129,7 +164,7 @@ let
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#tray, #pulseaudio, #network, #cpu, #memory, #battery {
|
||||
#tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #battery {
|
||||
color: @subtext;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user