feat(home): menu system, swaync, idle/lock, nm-applet, theme parity

- Themed fuzzel (modules/home/fuzzel.nix): palette/fonts/border from the
  JSON, themes/<slug>/fuzzel.ini whole-swap mechanism. Plus the
  nomarchy-menu dispatcher: root picker · power (SUPER+Escape) · theme
  (SUPER+T) · clipboard (SUPER+CTRL+V, cliphist) · calc (qalc) · files
  (fd→xdg-open) · web (DuckDuckGo).
- swaync notifications themed from the JSON (SUPER+N) — until now nothing
  rendered notify-send (theme toasts, font warnings, welcome msg).
- hyprlock + hypridle (modules/home/idle.nix): lock 5min, dpms off 10,
  suspend 30; enables the power menu's Lock entry. cliphist daemon on.
- NetworkManager applet in waybar's tray (preferStatusNotifierItems);
  network module on-click → $TERMINAL -e nmtui.
- nomarchy.keyboard.layout/.variant option → Hyprland kb_layout/kb_variant
  (the installer writes it; pairs with system-side xkb for tty/LUKS).
- Media-key/audio on-click moved to wpctl (pamixer was inconsistent).
- Theme parity: summer-day/night carry their legacy bar LAYOUTS as
  waybar.jsonc whole-swaps (the original import took waybar.css but not
  config.jsonc, so identity themes styled nonexistent modules). Dead
  legacy script-modules dropped, Nerd-Fonts-v2 codepoints → FontAwesome/v3
  (font-awesome now shipped), logo buttons open nomarchy-menu.

New toggles: nomarchy.{fuzzel,swaync,idle}.enable (all default true).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-06-13 07:37:47 +01:00
parent eda8461304
commit 465f013155
7 changed files with 154 additions and 17 deletions

View File

@@ -12,8 +12,20 @@
./waybar.nix
./ghostty.nix
./btop.nix
./fuzzel.nix # launcher theming + the nomarchy-menu dispatcher
./swaync.nix # notification daemon, themed from the same JSON
./idle.nix # hyprlock + hypridle, themed from the same JSON
];
# Clipboard history (wl-paste watcher); browsed via the SUPER+CTRL+V
# menu module.
services.cliphist.enable = true;
# Wifi from the bar: nm-applet lives in waybar's tray (SNI flag via
# preferStatusNotifierItems — without it there is no tray icon).
services.network-manager-applet.enable = true;
xsession.preferStatusNotifierItems = true;
home.stateVersion = "26.05";
home.packages = with pkgs; [

View File

@@ -1,7 +1,10 @@
# Fuzzel — launcher, dmenu renderer for the menu system, themed from
# theme-state.json like every other surface. Also home of nomarchy-menu,
# the dispatcher script the menu roadmap grows out of (first module:
# power).
# the dispatcher script all menu modules grow out of.
#
# Per-theme identity: themes/<slug>/fuzzel.ini is a whole-swap (like
# waybar.css) — it replaces the generated config entirely, probed at
# eval time.
{ config, lib, pkgs, ... }:
let
@@ -9,43 +12,125 @@ let
t = cfg.theme;
c = t.colors;
# Per-theme override probe (same convention as waybar.nix).
iniOverride = cfg.themesDir + "/${t.slug}/fuzzel.ini";
hasIniOverride = builtins.pathExists iniOverride;
# fuzzel.ini colors are rrggbbaa, no leading #.
hexa = color: alpha: "${lib.removePrefix "#" color}${alpha}";
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
# Nomarchy menu dispatcher thin presentation layer over
# `fuzzel --dmenu`; actions delegate to systemctl/hyprctl/
# nomarchy-theme-sync. The icons are nf-md glyphs (any shipped
# Nerd Font carries them).
# `fuzzel --dmenu`; actions delegate to nomarchy-theme-sync,
# systemctl, hyprctl and friends. Icons are nf-md glyphs (any
# shipped Nerd Font carries them). `nomarchy-menu` with no
# argument shows the module picker.
urlencode() {
local s="$1" out="" ch
for ((i = 0; i < ''${#s}; i++)); do
ch=''${s:i:1}
case "$ch" in
[a-zA-Z0-9.~_-]) out+="$ch" ;;
' ') out+="+" ;;
*) printf -v ch '%%%02X' "'$ch"; out+="$ch" ;;
esac
done
printf '%s' "$out"
}
case "''${1:-}" in
power)
choice=$(printf '%s\n' \
"󰌾 Lock" \
"󰍃 Logout" \
"󰤄 Suspend" \
"󰒲 Hibernate" \
"󰜉 Reboot" \
"󰐥 Shutdown" \
| fuzzel --dmenu --prompt "power " --lines 5 --width 24) || exit 0
| fuzzel --dmenu --prompt "power " --lines 6 --width 24) || exit 0
case "$choice" in
*Lock) loginctl lock-session ;;
*Logout) hyprctl dispatch exit ;;
*Suspend) systemctl suspend ;;
*Hibernate) systemctl hibernate ;;
*Reboot) systemctl reboot ;;
*Shutdown) systemctl poweroff ;;
esac ;;
theme)
choice=$(nomarchy-theme-sync list \
| fuzzel --dmenu --prompt "theme ") || exit 0
[ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;;
clipboard)
sel=$(cliphist list | fuzzel --dmenu --prompt "clip " --width 60) || exit 0
printf '%s' "$sel" | cliphist decode | wl-copy ;;
calc)
expr=$(fuzzel --prompt-only "= ") || exit 0
[ -n "$expr" ] || exit 0
result=$(qalc -t -- "$expr" 2>&1 | tail -n 1)
choice=$(printf '%s\n' "󰆏 Copy result" "󰃬 New calculation" \
| fuzzel --dmenu --lines 2 --width 40 \
--prompt "= " --mesg "$expr = $result") || exit 0
case "$choice" in
*Copy*) printf '%s' "$result" | wl-copy ;;
*New*) exec "$0" calc ;;
esac ;;
files)
sel=$(fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \
| head -n 50000 \
| sed "s|^$HOME/||" \
| fuzzel --dmenu --prompt "file " --width 60) || exit 0
[ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;;
web)
q=$(fuzzel --prompt-only "search ") || exit 0
[ -n "$q" ] || exit 0
exec xdg-open "https://duckduckgo.com/?q=$(urlencode "$q")" ;;
"")
choice=$(printf '%s\n' \
"󰀻 Apps" \
"󰏘 Theme" \
"󰅌 Clipboard" \
"󰃬 Calculator" \
"󰈞 Files" \
"󰖟 Web search" \
"󰐥 Power" \
| fuzzel --dmenu --prompt "menu " --lines 7 --width 24) || exit 0
case "$choice" in
*Apps*) exec fuzzel ;;
*Theme*) exec "$0" theme ;;
*Clipboard*) exec "$0" clipboard ;;
*Calc*) exec "$0" calc ;;
*Files*) exec "$0" files ;;
*Web*) exec "$0" web ;;
*Power*) exec "$0" power ;;
esac ;;
*)
echo "usage: nomarchy-menu power" >&2
echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web]" >&2
exit 64 ;;
esac
'';
in
{
config = lib.mkIf cfg.fuzzel.enable {
home.packages = [ nomarchy-menu ];
home.packages = [
nomarchy-menu
pkgs.libqalculate # qalc, the calc module's engine
pkgs.fd # files module
pkgs.xdg-utils # xdg-open for files/web
];
programs.fuzzel = {
enable = true;
settings = {
# Whole-swap themes bring their own fuzzel.ini; otherwise the
# config is generated from the palette.
settings = lib.mkIf (!hasIniOverride) {
main = {
# UI font first; the mono Nerd Font follows so menu icons
# (nf-md glyphs) resolve — fcft falls back per glyph.
@@ -78,5 +163,9 @@ in
};
};
};
xdg.configFile."fuzzel/fuzzel.ini" = lib.mkIf hasIniOverride {
source = iniOverride;
};
};
}

View File

@@ -90,7 +90,8 @@ in
# ── Behaviour ──────────────────────────────────────────────────
input = {
kb_layout = "us";
kb_layout = config.nomarchy.keyboard.layout;
kb_variant = config.nomarchy.keyboard.variant;
follow_mouse = 1;
touchpad.natural_scroll = true;
};
@@ -120,12 +121,21 @@ in
"$mod, V, togglefloating"
"$mod SHIFT, E, exit"
# Theme picker: fuzzel menu over the presets; apply writes the
# state and runs home-manager switch (progress via notify-send).
"$mod, T, exec, nomarchy-theme-sync apply \"$(nomarchy-theme-sync list | fuzzel --dmenu --prompt 'theme> ')\""
# Theme picker (menu dispatcher): apply writes the state and
# runs home-manager switch (progress via notify-send).
"$mod, T, exec, nomarchy-menu theme"
# Cycle the current theme's wallpapers (instant, no rebuild).
"$mod SHIFT, T, exec, nomarchy-theme-sync bg next"
# Power menu (fuzzel dmenu via the nomarchy-menu dispatcher).
"$mod, Escape, exec, nomarchy-menu power"
# Notification centre (swaync).
"$mod, N, exec, swaync-client -t"
# Clipboard history (cliphist via the menu dispatcher).
"$mod CTRL, V, exec, nomarchy-menu clipboard"
# Focus
"$mod, H, movefocus, l"
"$mod, L, movefocus, r"

View File

@@ -23,6 +23,25 @@
description = "Terminal emulator command, used by keybinds and $TERMINAL.";
};
keyboard.layout = lib.mkOption {
type = lib.types.str;
default = "us";
example = "de";
description = ''
XKB layout for the Hyprland session. The console (and the LUKS
passphrase prompt) take theirs from the system side the
installer writes services.xserver.xkb + console.useXkbConfig
into system.nix with the same value.
'';
};
keyboard.variant = lib.mkOption {
type = lib.types.str;
default = "";
example = "nodeadkeys";
description = "XKB variant for the Hyprland session.";
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.nomarchy-theme-sync;
@@ -44,6 +63,9 @@
# ── Component toggles ──────────────────────────────────────────
hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
fuzzel.enable = lib.mkEnableOption "Nomarchy's themed fuzzel + the nomarchy-menu dispatcher (power menu)" // { default = true; };
swaync.enable = lib.mkEnableOption "swaync notifications, themed from the state file" // { default = true; };
idle.enable = lib.mkEnableOption "hyprlock + hypridle (idle lock, display off, suspend)" // { default = true; };
ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; };
btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; };
stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; };

View File

@@ -60,7 +60,7 @@ let
format = "{icon} {volume}%";
format-muted = "󰝟";
format-icons.default = [ "󰕿" "󰖀" "󰕾" ];
on-click = "pamixer -t";
on-click = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
};
network = {
@@ -68,6 +68,9 @@ let
format-ethernet = "󰈀";
format-disconnected = "󰤭";
tooltip-format = "{ipaddr} via {gwaddr}";
# nm-applet sits in the tray for the GUI path; this is the
# keyboard-friendly one.
on-click = "${config.nomarchy.terminal} -e nmtui";
};
cpu.format = "󰍛 {usage}%";