From 465f013155442045010c0a6d842bd2ea6a78f32c Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 07:37:47 +0100 Subject: [PATCH] feat(home): menu system, swaync, idle/lock, nm-applet, theme parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Themed fuzzel (modules/home/fuzzel.nix): palette/fonts/border from the JSON, themes//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 --- modules/home/default.nix | 12 ++++ modules/home/fuzzel.nix | 107 ++++++++++++++++++++++++++++++--- modules/home/hyprland.nix | 18 ++++-- modules/home/options.nix | 22 +++++++ modules/home/waybar.nix | 5 +- themes/summer-day/waybar.css | 1 + themes/summer-night/waybar.css | 6 +- 7 files changed, 154 insertions(+), 17 deletions(-) diff --git a/modules/home/default.nix b/modules/home/default.nix index 699ce68..e33dd6c 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -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; [ diff --git a/modules/home/fuzzel.nix b/modules/home/fuzzel.nix index b0f126b..eee8416 100644 --- a/modules/home/fuzzel.nix +++ b/modules/home/fuzzel.nix @@ -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//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; + }; }; } diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 5584ad1..5478f88 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -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" diff --git a/modules/home/options.nix b/modules/home/options.nix index 69eafb8..5f3aab0 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -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; }; diff --git a/modules/home/waybar.nix b/modules/home/waybar.nix index e1bc3e5..2770db0 100644 --- a/modules/home/waybar.nix +++ b/modules/home/waybar.nix @@ -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}%"; diff --git a/themes/summer-day/waybar.css b/themes/summer-day/waybar.css index 18ac8f3..aa1a4e8 100755 --- a/themes/summer-day/waybar.css +++ b/themes/summer-day/waybar.css @@ -49,6 +49,7 @@ window#waybar { #pulseaudio, #network, #battery, +#tray, #custom-powermenu { background-color: @bg0; color: @fg; diff --git a/themes/summer-night/waybar.css b/themes/summer-night/waybar.css index 83466ad..a52bda3 100644 --- a/themes/summer-night/waybar.css +++ b/themes/summer-night/waybar.css @@ -53,8 +53,9 @@ window#waybar { #clock.date, #workspaces, #pulseaudio, +#network, #idle_inhibitor, -#custom-battery, +#battery, #custom-powermenu { background-color: @bg0; color: @fg; @@ -98,7 +99,6 @@ window#waybar { margin-left: 15px; padding-left: 20px; padding-right: 21px; - font-family: Nomarchy; font-size: 20px; } @@ -112,7 +112,7 @@ window#waybar { padding-right: 23px; } -#custom-battery.preservation { +#battery.charging { color: @green; }