From 7b44fe8bf1253306e3544273c117af5c6c92a528 Mon Sep 17 00:00:00 2001 From: Daniel Fainberg Date: Tue, 7 Jul 2026 20:36:52 +0200 Subject: [PATCH] feat(desktop): dark fuzzel, curated launcher, dmenu power menu Fuzzel ignores the portal color-scheme, so set its palette explicitly to match waybar. Hide non-launcher `.desktop` entries (foot variants, mpv/ umpv, zathura, nvim) via `noDisplay` shadows. Replace the per-action app-list power entries with a `$mod+Backspace` `fuzzel --dmenu` power menu (Log Out / Reboot / Shut Down), colored Papirus icons. Co-Authored-By: Claude Opus 4.8 --- modules/home.nix | 75 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/modules/home.nix b/modules/home.nix index 6c5a469..81d127b 100644 --- a/modules/home.nix +++ b/modules/home.nix @@ -3,7 +3,7 @@ # user.name/email), host topology (SSH hosts, Syncthing peers) and personal app # choices (grayjay, Claude Code, …) live in the inventory, not in this module. -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: let terminal = "foot"; @@ -50,6 +50,25 @@ let ${pkgs.libnotify}/bin/notify-send "Screenshot" "Saved $file" fi ''; + + # Session/power menu: a fuzzel --dmenu picker (not app-list entries), bound to + # $mod+Backspace. --index returns the chosen row number, so dispatch matches on + # position, not label text. Icons use the Papirus names via fuzzel's dmenu + # icon protocol (NUL + 0x1f after each label). Escape/no pick → fuzzel exits + # non-zero, command substitution is empty, case falls through → no-op. + # + # Icon names deliberately picked from Papirus's apps/ (colored) set that have + # NO actions/ variant: system-{reboot,shutdown,log-out} exist in both, and at + # dmenu line-height fuzzel resolves the monochrome actions/ copy. gnome-logout + # / system-restart / gnome-session-halt are apps-only, so they stay colored. + powerMenu = pkgs.writeShellScript "power-menu" '' + case $(printf 'Log Out\0icon\x1fgnome-logout\nReboot\0icon\x1fsystem-restart\nShut Down\0icon\x1fgnome-session-halt\n' \ + | ${pkgs.fuzzel}/bin/fuzzel --dmenu --index --prompt 'power ') in + 0) ${config.wayland.windowManager.hyprland.finalPackage}/bin/hyprctl dispatch exit ;; + 1) ${pkgs.systemd}/bin/systemctl reboot ;; + 2) ${pkgs.systemd}/bin/systemctl poweroff ;; + esac + ''; in { imports = [ ./gpg.nix ./media.nix ]; @@ -94,6 +113,8 @@ in home.packages = with pkgs; [ librewolf keepassxc # config in ~/.config/keepassxc + papirus-icon-theme # fuzzel icon-theme; ships non-symbolic power icons + # (Adwaita only has them as symbolic, which fuzzel skips) # claude-code is installed by programs.claude-code below (its wrapped # finalPackage) — do NOT also list it here, or buildEnv conflicts on the # wrapper binary. @@ -205,7 +226,56 @@ in # so their stderr goes to the journal, not whatever terminal you're in. programs.fuzzel = { enable = true; - settings.main.font = "JetBrainsMono Nerd Font:size=11"; + settings = { + main.font = "JetBrainsMono Nerd Font:size=11"; + # Papirus for colored power icons in the $mod+Backspace dmenu (Adwaita + # ships those only as symbolic, and fuzzel skips symbolic dirs). It + # inherits hicolor, so app icons still resolve (recolored to Papirus). + main.icon-theme = "Papirus"; + # Fuzzel ignores the portal color-scheme, and its built-in palette is + # light — dark has to be set here. Matches waybar (#222/#bbb + One Dark + # accents); flat, thin border, no rounding per the minimal look. + border = { width = 1; radius = 0; }; + colors = { + background = "222222f2"; + text = "bbbbbbff"; + match = "e5c07bff"; + selection = "333333ff"; + selection-text = "ffffffff"; + selection-match = "e5c07bff"; + border = "444444ff"; + }; + }; + }; + + # Curate what fuzzel's app-list mode shows. Fuzzel just enumerates XDG + # `.desktop` files, so we shape that set here. Shadow a package's entry by + # redefining its id with noDisplay = true: our ~/.local/share/applications + # copy wins over the profile's. noDisplay only hides from launchers/menus — + # the app stays wired up for xdg-open and "open with", so opening a file + # *with* mpv/zathura is unaffected. The attrset merges with anything a private + # host adds. noDisplay is mkDefault so a host can un-hide one (e.g. re-expose + # mpv) with a plain override, no conflict. (Power/session actions are not + # entries here — see the $mod+Backspace dmenu power menu above.) + xdg.desktopEntries = let + hide = exec: { + inherit exec; + name = "hidden"; + noDisplay = lib.mkDefault true; + }; + in { + # foot's three entries: launched via $mod+Return, never from a menu. + foot = hide "foot"; + foot-server = hide "foot --server"; + footclient = hide "footclient"; + # mpv/zathura are file handlers, not things to launch bare from a menu. + mpv = hide "mpv %U"; + umpv = hide "umpv %U"; + "org.pwmt.zathura" = hide "zathura %U"; + "org.pwmt.zathura-djvu" = hide "zathura %U"; + "org.pwmt.zathura-cb" = hide "zathura %U"; + # nvim is $EDITOR / opened from a terminal; no place in a GUI launcher. + nvim = hide "nvim %F"; }; programs.fzf.enable = true; # provides the ^f "cd into fzf dir" binding @@ -413,6 +483,7 @@ in "$mod SHIFT, f, fullscreen, 0" "$mod, w, exec, ${browser}" "$mod, d, exec, fuzzel" + "$mod, Backspace, exec, ${powerMenu}" "$mod, j, layoutmsg, cyclenext" "$mod SHIFT, j, layoutmsg, swapnext" "$mod, k, layoutmsg, cycleprev"