feat: live charge limit, identity retunes, btop assets (product calls)
All checks were successful
Check / eval (push) Successful in 3m0s
All checks were successful
Check / eval (push) Successful in 3m0s
- Charge limit: udev GROUP=users on threshold; menu applies sysfs live and persists state; boot/AC oneshot prefers live theme-state.json. - Look & Feel: Reset wallpaper (auto); Theme/next/night light already there. - NVIDIA first-class wrappers deferred past v1 (keep #59 comments). - Identity taste: white, lumon, hackerman, matte-black, miasma. - Hand btop.theme for identity + boreal/executive-slate/neon-glass. Verified: V0 contrast; V1 battery-charge-limit VM. V3 live sysfs on laptop.
This commit is contained in:
@@ -314,9 +314,18 @@ let
|
||||
fi
|
||||
back
|
||||
} | rofi -dmenu -show-icons -p "Power — profile: ''${curp:-n/a}, limit: ''${curc:-default}") || exit 0
|
||||
# Persist in-flake + apply live to sysfs (udev makes the node
|
||||
# group-writable for users — Bernardo 2026-07-10).
|
||||
set_limit() {
|
||||
nomarchy-theme-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
|
||||
notify-send "Battery limit" "$2 — applies on the next rebuild."
|
||||
n="$1"; [ "$n" = null ] && n=100
|
||||
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"
|
||||
[ -w "$thresh" ] && echo "$n" > "$thresh" 2>/dev/null || true
|
||||
done
|
||||
notify-send "Battery limit" "$2 — applied now."
|
||||
}
|
||||
case "$choice" in
|
||||
"$BACK") exec "$0" ;;
|
||||
@@ -530,12 +539,13 @@ ${themeRows}
|
||||
esac ;;
|
||||
|
||||
lookfeel)
|
||||
# Look & Feel — appearance grouped (item 19): the theme picker,
|
||||
# wallpaper cycling, night light. The root stays six rows; the
|
||||
# SUPER+T / SUPER+SHIFT+T direct binds still hit the leaves.
|
||||
# Look & Feel — appearance: Theme, wallpapers, night light.
|
||||
# Root stays six entries (Apps / Look & Feel / Tools / System /
|
||||
# Power / Keybindings). SUPER+T / SUPER+SHIFT+T still hit leaves.
|
||||
choice=$( {
|
||||
row "Theme${menuHint "theme"}" preferences-desktop-theme
|
||||
row "Next wallpaper${hintForAction "exec, nomarchy-theme-sync bg next"}" preferences-desktop-wallpaper
|
||||
row "Reset wallpaper (auto)" preferences-desktop-wallpaper
|
||||
if systemctl --user is-active --quiet hyprsunset.service 2>/dev/null
|
||||
then row "Night light (on)" weather-clear-night
|
||||
else row "Night light (off)" weather-clear-night
|
||||
@@ -545,7 +555,8 @@ ${themeRows}
|
||||
case "$choice" in
|
||||
"$BACK") exec "$0" ;;
|
||||
*Theme*) exec "$0" theme ;;
|
||||
*wallpaper*) exec nomarchy-theme-sync bg next ;;
|
||||
*"Next wallpaper"*) exec nomarchy-theme-sync bg next ;;
|
||||
*"Reset wallpaper"*) exec nomarchy-theme-sync bg auto ;;
|
||||
*"Night light"*) exec "$0" nightlight ;;
|
||||
esac ;;
|
||||
|
||||
@@ -903,10 +914,8 @@ ${themeRows}
|
||||
# Battery charge-limit preset picker — the rofi-System counterpart of
|
||||
# the control-center's toggle (Bernardo: he wanted this in the menu,
|
||||
# not buried in the gum TUI). Writes the baked NixOS option
|
||||
# settings.power.batteryChargeLimit via theme-sync (--no-switch), so
|
||||
# it lands on the next sys-rebuild — the sysfs oneshot in power.nix
|
||||
# owns applying it (boot + AC-replug). Self-gated in System; guard
|
||||
# here too in case it's invoked directly.
|
||||
# Persist + live sysfs apply (same as powermgmt). Boot/AC re-apply
|
||||
# stays with the oneshot in power.nix.
|
||||
has_charge_threshold \
|
||||
|| { notify-send "Battery limit" "No charge-threshold control on this machine."; exit 0; }
|
||||
cur=$(nomarchy-theme-sync get settings.power.batteryChargeLimit 2>/dev/null)
|
||||
@@ -920,7 +929,14 @@ ${themeRows}
|
||||
} | rofi -dmenu -show-icons -p "Battery limit (now: ''${cur:-default})") || exit 0
|
||||
set_limit() {
|
||||
nomarchy-theme-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
|
||||
notify-send "Battery limit" "$2 — applies on the next rebuild."
|
||||
n="$1"; [ "$n" = null ] && n=100
|
||||
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"
|
||||
[ -w "$thresh" ] && echo "$n" > "$thresh" 2>/dev/null || true
|
||||
done
|
||||
notify-send "Battery limit" "$2 — applied now."
|
||||
}
|
||||
case "$sel" in
|
||||
"$BACK") exec "$0" system ;;
|
||||
|
||||
@@ -109,9 +109,10 @@
|
||||
where the hardware exposes charge_control_end_threshold on a
|
||||
system battery (type=Battery under /sys/class/power_supply;
|
||||
name-agnostic — BAT0, CMB0, …).
|
||||
null leaves charging at the firmware default. Backend-independent
|
||||
(a small systemd unit writes the sysfs knob, re-applied on AC
|
||||
state changes), so it works under PPD too; needs
|
||||
null leaves charging at the firmware default (menu writes 100).
|
||||
Backend-independent: the menu applies live via sysfs (udev
|
||||
GROUP=users on the threshold node) and persists settings in
|
||||
theme-state; a oneshot re-applies on boot and AC replug. Needs
|
||||
nomarchy.system.power.laptop.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
# 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, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
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 {
|
||||
@@ -40,36 +39,51 @@ in
|
||||
# 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}%";
|
||||
# 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 = ''
|
||||
# Name-agnostic (BAT0, CMB0, …): same type/scope filter as
|
||||
# nomarchy-battery-notify — BACKLOG #60.
|
||||
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"
|
||||
[ -w "$thresh" ] && echo ${toString cfg.batteryChargeLimit} > "$thresh"
|
||||
[ -w "$thresh" ] && echo "$limit" > "$thresh" || true
|
||||
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 ''
|
||||
# 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.
|
||||
services.udev.extraRules = lib.mkIf cfg.laptop ''
|
||||
SUBSYSTEM=="power_supply", ATTR{type}=="Battery", TEST=="charge_control_end_threshold", GROUP="users", MODE="0664"
|
||||
SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="${config.systemd.package}/bin/systemctl --no-block restart nomarchy-battery-charge-limit.service"
|
||||
'';
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user