Files
Nomarchy/modules/home/waybar.nix
Bernardo Magri d79ad3517e fix(waybar): #152 — inset the workspace pill, breathe at the bar's ends
The generated bar (every non-whole-swap theme; whole-swaps untouched):

- The active-workspace highlighter filled the bar's full inner height
  and visually overhung the rounded border. The pill now gets a 3px
  vertical inset margin (the boreal whole-swap's proven pattern) and
  the bar grows 34 -> 36 so the inset doesn't cramp the number.
- The end modules sat hard against the bar's rounded corners (the
  power button ~8px from the right curve vs ~28px inter-icon gaps).
  The Nomarchy mark and the power button get wider outer padding
  (14/16px); the item-28c group rhythm is preserved.

Verification: V2. nix flake check --no-build green. §3 protocol:
before/after theme-shot under kanagawa (generated palette) viewed at
2x crops — before: pill overhangs top+bottom, power glyph kisses the
corner; after: pill fully inside the bar, clear end gap
(before /nix/store/iwrvzzdd...theme-shot-kanagawa, after
/nix/store/za...see scratchpad shot-kanagawa-after.path). No-regression
capture under the summer-night whole-swap viewed — its chip identity
unchanged (whole-swaps replace both style and layout, out of scope
per the item). BACKLOG #152 closed: deleted per the finished-items
rule in the next bookkeeping touch of this push.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:22:52 +01:00

581 lines
25 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Waybar — two-tier theming:
#
# 1. Default: structure, fonts, geometry AND palette baked from
# state.json (colors as GTK named colors, @define-color).
#
# 2. Whole-swap: themes with their own visual identity ship
# <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, pkgs, ... }:
let
t = config.nomarchy.theme;
termSheet = import ./term-sheet.nix { inherit pkgs; };
# Whether the keyboard-layout indicator is worth bar space, and the filter
# that holds a whole-swap to the same answer. Contract + rationale (and
# checks.waybar-language) live in ./waybar-language.nix.
langLib = import ./waybar-language.nix { inherit lib; };
showLanguage = langLib.show {
layout = config.nomarchy.keyboard.layout;
devices = config.nomarchy.keyboard.devices;
remembered = config.nomarchy.settings.keyboard.devices or { };
};
gateLanguage = langLib.gate showLanguage;
# 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.
#
# Named writeShellScriptBins (put on PATH via home.packages below) rather
# than bare writeShellScript store paths, so the whole-swap themes' static
# waybar.jsonc can exec them by name too — same as the swaync bell.
# Waybar supervisor — exec-once has no restart, so a crashed bar used to
# leave the session bar-less until relogin (seen on hardware: a theme
# switch crashed waybar mid-reload). Respawns on ANY exit — a plain
# `pkill -x waybar` is now a clean restart, which nomarchy-state-sync
# uses instead of the crash-prone in-place SIGUSR2 reload when it sees
# this supervisor running. Crash-loop guard: 5 exits within 10s of
# their start → give up with a critical notification instead of
# spinning. Stop the bar for real: pkill -f nomarchy-waybar (TERM is
# trapped to take the child down too).
waybarSupervisor = pkgs.writeShellScriptBin "nomarchy-waybar" ''
child=
trap '[ -n "$child" ] && kill "$child" 2>/dev/null; exit 0' TERM INT
fails=0
while :; do
start=$(date +%s)
waybar & child=$!
wait "$child"; code=$?
if [ $(( $(date +%s) - start )) -lt 10 ]; then
fails=$((fails + 1))
if [ "$fails" -ge 5 ]; then
notify-send -u critical "Waybar" \
"Crashing on start (exit $code) check ~/.config/waybar. Giving up." 2>/dev/null
exit 1
fi
else
fails=0
fi
sleep 1
done
'';
powerProfileStatus = pkgs.writeShellScriptBin "nomarchy-powerprofile-status" ''
# Name-agnostic system battery (BAT0, CMB0, ) BACKLOG #60.
has_bat=
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
has_bat=1; break
done
[ -n "$has_bat" ] || exit 0
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.writeShellScriptBin "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"
'';
# Opens calcurse (lightweight TUI calendar) in a floating, centered kitty
# window — Waybar clock on-click. Distinct --class → hyprland.nix float
# rules. Kitty is always installed (kitty.nix). The sheet sizes itself
# (#139: a percentage `size` rule is silently ignored) — see term-sheet.nix.
calendarLauncher = pkgs.writeShellScriptBin "nomarchy-calendar" ''
if ! command -v calcurse >/dev/null 2>&1; then
notify-send "Calendar" "calcurse isn't installed (removed from home.packages?)." 2>/dev/null
exit 0
fi
exec ${termSheet}/bin/nomarchy-term-sheet com.nomarchy.calendar 60 65 calcurse
'';
# VPN indicator — shows a shield when a NetworkManager VPN/WireGuard
# connection is active OR Tailscale is up; prints nothing otherwise so the
# module self-hides (like nightlight/updates). The tooltip names each
# active VPN (NM connection names, "Tailscale") — without it a forgotten
# tailscaled reads as "the icon is stuck". Click opens the VPN submenu.
vpnStatus = pkgs.writeShellScriptBin "nomarchy-vpn-status" ''
names=$(nmcli -t -f NAME,TYPE connection show --active 2>/dev/null \
| awk -F: '$2=="vpn"||$2=="wireguard"{print $1}')
ts=""
if command -v tailscale >/dev/null 2>&1; then
[ "$(tailscale status --json 2>/dev/null | jq -r '.BackendState // empty')" = Running ] && ts=Tailscale
fi
[ -n "$names$ts" ] || exit 0
list=$(printf '%s\n' "$names" "$ts" | grep -v '^$' | paste -sd ', ' -)
tip=$(printf 'VPN: %s\n(click to manage)' "$list" | jq -Rs 'rtrimstr("\n")')
printf '{"text":"󰦝","tooltip":%s,"class":"on"}\n' "$tip"
'';
# Health warning — self-gates: prints nothing while nomarchy-doctor
# exits 0, so the module only appears when the sheet has a ✖ (the
# same self-hide discipline as vpn/nightlight/updates). The tooltip
# carries the first failing lines; click opens the full sheet.
# Absolute path to the doctor binary: waybar's custom-module env can
# miss system PATH, and `command -v … || exit 0` then self-hides forever.
doctorStatus = pkgs.writeShellScriptBin "nomarchy-doctor-status" ''
out=$(${pkgs.nomarchy-doctor}/bin/nomarchy-doctor 2>/dev/null) && exit 0
# Strip ANSI so the tooltip is plain text (and waybar never chokes on
# control chars); keep only the lines.
tip=$(printf '%s\n' "$out" \
| ${pkgs.gnused}/bin/sed 's/\x1b\[[0-9;]*m//g' \
| grep '' | head -5 \
| ${pkgs.jq}/bin/jq -Rs 'rtrimstr("\n") + "\n(click for the full sheet)"')
# 󰀨 (md-alert-circle): alert shape that survives incomplete Nerd Font
# cuts better than 󰒡; class:bad still paints it @bad.
printf '{"text":"󰀨","tooltip":%s,"class":"bad"}\n' "$tip"
'';
# Per-theme override probe.
assetDir = config.nomarchy.themesDir + "/${t.slug}";
styleOverride = assetDir + "/waybar.css";
configOverride = assetDir + "/waybar.jsonc";
hasStyleOverride = builtins.pathExists styleOverride;
hasConfigOverride = builtins.pathExists configOverride;
# The palette as GTK named colors, straight from the JSON.
colorDefs = lib.concatStringsSep "\n"
(lib.mapAttrsToList (name: value: "@define-color ${name} ${value};") t.colors);
generatedSettings = {
# `bottom`, not `top`: on Hyprland the `top` layer renders above every
# window — so a real fullscreen surface (a browser video gone
# fullscreen) sits *under* the bar. On `bottom` the fullscreen window
# covers the bar, while the exclusive zone still reserves its space in
# normal tiling. Keep in sync with the whole-swap jsoncs (parity rule).
layer = "bottom";
position = "top";
# 36, not 34: the active-workspace pill is inset 3px top+bottom (CSS
# below) so it can never overhang the bar's border — the extra height
# keeps the pill's text from cramping inside the inset (#152).
height = 36;
margin-top = t.ui.gapsOut;
margin-left = t.ui.gapsOut;
margin-right = t.ui.gapsOut;
spacing = 8;
# waybar re-reads style.css when it changes on disk, so a
# home-manager switch restyles the running bar without a restart.
reload_style_on_change = true;
# Logo + powermenu: whole-swaps already ship these (parity was reverse
# — BACKLOG #63). Click targets are existing nomarchy-menu entry points.
modules-left = [ "custom/nomarchy" "hyprland/workspaces" "hyprland/window" ];
modules-center = [ "clock" ];
modules-right = [ "custom/recording" "idle_inhibitor" "tray" "custom/vpn" "custom/airplane" "pulseaudio" "custom/powerprofile" "custom/nightlight" ]
++ lib.optional showLanguage "hyprland/language"
++ [ "battery" "custom/doctor" "custom/updates" "custom/notification" "custom/powermenu" ];
"custom/nomarchy" = {
interval = "once";
# U+F000 — Nomarchy monogram (literal UTF-8; Nix has no \u escapes).
# CSS pins font-family: Nomarchy so Nerd Fonts' glass glyph does not win.
format = "";
on-click = "nomarchy-menu";
tooltip-format = "Nomarchy menu";
};
"custom/powermenu" = {
format = ""; # U+F011 power symbol
on-click = "nomarchy-menu power";
tooltip = false;
};
"hyprland/workspaces" = {
format = "{icon}";
on-click = "activate";
};
"hyprland/window" = {
max-length = 48;
separate-outputs = true;
};
clock = {
# Single module: time + short date (whole-swaps used to split these
# into clock + clock#date — one click surface for the calendar).
format = "{:%H:%M · %a %d %b}";
# Left-click → the calendar (nomarchy-calendar → calcurse in a floating
# kitty). Tooltip is plain date/zone only — embedding {calendar}
# produced an empty popup on hardware (2026-07-10); month view is
# one click away.
on-click = "nomarchy-calendar";
tooltip = true;
# One chrono block only: waybar/fmt empties the tooltip when two
# bare `{:%…}` specs are concatenated (bar format works because it
# is a single block). Newline + zone live under auto-timezone
# (tz-watch SIGUSR2 reload).
tooltip-format = "{:%A, %d %B %Y\n%Z (UTC%z)}";
};
# Active keyboard layout (per focused device) — only placed in
# modules-right when showLanguage (see above).
"hyprland/language" = {
# Two spaces after the glyph: the keyboard icon's right bearing
# otherwise kisses the layout code (esp. mono Nerd faces).
format = "<span size='${toString (t.fonts.size + 2)}pt'>󰌌 </span>{short}";
tooltip = false;
};
pulseaudio = {
# Two trailing spaces on every icon level: high-volume glyphs are the
# widest and still kissed the % with a single pad (hardware report).
format = "<span size='${toString (t.fonts.size + 2)}pt'>{icon}</span>{volume}%";
format-muted = "󰝟";
format-icons.default = [ "󰕿 " "󰖀 " "󰕾 " ];
on-click = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
# Right-click → the full mixer (per-app volumes) in a floating window
# (item 35). pwvucontrol ships in the template suite; the Hyprland
# windowrule floats it. Keep in sync with the whole-swap jsoncs.
on-click-right = "pwvucontrol";
};
# Idle inhibitor (caffeine): click 󰾪 → 󰅶 to hold the screen awake —
# blocks hypridle's lock / display-off / suspend during video or a
# presentation. Waybar holds a Wayland idle-inhibit while activated;
# the state resets with the bar (deliberate — caffeine shouldn't
# survive a relogin). Summer-night's whole-swap bar had this first
# (reverse parity gap, 2026-07-04).
idle_inhibitor = {
format = "{icon}";
format-icons = { activated = "󰅶"; deactivated = "󰾪"; };
tooltip-format-activated = "Screen held awake click to release";
tooltip-format-deactivated = "Keep the screen awake (caffeine)";
};
# No network module: nm-applet lives in the tray (the GUI path), so the
# bar's wifi/ethernet indicator would just duplicate it.
battery = {
states = { warning = 25; critical = 10; };
# Same double-pad as pulseaudio (single space still tight on some glyphs).
format = "<span size='${toString (t.fonts.size + 2)}pt'>{icon}</span>{capacity}%";
format-charging = "<span size='${toString (t.fonts.size + 2)}pt'>󰂄 </span>{capacity}%";
format-icons = [ "󰁺 " "󰁼 " "󰁾 " "󰂀 " "󰁹 " ];
# The kernel's "Not charging" maps to waybar's Plugged state — on a
# machine with a charge cap that's the hold band doing its job
# (power.nix sets start = cap 10), which reads as "broken charger"
# without an explanation. The cap value isn't baked here: the menu
# can change it live (sysfs + state, no rebuild), so a number would
# go stale.
tooltip-format-plugged = "Plugged in, not charging held at {capacity}% by the battery charge cap\nCharging resumes ~10% below the cap · click to adjust";
# 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;
# 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
# up. Click opens the VPN submenu (nomarchy-vpn). 5s poll like powerprofile.
"custom/vpn" = {
exec = "nomarchy-vpn-status";
return-type = "json";
interval = 5;
on-click = "nomarchy-vpn";
};
# Health check is real work (systemctl/disk/git sweeps), so a long
# interval — this is a tripwire, not a monitor.
"custom/doctor" = {
exec = "nomarchy-doctor-status";
return-type = "json";
format = "{}";
interval = 300;
# signal 10: poke after a fix (or from theme-shot) so the tripwire
# doesn't wait a full interval after a first-poll miss.
signal = 10;
on-click = "nomarchy-menu doctor";
};
# Screen recording indicator. Self-gates: visible only while
# nomarchy-record runs (status prints nothing otherwise), and the
# click IS the stop surface — the menu only starts recordings.
# signal 8: the recorder pokes the bar on start/stop so the ⏺
# appears/vanishes instantly (the interval is just a safety net).
"custom/recording" = {
exec = "nomarchy-record status";
return-type = "json";
interval = 10;
signal = 8;
on-click = "nomarchy-record stop";
};
# Night-light (hyprsunset) indicator. Self-gates: the moon shows only while
# the schedule runs; otherwise the status helper prints nothing => hidden
# (enable / re-enable from the System menu). Click toggles instantly — writes
# the in-flake on/off (settings.nightlight.on, no rebuild) and flips the unit
# with systemctl, so the choice lands in the flake and survives reboot.
"custom/nightlight" = {
exec = "nomarchy-nightlight status";
return-type = "json";
interval = 3;
on-click = "nomarchy-nightlight toggle";
};
# Airplane mode (#104). Self-gates: plane glyph only while engaged
# (status prints nothing otherwise). Click toggles Wi-Fi+BT and
# restores prior radio state on disengage. signal 11 = instant refresh.
"custom/airplane" = {
exec = "nomarchy-airplane status";
return-type = "json";
interval = 5;
signal = 11;
on-click = "nomarchy-airplane toggle";
};
# Update awareness. Self-gates: hidden unless nomarchy.updates is enabled
# AND the periodic check found something (the helper prints nothing then).
# signal 9 lets the checker refresh it instantly; click opens the upgrade
# flow in a terminal.
"custom/updates" = {
exec = "nomarchy-updates status";
return-type = "json";
interval = 1800;
signal = 9;
on-click = "nomarchy-updates upgrade-window";
};
# swaync notification bell + Do-Not-Disturb state. `-swb` streams JSON
# (text/tooltip/class) on every change, so it tracks count and DND with
# no polling. Left-click toggles the panel; right-click toggles DND.
# Every *suppressed* state — DND or app-inhibited — maps to the bell-off
# glyph 󰂛 and the muted color below, so "notifications are off right now"
# reads by SHAPE, not color alone (color-blind sweep, item 28): inhibited
# used to reuse the normal 󰂜/󰂚 bells, distinguishable only by color.
"custom/notification" = {
exec = "swaync-client -swb";
return-type = "json";
exec-if = "which swaync-client";
format = "{icon}";
format-icons = {
none = "󰂜";
notification = "󰂚";
dnd-none = "󰂛";
dnd-notification = "󰂛";
inhibited-none = "󰂛";
inhibited-notification = "󰂛";
dnd-inhibited-none = "󰂛";
dnd-inhibited-notification = "󰂛";
};
on-click = "swaync-client -t -sw";
on-click-right = "swaync-client -d -sw";
tooltip = true;
escape = true;
};
tray.spacing = 8;
};
generatedStyle = ''
/* Palette baked from state.json */
${colorDefs}
/* NB: this `*` reset reaches the SNI tray menus Waybar hosts too
(its stylesheet applies process-wide, at a priority user gtk.css
cannot out-rank). The menu block right below undoes the damage
keep the two in sync. Do NOT scope `*` to window#waybar instead:
the id's specificity would beat the class rules below and wreck
the bar. Whole-swap theme waybar.css files carry the same pair. */
* {
font-family: "${t.fonts.ui}", "${t.fonts.mono}";
font-size: ${toString t.fonts.size}pt;
min-height: 0;
}
/* Tray menus: undo the `*` reset same stylesheet, higher
specificity. Arrows/checks/separators are CSS-sized nodes;
min-height 0 makes them invisible. */
menu menuitem arrow { min-width: 16px; min-height: 16px; }
menu check, menu radio { min-width: 14px; min-height: 14px; }
menu separator {
min-height: 1px;
margin: 5px 0;
background: alpha(@text, 0.15);
}
window#waybar {
background: alpha(@base, 0.85);
color: @text;
border: ${toString t.ui.borderSize}px solid alpha(@accent, 0.4);
border-radius: ${toString t.ui.rounding}px;
}
/* Dim states use the palette's @muted role: since item 28b it is
floor-guaranteed legible on @base in every theme (muted/base >=
2.0, gated by tools/check-theme-contrast.py) the palettes that
once made it vanish (gruvbox mutedbase, item 27) were retuned.
Secondary-but-not-dim stays alpha(@text, 0.85). */
/* 3px vertical margin insets the active pill INSIDE the bar without
it the filled button spans the bar's full inner height and visually
overhangs the rounded border (#152). */
#workspaces button {
padding: 0 8px;
margin: 3px 0;
color: @muted;
border-radius: ${toString t.ui.rounding}px;
}
#workspaces button.active {
color: @base;
background: @accent;
}
#workspaces button.urgent {
color: @base;
background: @bad;
}
#window {
color: alpha(@text, 0.85);
padding: 0 12px;
}
#clock {
color: @text;
font-weight: bold;
}
#custom-nomarchy {
color: @accent;
font-family: Nomarchy;
font-size: ${toString (t.fonts.size + 4)}pt;
/* Wider outer padding: the bar's rounded corner curves into the
end module's box, so the edge glyphs need more room than the
uniform 10px or they look cramped (#152; power button mirrors). */
padding: 0 10px 0 14px;
}
#custom-nomarchy:hover { color: @accentAlt; }
#tray, #pulseaudio, #custom-powerprofile, #custom-nightlight, #custom-airplane, #custom-updates, #custom-vpn, #custom-recording, #idle_inhibitor, #language, #battery, #custom-doctor, #custom-notification, #custom-powermenu {
color: alpha(@text, 0.85);
padding: 0 10px;
}
#custom-powermenu { padding: 0 16px 0 10px; }
#custom-powermenu:hover { color: @bad; }
/* Group rhythm (item 28c): a wider breath before each functional
group of the right cluster media/stats · toggles · status
on top of the uniform module spacing. A group head can self-hide
(e.g. no battery on desktops); grouping then degrades to the
uniform spacing, never breaks. */
#pulseaudio, #custom-powerprofile, #battery { margin-left: 14px; }
/* Caffeine engaged warm tone (the screen is being held awake). */
#idle_inhibitor.activated { color: @warn; }
/* Recording in progress the alert red; the is also the stop button. */
#custom-recording.recording { color: @bad; }
/* VPN active accent green, reading as "connected / protected". */
#custom-vpn.on { color: @good; }
/* Night-light active warm tone, matching the filter it represents. */
#custom-nightlight.on { color: @warn; }
#custom-airplane.on { color: @warn; }
/* Updates pending accent, to draw the eye. */
#custom-updates.available { color: @accent; }
/* Doctor tripwire only rendered when something is , so it is
always the alert color. */
#custom-doctor { color: @bad; }
/* Icon-only status modules carry no text, so they don't get the
(size+2)pt Pango icon span the icon+text modules use bump their
font-size to match, or these glyphs read smaller than the volume /
battery / language icons beside them. */
#custom-recording, #custom-updates, #custom-vpn, #custom-airplane, #custom-nightlight, #custom-doctor, #custom-notification, #custom-powermenu { font-size: ${toString (t.fonts.size + 2)}pt; }
/* The speedometer + caffeine glyphs render small in their em box
size them up a touch more so they read at a glance. */
#custom-powerprofile, #idle_inhibitor { font-size: ${toString (t.fonts.size + 3)}pt; }
/* notifications waiting accent; suppressed (DND or app-inhibited)
muted bell-off, so it reads by shape+color, never color alone. */
#custom-notification.notification { color: @accent; }
#custom-notification.dnd-none,
#custom-notification.dnd-notification,
#custom-notification.inhibited-none,
#custom-notification.inhibited-notification { color: @muted; }
#pulseaudio.muted { color: @muted; }
#battery.warning { color: @warn; }
#battery.critical { color: @bad; }
#battery.charging { color: @good; }
/* Clock (and other) hover tooltips explicit colors so an empty
looking box is never just text same as background. */
tooltip {
background: @surface;
border: ${toString t.ui.borderSize}px solid alpha(@accent, 0.4);
border-radius: ${toString t.ui.rounding}px;
}
tooltip label { color: @text; }
'';
in
{
programs.waybar = lib.mkIf config.nomarchy.waybar.enable {
enable = true;
# Launched from Hyprland's exec-once (hyprland.nix), NOT a systemd user
# service. Bound to graphical-session.target the unit raced Hyprland's IPC
# on a warm relogin — it started before the socket was up, exited, landed
# in `failed`, and was never retried, so the bar vanished. exec-once only
# fires once Hyprland is up, dodging the race; theme switches reload the
# running bar via SIGUSR2 (nomarchy-state-sync). No uwsm here to manage the
# session target, so we don't depend on its lifecycle.
systemd.enable = false;
# mkDefault so downstream can replace the whole bar config/style with
# a plain home.nix assignment. For per-theme identity, prefer the
# whole-swap assets (themes/<slug>/waybar.{jsonc,css}); for one-off
# tweaks of the generated bar, lib.mkForce a sub-key. See
# docs/OVERRIDES.md.
settings.mainBar = lib.mkDefault (
if hasConfigOverride
then gateLanguage (builtins.fromJSON (builtins.readFile configOverride))
else generatedSettings
);
style = lib.mkDefault (
if hasStyleOverride
then builtins.readFile styleOverride
else generatedStyle
);
};
# Bar helpers on PATH (whole-swap jsoncs exec them by bare name) +
# feature deps of bar clicks: calcurse (clock), pwvucontrol (volume
# right-click) — not opt-in template packages.
home.packages = lib.optionals config.nomarchy.waybar.enable
[ waybarSupervisor powerProfileStatus powerProfileCycle vpnStatus doctorStatus calendarLauncher
pkgs.calcurse
pkgs.pwvucontrol
];
}