feat(desktop): add the rofi launcher (task 0024)

Add a rofi module to the desktop group, enabled by the aggregator.
It runs the drun application launcher, bound on Super+R. A session menu
built on the same themed rofi is bound on Super+Shift+X, and every
rofi -dmenu call inherits the Stylix theme, so later utility menus
reuse it for free.

Also give layer surfaces their own fade-in (layersIn and fadeLayersIn)
at the window animation speed, so the launcher fades in quickly rather
than snapping into place.
This commit is contained in:
2026-07-22 15:52:21 -04:00
parent aca09d87a1
commit f18b551794
4 changed files with 76 additions and 5 deletions

View File

@@ -13,8 +13,23 @@ Structure it so it is reusable as the dmenu-style frontend for later utility men
## Acceptance criteria ## Acceptance criteria
- [ ] A rofi module exists in the desktop group and is enabled by the aggregator. - [x] A rofi module exists in the desktop group and is enabled by the aggregator.
- [ ] rofi combines application-run, binary-run, and window-switch modes, plus math evaluation and emoji. - [-] rofi combines application-run, binary-run, and window-switch modes, plus math evaluation and emoji.
- [ ] rofi opens on `Super+R`. - [x] rofi opens on `Super+R`.
- [ ] rofi is usable as a dmenu-style frontend for utility menus, and a power menu is provided through it. - [x] rofi is usable as a dmenu-style frontend for utility menus, and a power menu is provided through it.
- [ ] neogaia builds green under `nix flake check`. - [x] neogaia builds green under `nix flake check`.
## Implementation Notes
- `rofi-wayland` no longer exists as a separate package: nixpkgs merged the Wayland fork into `pkgs.rofi` (now 2.0.0).
The module uses the plain `pkgs.rofi`, which is the Wayland-capable build.
- Criterion 2 is deliberately reduced (`[-]`): the launcher is application-launch only, `modi = "drun"`, opened with `rofi -show drun` on `Super+R`.
Binary-run, window-switch, math (`rofi-calc`) and emoji (`rofi-emoji`) were all dropped at the operator's direction, and the two plugins removed with them, to keep the prompt as fast and uncluttered as possible.
This narrows the parent spec's "search-everything launcher" (user story 7) to a plain application launcher — a reversible choice, since any mode or plugin can be added back later.
Application icons are disabled too (`show-icons = false`), since resolving an icon per entry is the largest part of drun's per-launch startup and rofi runs no resident daemon to amortise it.
- The dmenu-style reuse is the themed rofi itself, not a separate abstraction: any `rofi -dmenu` invocation reads the same config and Stylix theme, so the power menu — and later clipboard/utility menus — look uniform for free.
- The launcher and power-menu keybinds live in this module rather than in `hyprland.nix`, contributed through `settings.bind`, which the module system concatenates with the compositor's own binds in the single `hyprland.conf`.
This keeps each command next to its binding and referenced by store path, so a rename cannot silently break the bind.
- The power menu is bound to `Super+Shift+X`, chosen by the operator.
It pairs with lock on `Super+X` (a key the spec's table does list), while the parent spec's table has no power-menu key of its own.
- No Waybar `window-rewrite` icon mapping was added: rofi renders as a Wayland layer-shell overlay, not a tiled window with a class on a workspace, so it never appears on the workspace indicator the convention governs.

View File

@@ -11,6 +11,7 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
modules.desktop.hyprland.enable = lib.mkDefault true; modules.desktop.hyprland.enable = lib.mkDefault true;
modules.desktop.login.enable = lib.mkDefault true; modules.desktop.login.enable = lib.mkDefault true;
modules.desktop.rofi.enable = lib.mkDefault true;
modules.desktop.terminal.enable = lib.mkDefault true; modules.desktop.terminal.enable = lib.mkDefault true;
modules.desktop.theming.enable = lib.mkDefault true; modules.desktop.theming.enable = lib.mkDefault true;
modules.desktop.waybar.enable = lib.mkDefault true; modules.desktop.waybar.enable = lib.mkDefault true;

View File

@@ -98,6 +98,9 @@ in
animation = [ animation = [
"windows, 1, 3, ease" "windows, 1, 3, ease"
"fade, 1, 3, ease" "fade, 1, 3, ease"
# Layer surfaces like the launcher fade in as quickly as windows.
"layersIn, 1, 3, ease"
"fadeLayersIn, 1, 3, ease"
"workspaces, 1, 3, ease" "workspaces, 1, 3, ease"
"border, 1, 3, ease" "border, 1, 3, ease"
]; ];

52
modules/desktop/rofi.nix Normal file
View File

@@ -0,0 +1,52 @@
{
config,
lib,
pkgs,
...
}:
# rofi as the desktop launcher and dmenu-style menu frontend.
let
cfg = config.modules.desktop.rofi;
user = config.user.name;
# A session menu built on the same themed rofi, so the power actions look
# like every other menu the launcher drives.
powerMenu = pkgs.writeShellScript "rofi-power-menu" ''
chosen=$(printf '%s\n' Lock Suspend 'Log out' Reboot 'Shut down' \
| ${pkgs.rofi}/bin/rofi -dmenu -i -p Power)
case "$chosen" in
Lock) ${pkgs.systemd}/bin/loginctl lock-session ;;
Suspend) ${pkgs.systemd}/bin/systemctl suspend ;;
'Log out') ${pkgs.systemd}/bin/loginctl terminate-session "$XDG_SESSION_ID" ;;
Reboot) ${pkgs.systemd}/bin/systemctl reboot ;;
'Shut down') ${pkgs.systemd}/bin/systemctl poweroff ;;
esac
'';
in
{
options.modules.desktop.rofi.enable = lib.mkEnableOption "the rofi launcher";
config = lib.mkIf cfg.enable {
home-manager.users.${user} = {
# No theme is set here: Stylix's rofi target supplies the Nord one.
programs.rofi = {
enable = true;
# Application launch is the only mode, kept minimal so the prompt opens
# as fast as rofi can read the desktop entries.
# Icons are off: resolving one per entry is the bulk of drun's startup.
extraConfig = {
modi = "drun";
show-icons = false;
};
};
# The launcher and the session menu, kept next to the tool they drive.
# $mod is defined by the compositor config these binds share.
wayland.windowManager.hyprland.settings.bind = [
"$mod, R, exec, ${pkgs.rofi}/bin/rofi -show drun"
"$mod SHIFT, X, exec, ${powerMenu}"
];
};
};
}