feat(home): switch launcher/menu from fuzzel to rofi 2.0
rofi 2.0 landed in nixpkgs 26.05 with native Wayland (mainline upstreamed the lbonn rofi-wayland fork — `wayland enabled`), so the original reason to prefer fuzzel (rofi being X11-only / needing a community fork) is gone. rofi's `.rasi` is far more expressive than fuzzel's flat INI, which lets the launcher be a proper themed hero surface and revives the legacy per-theme launcher designs. - modules/home/rofi.nix replaces fuzzel.nix: per-element theme generated from theme-state.json (accent border, highlighted selection, rounded inputbar) via lib.formats.rasi.mkLiteral; themes/<slug>/rofi.rasi whole-swap; the nomarchy-menu dispatcher re-pointed to `rofi -dmenu` (power/theme/clipboard/calc with -mesg/files/web + root picker). - SUPER+D is now `rofi -show drun`; the menu binds are unchanged. - nomarchy.fuzzel.enable → nomarchy.rofi.enable; fuzzel dropped from home.packages. Pango handles nf-glyph fallback (no explicit font list needed as fuzzel's fcft did). show-icons off until an icon theme ships. Verified: flake check green, HM generation builds, the generated theme.rasi parses under `rofi -dump-theme`, Hyprland config verifies. Not verified: rofi's on-screen rendering needs a real session. Docs: README menu/override/roadmap sections and the module tree updated; the per-theme launcher roadmap item now points at porting the legacy rofi.rasi designs (the whole-swap mechanism is in place). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
222
modules/home/rofi.nix
Normal file
222
modules/home/rofi.nix
Normal file
@@ -0,0 +1,222 @@
|
||||
# rofi (2.0, native Wayland on 26.05) — launcher + dmenu renderer for
|
||||
# the menu system, themed from theme-state.json. rofi's .rasi is far more
|
||||
# expressive than a flat scheme, so the generated theme styles each
|
||||
# element (accent border, highlighted selection, rounded inputbar); a
|
||||
# themes/<slug>/rofi.rasi whole-swap replaces it entirely.
|
||||
#
|
||||
# Home of nomarchy-menu, the dispatcher all menu modules hang off.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.nomarchy;
|
||||
t = cfg.theme;
|
||||
c = t.colors;
|
||||
inherit (config.lib.formats.rasi) mkLiteral;
|
||||
|
||||
px = n: mkLiteral "${toString n}px";
|
||||
|
||||
# Per-theme override probe (same convention as waybar.css).
|
||||
rasiOverride = cfg.themesDir + "/${t.slug}/rofi.rasi";
|
||||
hasRasiOverride = builtins.pathExists rasiOverride;
|
||||
|
||||
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
|
||||
# Nomarchy menu dispatcher — thin presentation layer over
|
||||
# `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl,
|
||||
# hyprctl and friends. Icons are nf-md glyphs (Pango falls back to a
|
||||
# Nerd Font for them). `nomarchy-menu` with no argument shows the
|
||||
# module picker.
|
||||
|
||||
urlencode() {
|
||||
local s="$1" out="" ch i
|
||||
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" \
|
||||
| rofi -dmenu -p power) || 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 | rofi -dmenu -p theme) || exit 0
|
||||
[ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;;
|
||||
|
||||
clipboard)
|
||||
sel=$(cliphist list | rofi -dmenu -p clip) || exit 0
|
||||
printf '%s' "$sel" | cliphist decode | wl-copy ;;
|
||||
|
||||
calc)
|
||||
expr=$(rofi -dmenu -p "= " < /dev/null) || exit 0
|
||||
[ -n "$expr" ] || exit 0
|
||||
result=$(qalc -t -- "$expr" 2>&1 | tail -n 1)
|
||||
choice=$(printf '%s\n' " Copy result" " New calculation" \
|
||||
| rofi -dmenu -p "= " -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/||" \
|
||||
| rofi -dmenu -p file) || exit 0
|
||||
[ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;;
|
||||
|
||||
web)
|
||||
q=$(rofi -dmenu -p search < /dev/null) || 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" \
|
||||
| rofi -dmenu -p menu) || exit 0
|
||||
case "$choice" in
|
||||
*Apps*) exec rofi -show drun ;;
|
||||
*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.rofi.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.rofi = {
|
||||
enable = true;
|
||||
terminal = cfg.terminal;
|
||||
font = "${t.fonts.ui} 12";
|
||||
|
||||
extraConfig = {
|
||||
modi = "drun,run";
|
||||
show-icons = false; # no icon theme shipped yet — clean text list
|
||||
drun-display-format = "{name}";
|
||||
display-drun = "apps";
|
||||
display-run = "run";
|
||||
};
|
||||
|
||||
# Whole-swap themes bring their own rofi.rasi; otherwise the theme
|
||||
# is generated from the palette.
|
||||
theme =
|
||||
if hasRasiOverride then rasiOverride
|
||||
else {
|
||||
"*" = {
|
||||
bg = mkLiteral c.base;
|
||||
fg = mkLiteral c.text;
|
||||
dim = mkLiteral c.subtext;
|
||||
surface = mkLiteral c.surface;
|
||||
accent = mkLiteral c.accent;
|
||||
|
||||
background-color = mkLiteral "transparent";
|
||||
text-color = mkLiteral "@fg";
|
||||
margin = 0;
|
||||
padding = 0;
|
||||
spacing = 0;
|
||||
};
|
||||
|
||||
"window" = {
|
||||
background-color = mkLiteral "@bg";
|
||||
border = px t.ui.borderSize;
|
||||
border-color = mkLiteral "@accent";
|
||||
border-radius = px t.ui.rounding;
|
||||
width = mkLiteral "40%";
|
||||
padding = px 8;
|
||||
};
|
||||
|
||||
"mainbox" = {
|
||||
children = map mkLiteral [ "inputbar" "message" "listview" ];
|
||||
};
|
||||
|
||||
"inputbar" = {
|
||||
background-color = mkLiteral "@surface";
|
||||
border-radius = px t.ui.rounding;
|
||||
padding = mkLiteral "8px 12px";
|
||||
spacing = px 8;
|
||||
children = map mkLiteral [ "prompt" "entry" ];
|
||||
};
|
||||
"prompt" = { text-color = mkLiteral "@accent"; };
|
||||
"entry" = {
|
||||
placeholder = "Search…";
|
||||
placeholder-color = mkLiteral "@dim";
|
||||
};
|
||||
|
||||
"listview" = {
|
||||
lines = 8;
|
||||
columns = 1;
|
||||
dynamic = true;
|
||||
scrollbar = false;
|
||||
spacing = px 2;
|
||||
padding = mkLiteral "8px 0px 0px 0px";
|
||||
};
|
||||
|
||||
"element" = {
|
||||
padding = mkLiteral "8px 12px";
|
||||
border-radius = px t.ui.rounding;
|
||||
spacing = px 8;
|
||||
};
|
||||
"element selected" = {
|
||||
background-color = mkLiteral "@accent";
|
||||
text-color = mkLiteral "@bg";
|
||||
};
|
||||
"element-icon" = {
|
||||
background-color = mkLiteral "transparent";
|
||||
size = mkLiteral "1em";
|
||||
};
|
||||
"element-text" = {
|
||||
background-color = mkLiteral "transparent";
|
||||
text-color = mkLiteral "inherit";
|
||||
};
|
||||
|
||||
"message" = { padding = mkLiteral "8px 0px 0px 0px"; };
|
||||
"textbox" = {
|
||||
background-color = mkLiteral "@surface";
|
||||
text-color = mkLiteral "@fg";
|
||||
border-radius = px t.ui.rounding;
|
||||
padding = mkLiteral "8px 12px";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user