From 1143e67f9578dd61dd7b6a2510a943414aa015ed Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Wed, 8 Jul 2026 14:40:40 +0100 Subject: [PATCH] feat(waybar): combined power menu on the battery + profile icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Item 36(b). Adds `nomarchy-menu powermgmt` — one flat rofi menu of power Profile rows (→ powerprofilesctl set) + Charge limit rows (→ the same theme-sync writer batterylimit uses), each half self-gating on its hardware. No backend duplication; Custom charge stays in the dedicated batterylimit picker. Opened two ways (Bernardo's call): the Waybar battery icon AND the power-profile icon — the profile icon's click flips from cycle → menu. Wired in waybar.nix and all four whole-swaps (parity). Named `powermgmt` to avoid the whole-swaps' existing `custom/powermenu` shutdown button (→ nomarchy-menu power), which is left untouched. The granular System ▸ Power profile / Battery limit rows stay ("keep both"). nomarchy-powerprofile-cycle kept as a standalone bin, now unbound by default. Verified: V0 green; V1 HM built (bash -n) — powermgmt case ships, the generated waybar-config.json wires both icons (2 hits), all four jsoncs re-wired and still parse, shutdown buttons intact. Click→menu→apply is V3 (queued). Co-Authored-By: Claude Opus 4.8 --- agent/BACKLOG.md | 23 ------------- agent/HARDWARE-QUEUE.md | 10 ++++++ agent/JOURNAL.md | 25 ++++++++++++++ modules/home/rofi.nix | 52 ++++++++++++++++++++++++++++- modules/home/waybar.nix | 8 ++++- themes/boreal/waybar.jsonc | 4 +-- themes/executive-slate/waybar.jsonc | 4 +-- themes/summer-day/waybar.jsonc | 4 +-- themes/summer-night/waybar.jsonc | 4 +-- 9 files changed, 101 insertions(+), 33 deletions(-) diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index 27350c5..f752c10 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -26,29 +26,6 @@ next, in what order*. ## NEXT -### 36. Battery / power quick menu `[human]` -(Raised by Bernardo, 2026-07-07 — item 6.) Slice (a) — the charge-threshold -toggle in the rofi System submenu — shipped 2026-07-07 (iteration #64; -`nomarchy-menu batterylimit`, self-gated on the sysfs node, and MOVED out -of the gum control-center). Remaining: -- (b) **Battery-icon click → a dedicated power menu** offering charge - threshold + power profile. Mockup provided by Bernardo 2026-07-08 (a - `power-menu` rofi script from another system): a `rofi -dmenu` with - icon'd rows via the `\x00icon\x1f` protocol — "Charge to 100%", - "Limit to 80%" (battery-*-charging-symbolic icons) + power-profile rows - (power-profile-{power-saver,performance}-symbolic), each `case` firing a - backend command + `notify-send`. The submitted script drives **TLP** - (`tlp fullcharge` / `tlp setcharge 75 80 BAT0`) and ryzenadj (ignore the - ryzenadj half). **Nomarchy adaptation:** reuse the existing writers, not - raw TLP — charge rows call `nomarchy-menu batterylimit`'s logic - (sysfs `charge_control_end_threshold` + baked `settings.power. - batteryChargeLimit`), profile rows call `power-profile`'s (PPD). Wire the - Waybar **battery module `on-click`** (generated `waybar.nix` + all four - whole-swap jsoncs — parity) to a new `nomarchy-menu powermenu`. Open - call left: whether to *replace* the separate System ▸ Battery limit / - Power profile rows or keep both surfaces. Pairs with the instant-effect - follow-up in PROPOSED (a udev grant makes the charge write no-rebuild). - ### 29. Laptop docking / external-monitor UX `[human]` (needs hardware) (Raised by Bernardo, 2026-07-07.) The shipped display application is weak and unintuitive, and Hyprland's model for workspaces across diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index a8c6a35..a0fe1ca 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -282,6 +282,16 @@ QA machine), the **T14s** (webcam case). shows a single arrow, no double; (c) on this single-monitor box, System › Display → pick a resolution → Back returns to System (not back into the same resolution list). +- [ ] **Combined power menu** (iteration #67, item 36b) — after + `home-update` + relogin: clicking EITHER the Waybar battery icon OR + the power-profile icon opens one "Power" menu listing Profile: rows + + Charge limit: rows; picking a profile calls `powerprofilesctl set` + (verify `powerprofilesctl get` changes), picking a charge preset + writes `settings.power.batteryChargeLimit` (applies next rebuild). + The prompt shows the current profile + limit. System ▸ Power profile + / Battery limit still work directly; the whole-swap bars (summer-day/ + night, executive-slate, boreal) behave the same and their shutdown + power-button (if present) still opens the lock/logout menu. ## AMD dev box only - [ ] **AMD runtime bits** — VA-API (`vainfo` → radeonsi), amd-pstate EPP diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index f7460f3..89e4d63 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -17,6 +17,31 @@ Template: --- +## 2026-07-08 — Combined power menu on the bar icons (iteration #67, item 36b) +- **Task:** BACKLOG item 36(b), now unblocked. Bernardo's calls: keep the + granular System ▸ Battery limit / Power profile rows; build ONE combined + menu; open it two ways — the Waybar **battery** icon AND the + **power-profile** icon (the profile icon's click flips from cycle → menu). +- **Did:** New `nomarchy-menu powermgmt` (rofi.nix): a flat list of Profile + rows (→ `powerprofilesctl set`, like the power-profile picker) + Charge + limit rows (→ the same theme-sync writer as batterylimit), each half + self-gating on its hardware; no backend duplication, no Custom flow (that + stays in the dedicated batterylimit picker). Wired `battery.on-click` + + `custom/powerprofile.on-click` → `powermgmt` in waybar.nix AND all four + whole-swaps (parity). Named `powermgmt` to avoid the whole-swaps' existing + `custom/powermenu` (that's the shutdown button → `nomarchy-menu power`, + left untouched). `nomarchy-powerprofile-cycle` kept as a standalone bin + (now just unbound by default). +- **Verified:** V0 green; V1 HM built (bash -n passed) — shipped + nomarchy-menu has the `powermgmt` case; generated waybar-config.json wires + both icons to it (2 hits); all four whole-swap jsoncs re-wired and still + parse as JSON, shutdown buttons intact. +- **Pending:** V3 — click either icon → menu opens, picking a profile / + charge preset actually applies (queued in HARDWARE-QUEUE). The + instant-charge-write follow-up stays PROPOSED/`[blocked:hw]`. +- **Next suggestion:** back to the queue — item 33 (rofi scrollbar) or a + QA sweep; both remaining NEXT items lean human/hardware. + ## 2026-07-08 — Capture rows show Print-key hints (iteration #66, direct requests) - **Task:** Two live requests from Bernardo. (1) Fold his provided power-menu mockup into item 36(b). (2) "make sure all menu items that diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index c52fed9..e974afd 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -256,6 +256,56 @@ let [ "$choice" = "$BACK" ] && exec "$0" system [ -n "$choice" ] && powerprofilesctl set "$choice" ;; + powermgmt) + # Combined power menu (item 36b) — power PROFILE + battery CHARGE cap + # in one flat list, opened from BOTH the Waybar battery and + # power-profile icons (the two granular System ▸ Power profile / + # Battery limit rows stay for direct access). No new backend: profile + # rows drive powerprofilesctl like the power-profile picker, charge + # rows drive the same theme-sync writer as batterylimit. Each half + # self-gates on its hardware, so a desktop with neither just toasts. + bats=(/sys/class/power_supply/BAT*) + haveppd=false; command -v powerprofilesctl >/dev/null 2>&1 && haveppd=true + havecharge=false; [ -e "''${bats[0]}/charge_control_end_threshold" ] && havecharge=true + if ! $haveppd && ! $havecharge; then + notify-send "Power" "No power-profile or charge-limit control on this machine." + exit 0 + fi + curp=""; $haveppd && curp=$(powerprofilesctl get 2>/dev/null) + curc=$(nomarchy-theme-sync get settings.power.batteryChargeLimit 2>/dev/null) + choice=$( { + if $haveppd; then + powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p' \ + | while IFS= read -r p; do + case "$p" in + performance) row "Profile: $p" battery-profile-performance ;; + balanced) row "Profile: $p" battery-profile-balanced ;; + power-saver) row "Profile: $p" battery-profile-powersave ;; + *) row "Profile: $p" preferences-system-power ;; + esac + done + fi + if $havecharge; then + row "Charge limit: 80% (recommended)" battery-080 + row "Charge limit: 90%" battery-090 + row "Charge limit: 60%" battery-060 + row "Charge limit: Off (100%)" battery-100 + fi + back + } | rofi -dmenu -show-icons -p "Power — profile: ''${curp:-n/a}, limit: ''${curc:-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." + } + case "$choice" in + "$BACK") exec "$0" ;; + "Profile: "*) powerprofilesctl set "''${choice#Profile: }" ;; + "Charge limit: 80%"*) set_limit 80 "Set to 80%" ;; + "Charge limit: 90%"*) set_limit 90 "Set to 90%" ;; + "Charge limit: 60%"*) set_limit 60 "Set to 60%" ;; + "Charge limit: Off"*) set_limit null "Off — charges to 100%" ;; + esac ;; + theme) # Visual picker: a grid of real desktop previews (rows + Name→slug # map generated from the presets at build time). Pick a card → apply @@ -790,7 +840,7 @@ ${themeRows} esac ;; *) - echo "usage: nomarchy-menu [lookfeel|tools|system|power|power-profile|batterylimit|theme|clipboard|calc|files|emoji|web|network|bluetooth|audio|display|display-profile|printers|capture|colorpicker|keybinds|ask|dnd|nightlight|autotimezone|autocommit|vpn|snapshot]" >&2 + echo "usage: nomarchy-menu [lookfeel|tools|system|power|power-profile|powermgmt|batterylimit|theme|clipboard|calc|files|emoji|web|network|bluetooth|audio|display|display-profile|printers|capture|colorpicker|keybinds|ask|dnd|nightlight|autotimezone|autocommit|vpn|snapshot]" >&2 exit 64 ;; esac ''; diff --git a/modules/home/waybar.nix b/modules/home/waybar.nix index 6db7803..5da16bc 100644 --- a/modules/home/waybar.nix +++ b/modules/home/waybar.nix @@ -199,13 +199,19 @@ let format = "{icon} {capacity}%"; format-charging = "󰂄 {capacity}%"; format-icons = [ "󰁺" "󰁼" "󰁾" "󰂀" "󰁹" ]; + # Click either the battery or the power-profile icon → the combined + # power menu (profile + charge cap). The granular System rows stay. + on-click = "nomarchy-menu powermgmt"; }; "custom/powerprofile" = { exec = "nomarchy-powerprofile-status"; return-type = "json"; interval = 5; - on-click = "nomarchy-powerprofile-cycle"; + # Opens the same combined power menu as the battery icon (item 36b). + # nomarchy-powerprofile-cycle stays a standalone bin for downstream + # rebinding — it's just no longer the default click. + on-click = "nomarchy-menu powermgmt"; }; # VPN shield — self-hides unless a NM VPN/WireGuard tunnel or Tailscale is diff --git a/themes/boreal/waybar.jsonc b/themes/boreal/waybar.jsonc index c32c384..30c1f05 100644 --- a/themes/boreal/waybar.jsonc +++ b/themes/boreal/waybar.jsonc @@ -135,14 +135,14 @@ "", "" ], - "on-click": "nomarchy-menu power", + "on-click": "nomarchy-menu powermgmt", "tooltip-format": "Battery status" }, "custom/powerprofile": { "return-type": "json", "interval": 5, "exec": "nomarchy-powerprofile-status", - "on-click": "nomarchy-powerprofile-cycle" + "on-click": "nomarchy-menu powermgmt" }, "tray": { "icon-size": 15, diff --git a/themes/executive-slate/waybar.jsonc b/themes/executive-slate/waybar.jsonc index 83e8b33..40a86db 100644 --- a/themes/executive-slate/waybar.jsonc +++ b/themes/executive-slate/waybar.jsonc @@ -111,7 +111,7 @@ "return-type": "json", "interval": 5, "exec": "nomarchy-powerprofile-status", - "on-click": "nomarchy-powerprofile-cycle" + "on-click": "nomarchy-menu powermgmt" }, "battery": { "interval": 30, @@ -129,7 +129,7 @@ "", "" ], - "on-click": "nomarchy-menu power", + "on-click": "nomarchy-menu powermgmt", "tooltip-format": "Battery status" }, "custom/vpn": { diff --git a/themes/summer-day/waybar.jsonc b/themes/summer-day/waybar.jsonc index 7c02928..6af034f 100644 --- a/themes/summer-day/waybar.jsonc +++ b/themes/summer-day/waybar.jsonc @@ -64,7 +64,7 @@ "format-plugged": " {capacity}%", "format-full": " 100%", "format-icons": ["", "", "", "", ""], - "on-click": "nomarchy-menu power", + "on-click": "nomarchy-menu powermgmt", "tooltip-format": "Battery status" }, @@ -85,7 +85,7 @@ "return-type": "json", "interval": 5, "exec": "nomarchy-powerprofile-status", - "on-click": "nomarchy-powerprofile-cycle" + "on-click": "nomarchy-menu powermgmt" }, "custom/recording": { diff --git a/themes/summer-night/waybar.jsonc b/themes/summer-night/waybar.jsonc index c5fe434..a3ec3e5 100644 --- a/themes/summer-night/waybar.jsonc +++ b/themes/summer-night/waybar.jsonc @@ -63,7 +63,7 @@ "format-charging": " {capacity}%", "format-plugged": " {capacity}%", "format-icons": ["", "", "", "", ""], - "on-click": "nomarchy-menu power", + "on-click": "nomarchy-menu powermgmt", "tooltip-format": "Battery status" }, @@ -84,7 +84,7 @@ "return-type": "json", "interval": 5, "exec": "nomarchy-powerprofile-status", - "on-click": "nomarchy-powerprofile-cycle" + "on-click": "nomarchy-menu powermgmt" }, "custom/recording": {