diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 305e678..1323d91 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -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)" ] } } diff --git a/README.md b/README.md index 977135a..372f1bf 100644 --- a/README.md +++ b/README.md @@ -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: Network ▸ nmtui · Bluetooth ▸ blueman · Capture ▸ - screenshot · **keybindings cheatsheet** (parse the bind list → rofi, - so SUPER-? shows every shortcut) · **ask Claude**: free-text → - `$TERMINAL -e claude ""` — 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 diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 30fd8b1..d7a2f1d 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -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, diff --git a/modules/home/keybinds.nix b/modules/home/keybinds.nix new file mode 100644 index 0000000..0fe7f52 --- /dev/null +++ b/modules/home/keybinds.nix @@ -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"; } + ]; +} diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index 4048940..826ee63 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -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 = {