feat(menu): network/bluetooth/capture/ask modules + keybindings cheatsheet

Build out the rofi menu system roadmap item:

- New nomarchy-menu modules: network (nmtui), bluetooth (blueman-manager),
  capture (grim/slurp submenu → clipboard/file), ask (free-text → claude
  CLI in a terminal), and keybinds (the SUPER+? cheatsheet).
- modules/home/keybinds.nix is now the single source of truth for both the
  Hyprland bind strings and the cheatsheet, so the two can't drift —
  hyprland.nix maps it into `bind`, rofi.nix renders the padded two-column
  sheet (generated/mouse binds live in its `extra` rows).
- New binds: SUPER+Space (quick launch), SUPER+M (main menu), SUPER+?
  (cheatsheet). SUPER+D stays rofi -show drun.
- Ask Claude pulls claude-code fresh from npm via `npx
  @anthropic-ai/claude-code@latest` (the nixpkgs package lags model
  releases); nodejs is bundled for npx.

nix flake check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-06-13 19:46:01 +01:00
parent 60138e20d0
commit 58d79aad17
5 changed files with 152 additions and 45 deletions

View File

@@ -261,7 +261,8 @@
"Bash(pkill -f \"qemu-system-x86_64.*NOMARCHY\\\\|qemu-system-x86_64.*nomarchy\")",
"Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-night --no-switch)",
"Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-day --no-switch)",
"Bash(xargs cat)"
"Bash(xargs cat)",
"Bash(nix-instantiate --parse modules/home/rofi.nix)"
]
}
}

View File

@@ -274,14 +274,20 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot.
`theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc`
(qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo).
SUPER+D is `rofi -show drun`.
- next modules: Networknmtui · Bluetooth ▸ blueman · Capture ▸
screenshot · **keybindings cheatsheet** (parse the bind list → rofi,
so SUPER-? shows every shortcut) · **ask Claude**: free-text →
`$TERMINAL -e claude "<question>"` — the claude CLI auths via OAuth
against a Pro/Max subscription (no API key); REPL stays open for
follow-ups (claude-code in nixpkgs is unfree — allowUnfree is on)
- planned binds: `SUPER+Space``rofi -dmenu`/quick-launch and
`SUPER+M``nomarchy-menu` (main menu); `SUPER+D` stays `-show drun`
- ✓ shipped modules: `network` (nmtui in `$TERMINAL`) · `bluetooth`
(blueman-manager) · `capture` (grim/slurp submenu: region/full →
clipboard/file, saved to `~/Pictures/Screenshots`) · `keybinds` (the
cheatsheet, see below) · `ask` (free-text → claude CLI in a terminal;
auths via OAuth, no API key; pulled fresh via `npx
@anthropic-ai/claude-code@latest` rather than the nixpkgs package, which
lags model releases — `nodejs` is bundled for npx; REPL stays open)
- ✓ keybindings cheatsheet: `modules/home/keybinds.nix` is the **single
source** for both the Hyprland binds and the SUPER+? rofi list, so they
can't drift; `nomarchy-menu keybinds` renders the padded two-column
sheet (generated/mouse binds carried in its `extra` rows)
- ✓ shipped binds: `SUPER+Space``rofi -show drun` (quick launch) ·
`SUPER+M``nomarchy-menu` (main menu) · `SUPER+?` → the cheatsheet;
`SUPER+D` stays `-show drun`
- launcher icons: ✓ `show-icons` on, drawing from the theme's icon set
(Papirus, via the icon-themes work below)
- decision record: resolves the old Walker/Lua question — no GTK4

View File

@@ -17,6 +17,11 @@ let
"$mod SHIFT, ${ws}, movetoworkspace, ${ws}"
])
9);
# The keyboard binds — single source shared with the SUPER+? cheatsheet
# (rofi.nix renders the same list). Edit them in ./keybinds.nix.
keybinds = import ./keybinds.nix;
mkBind = b: "${b.mods}, ${b.key}, ${b.action}";
in
{
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
@@ -125,41 +130,11 @@ in
force_default_wallpaper = lib.mkDefault 0;
};
bind = [
"$mod, Return, exec, $terminal"
"$mod, D, exec, rofi -show drun"
"$mod, E, exec, $terminal -e yazi"
"$mod, Q, killactive"
"$mod, F, fullscreen"
"$mod, V, togglefloating"
"$mod SHIFT, E, exit"
# 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 (rofi dmenu via the nomarchy-menu dispatcher).
# Not Escape: Super+Escape gets swallowed before reaching the
# dispatcher on some setups (input methods / modal handling).
"$mod, X, 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"
"$mod, K, movefocus, u"
"$mod, J, movefocus, d"
# Screenshot region to clipboard
", Print, exec, grim -g \"$(slurp)\" - | wl-copy"
] ++ workspaceBinds;
# Rendered from ./keybinds.nix (the cheatsheet reads the same list),
# plus the generated per-workspace binds. Like exec-once above this
# stays a normal-priority list, so a downstream `bind = [...]`
# concatenates rather than replaces.
bind = map mkBind keybinds.binds ++ workspaceBinds;
# Media keys via swayosd-client: it performs the action AND shows
# the on-screen display (the nomarchy.osd module). e = repeat,

56
modules/home/keybinds.nix Normal file
View File

@@ -0,0 +1,56 @@
# Canonical keybind list — the single source of truth for BOTH the
# Hyprland `bind` strings (hyprland.nix) and the SUPER+? cheatsheet
# rendered by nomarchy-menu (rofi.nix). One place, no second renderer to
# keep in sync — the same rule the theme pipeline follows.
#
# mods — modifier string, Hyprland syntax ("$mod", "$mod SHIFT", "")
# key — the key name
# action — the Hyprland dispatcher (+args) that the bind runs
# desc — human label, shown in the cheatsheet
#
# `extra` holds cheatsheet-only rows for binds generated elsewhere
# (per-workspace numbers, mouse drags) so they still show up under SUPER+?.
{
binds = [
{ mods = "$mod"; key = "Return"; action = "exec, $terminal"; desc = "Open terminal"; }
{ mods = "$mod"; key = "Space"; action = "exec, rofi -show drun"; desc = "Quick launch (apps)"; }
{ mods = "$mod"; key = "D"; action = "exec, rofi -show drun"; desc = "App launcher"; }
{ mods = "$mod"; key = "M"; action = "exec, nomarchy-menu"; desc = "Main menu"; }
{ mods = "$mod"; key = "E"; action = "exec, $terminal -e yazi"; desc = "File manager (yazi)"; }
{ mods = "$mod"; key = "Q"; action = "killactive"; desc = "Close window"; }
{ mods = "$mod"; key = "F"; action = "fullscreen"; desc = "Fullscreen"; }
{ mods = "$mod"; key = "V"; action = "togglefloating"; desc = "Toggle floating"; }
{ mods = "$mod SHIFT"; key = "E"; action = "exit"; desc = "Exit Hyprland"; }
# Theme picker (menu dispatcher): apply writes the state and runs
# home-manager switch (progress via notify-send).
{ mods = "$mod"; key = "T"; action = "exec, nomarchy-menu theme"; desc = "Theme picker"; }
# Cycle the current theme's wallpapers (instant, no rebuild).
{ mods = "$mod SHIFT"; key = "T"; action = "exec, nomarchy-theme-sync bg next"; desc = "Next wallpaper"; }
# Power menu via the dispatcher. Not Escape: Super+Escape gets
# swallowed before reaching the dispatcher on some setups.
{ mods = "$mod"; key = "X"; action = "exec, nomarchy-menu power"; desc = "Power menu"; }
{ mods = "$mod"; key = "N"; action = "exec, swaync-client -t"; desc = "Notification centre"; }
{ mods = "$mod CTRL"; key = "V"; action = "exec, nomarchy-menu clipboard"; desc = "Clipboard history"; }
# SUPER+? (the "question" keysym already implies Shift on most layouts).
{ mods = "$mod"; key = "question"; action = "exec, nomarchy-menu keybinds"; desc = "Keybindings cheatsheet"; }
# Focus
{ mods = "$mod"; key = "H"; action = "movefocus, l"; desc = "Focus left"; }
{ mods = "$mod"; key = "L"; action = "movefocus, r"; desc = "Focus right"; }
{ mods = "$mod"; key = "K"; action = "movefocus, u"; desc = "Focus up"; }
{ mods = "$mod"; key = "J"; action = "movefocus, d"; desc = "Focus down"; }
# Screenshot region to clipboard (the menu's Capture module has more).
{ mods = ""; key = "Print"; action = "exec, grim -g \"$(slurp)\" - | wl-copy"; desc = "Screenshot region clipboard"; }
];
extra = [
{ keys = "SUPER + 1-9"; desc = "Switch to workspace 1-9"; }
{ keys = "SUPER + SHIFT + 1-9"; desc = "Move window to workspace 1-9"; }
{ keys = "SUPER + drag"; desc = "Move (LMB) / resize (RMB) window"; }
{ keys = "Volume / Brightness"; desc = "Hardware keys, shown via the OSD"; }
];
}

View File

@@ -19,6 +19,25 @@ let
rasiOverride = cfg.themesDir + "/${t.slug}/rofi.rasi";
hasRasiOverride = builtins.pathExists rasiOverride;
# Keybindings cheatsheet (SUPER+? → the `keybinds` menu module). Built
# from the SAME ./keybinds.nix that hyprland.nix binds, so it can never
# drift from the live shortcuts. "$mod" reads as SUPER; rows are padded
# into two columns. extra[] carries the generated/mouse binds.
keybinds = import ./keybinds.nix;
padRight = w: s:
let n = w - builtins.stringLength s;
in s + (if n > 0 then lib.concatStrings (builtins.genList (_: " ") n) else " ");
prettyKeys = b:
let m = lib.replaceStrings [ "$mod" ] [ "SUPER" ] b.mods;
prefix = if m == "" then "" else lib.concatStringsSep " + " (lib.splitString " " m) + " + ";
key = lib.replaceStrings [ "question" "slash" ] [ "?" "/" ] b.key;
in prefix + key;
cheatRows =
map (b: padRight 22 (prettyKeys b) + b.desc) keybinds.binds
++ map (e: padRight 22 e.keys + e.desc) keybinds.extra;
cheatsheetFile = pkgs.writeText "nomarchy-keybinds.txt"
(lib.concatStringsSep "\n" cheatRows + "\n");
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
# Nomarchy menu dispatcher thin presentation layer over
# `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl,
@@ -89,6 +108,45 @@ let
[ -n "$q" ] || exit 0
exec xdg-open "https://duckduckgo.com/?q=$(urlencode "$q")" ;;
network)
# nmtui in a terminal (NetworkManager is the system network stack).
exec ${cfg.terminal} -e nmtui ;;
bluetooth)
# blueman-manager GUI (services.blueman.enable, system-side).
exec blueman-manager ;;
capture)
choice=$(printf '%s\n' \
"󰆟 Region clipboard" \
"󰆟 Region file" \
"󰍹 Full screen clipboard" \
"󰍹 Full screen file" \
| rofi -dmenu -p capture) || exit 0
dir="$HOME/Pictures/Screenshots"
file="$dir/$(date +%Y%m%d-%H%M%S).png"
case "$choice" in
*"Region clipboard"*) grim -g "$(slurp)" - | wl-copy ;;
*"Region file"*) mkdir -p "$dir"; grim -g "$(slurp)" "$file" \
&& notify-send "Screenshot saved" "$file" ;;
*"Full screen clipboard"*) grim - | wl-copy ;;
*"Full screen file"*) mkdir -p "$dir"; grim "$file" \
&& notify-send "Screenshot saved" "$file" ;;
esac ;;
keybinds)
# Read-only cheatsheet, generated from keybinds.nix at build time.
rofi -dmenu -i -p keys < ${cheatsheetFile} >/dev/null || exit 0 ;;
ask)
# Free-text question claude CLI in a terminal (OAuth, no API key).
# Pulled fresh from npm via npx rather than nixpkgs' claude-code,
# which lags behind model releases; npx caches after first run. The
# REPL stays open for follow-ups.
q=$(rofi -dmenu -p claude < /dev/null) || exit 0
[ -n "$q" ] || exit 0
exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;;
"")
choice=$(printf '%s\n' \
"󰀻 Apps" \
@@ -97,6 +155,11 @@ let
"󰃬 Calculator" \
"󰈞 Files" \
"󰖟 Web search" \
"󰖩 Network" \
"󰂯 Bluetooth" \
"󰄄 Capture" \
"󰋖 Keybindings" \
"󰭹 Ask Claude" \
"󰐥 Power" \
| rofi -dmenu -p menu) || exit 0
case "$choice" in
@@ -106,11 +169,16 @@ let
*Calc*) exec "$0" calc ;;
*Files*) exec "$0" files ;;
*Web*) exec "$0" web ;;
*Network*) exec "$0" network ;;
*Bluetooth*) exec "$0" bluetooth ;;
*Capture*) exec "$0" capture ;;
*Keybindings*) exec "$0" keybinds ;;
*Ask*) exec "$0" ask ;;
*Power*) exec "$0" power ;;
esac ;;
*)
echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web]" >&2
echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web|network|bluetooth|capture|keybinds|ask]" >&2
exit 64 ;;
esac
'';
@@ -122,6 +190,7 @@ in
pkgs.libqalculate # qalc, the calc module's engine
pkgs.fd # files module
pkgs.xdg-utils # xdg-open for files/web
pkgs.nodejs # npx, for the Ask Claude module (claude-code from npm)
];
programs.rofi = {