# 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 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 cfg = config.nomarchy; 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 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 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|theme|clipboard|calc|files|web]" >&2 exit 64 ;; esac ''; in { config = lib.mkIf cfg.fuzzel.enable { 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; # 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. font = "${t.fonts.ui}:size=${toString (t.fonts.size + 1)}, ${t.fonts.mono}:size=${toString (t.fonts.size + 1)}"; terminal = cfg.terminal; layer = "overlay"; lines = 12; width = 40; horizontal-pad = 24; vertical-pad = 16; inner-pad = 8; }; colors = { background = hexa c.base "f2"; text = hexa c.text "ff"; prompt = hexa c.subtext "ff"; placeholder = hexa c.muted "ff"; input = hexa c.text "ff"; match = hexa c.accent "ff"; selection = hexa c.surface "ff"; selection-text = hexa c.text "ff"; selection-match = hexa c.accent "ff"; border = hexa c.accent "ee"; }; border = { width = t.ui.borderSize; radius = t.ui.rounding; }; }; }; xdg.configFile."fuzzel/fuzzel.ini" = lib.mkIf hasIniOverride { source = iniOverride; }; }; }