From d1e243dd12961655b121e6882f61cd678da6ea23 Mon Sep 17 00:00:00 2001 From: Anish Govind Date: Sat, 19 Sep 2026 01:14:33 -0700 Subject: [PATCH] hypr,nvim,quickshell: make configs live-editable Symlink three configs straight from the repo with mkOutOfStoreSymlink so edits apply without a rebuild: - home/hypr/hyprland.lua -> reload with `hyprctl reload` - home/hypr/quickshell/** -> restart quickshell - home/nvim/config/** -> next nvim start (lazy-lock.json now writable) Hyprland's config was generated Lua; extract it to a hand-maintained file and keep the palette and per-host monitors as generated fragments (theme.lua / monitors.lua) loaded via dofile. The file also restores the startup hook that imports the Wayland env into systemd and starts graphical-session.target, which the removed home-manager hyprland module used to provide. Verified: lua syntax + execution against a stub hl, and flake check. --- README.md | 26 ++ home/hypr/hyprland.lua | 282 ++++++++++++++++++ home/hypr/hyprland.nix | 606 +++------------------------------------ home/hypr/quickshell.nix | 17 +- home/nvim/default.nix | 18 +- 5 files changed, 374 insertions(+), 575 deletions(-) create mode 100644 home/hypr/hyprland.lua diff --git a/README.md b/README.md index 4b619ab..b5dd2bc 100644 --- a/README.md +++ b/README.md @@ -83,3 +83,29 @@ every home-manager module through `extraSpecialArgs`. Tools that only accept a raw config file (starship, rofi, the herdr picker) carry `@color@` placeholders that are substituted from the same palette at build time, so changing an accent means editing one file. + +## Editing configs: live vs generated + +Most configs are generated from Nix and symlinked read-only from the store, so +they need a `rebuild switch` to change: + +- `foot.ini`, `hyprlock.conf`, `hypridle.conf`, `mako/config`, `starship.toml`, + `rofi/config.rasi`, `.zshenv`/`.zshrc` (Nix attrs, themed) + +Three configs are instead symlinked **straight from the repo** with +`mkOutOfStoreSymlink`, so edits apply without a rebuild: + +| Live file | Applies on | +| --- | --- | +| `home/hypr/hyprland.lua` | `hyprctl reload` | +| `home/hypr/quickshell/**` | quickshell restart | +| `home/nvim/config/**` (incl. `lazy-lock.json`) | next nvim start; `:Lazy update` writes here | + +Because `hyprland.lua` is live, the palette and per-host monitors are emitted as +generated fragments, `~/.config/hypr/theme.lua` and +`~/.config/hypr/monitors.lua`, which the live file loads with `dofile`. Edit the +Lua directly for anything else. + +The tradeoff: live files are not part of the Nix closure, so the repo must exist +at `my.flakeDir` for the symlinks to resolve, and uncommitted edits can drift +from git. Keep them committed like any other tracked file. diff --git a/home/hypr/hyprland.lua b/home/hypr/hyprland.lua new file mode 100644 index 0000000..27cc1d9 --- /dev/null +++ b/home/hypr/hyprland.lua @@ -0,0 +1,282 @@ +-- Silicon Volt Hyprland configuration (hand-maintained). +-- +-- home-manager symlinks this file straight from the repo, so edits apply +-- on `hyprctl reload` without a rebuild. The generated fragments +-- `theme.lua` (from home/theme.nix) and `monitors.lua` (from hypr.monitors) +-- live next to this file and are loaded below. + +local configDir = (os.getenv("XDG_CONFIG_HOME") or (os.getenv("HOME") .. "/.config")) .. "/hypr" +local theme = dofile(configDir .. "/theme.lua") +local monitors = dofile(configDir .. "/monitors.lua") + + +-- settings.locals +local browser = "brave" +local mainMod = "SUPER" +local terminal = "foot" + +-- settings.curve +hl.curve("myBezier", { + ["points"] = { + { + 0.05, + 0.9 + }, + { + 0.1, + 1.05 + } + }, + ["type"] = "bezier" +}) + +-- settings.animation +hl.animation({ + ["bezier"] = "myBezier", + ["enabled"] = true, + ["leaf"] = "windows", + ["speed"] = 7 +}) +hl.animation({ + ["bezier"] = "default", + ["enabled"] = true, + ["leaf"] = "windowsOut", + ["speed"] = 7, + ["style"] = "popin 80%" +}) +hl.animation({ + ["bezier"] = "default", + ["enabled"] = true, + ["leaf"] = "border", + ["speed"] = 10 +}) +hl.animation({ + ["bezier"] = "default", + ["enabled"] = true, + ["leaf"] = "borderangle", + ["speed"] = 8 +}) +hl.animation({ + ["bezier"] = "default", + ["enabled"] = true, + ["leaf"] = "fade", + ["speed"] = 7 +}) +hl.animation({ + ["bezier"] = "default", + ["enabled"] = true, + ["leaf"] = "workspaces", + ["speed"] = 6 +}) + +-- settings.bind +hl.bind((mainMod .. " + Q"), (hl.dsp.exec_cmd(terminal))) +hl.bind((mainMod .. " + C"), (hl.dsp.window.close())) +hl.bind((mainMod .. " + SPACE"), (hl.dsp.exec_cmd("rofi -show combi -show-icons"))) +hl.bind((mainMod .. " + N"), (hl.dsp.exec_cmd("swaync-client -t -sw"))) +hl.bind((mainMod .. " + TAB"), (hl.dsp.exec_cmd("qs ipc call overview toggle"))) +hl.bind((mainMod .. " + V"), (hl.dsp.window.float({ action = "toggle" }))) +hl.bind((mainMod .. " + R"), (hl.dsp.exec_cmd("rofi -show combi"))) +hl.bind((mainMod .. " + G"), (hl.dsp.group.toggle())) +hl.bind((mainMod .. " + F"), (hl.dsp.window.fullscreen({ fullscreen = 0 }))) +hl.bind((mainMod .. " + h"), (hl.dsp.focus({ direction = "left" }))) +hl.bind((mainMod .. " + l"), (hl.dsp.focus({ direction = "right" }))) +hl.bind((mainMod .. " + k"), (hl.dsp.focus({ direction = "up" }))) +hl.bind((mainMod .. " + j"), (hl.dsp.focus({ direction = "down" }))) +hl.bind((mainMod .. " + e"), (hl.dsp.group.next())) +hl.bind((mainMod .. " + d"), (hl.dsp.group.prev())) +hl.bind((mainMod .. " + SHIFT + h"), (hl.dsp.window.move({ direction = "left" }))) +hl.bind((mainMod .. " + SHIFT + l"), (hl.dsp.window.move({ direction = "right" }))) +hl.bind((mainMod .. " + SHIFT + k"), (hl.dsp.window.move({ direction = "up" }))) +hl.bind((mainMod .. " + SHIFT + j"), (hl.dsp.window.move({ direction = "down" }))) +hl.bind((mainMod .. " + mouse_down"), (hl.dsp.focus({ workspace = "e+1" }))) +hl.bind((mainMod .. " + mouse_up"), (hl.dsp.focus({ workspace = "e-1" }))) +hl.bind((mainMod .. " + S"), (hl.dsp.exec_cmd("hyprlock"))) +hl.bind((mainMod .. " + b"), (hl.dsp.exec_cmd(browser))) +hl.bind((mainMod .. " + RETURN"), (hl.dsp.workspace.toggle_special("scratch"))) +hl.bind((mainMod .. " + SHIFT + RETURN"), (hl.dsp.window.move({ workspace = "special:scratch" }))) +hl.bind((mainMod .. " + M"), (hl.dsp.workspace.toggle_special("messaging"))) +hl.bind((mainMod .. " + mouse:272"), (hl.dsp.window.drag()), { + ["mouse"] = true +}) +hl.bind((mainMod .. " + mouse:273"), (hl.dsp.window.resize()), { + ["mouse"] = true +}) +hl.bind("XF86AudioRaiseVolume", (hl.dsp.exec_cmd("pactl -- set-sink-volume @DEFAULT_SINK@ +5%")), { + ["repeating"] = true +}) +hl.bind("XF86AudioLowerVolume", (hl.dsp.exec_cmd("pactl -- set-sink-volume @DEFAULT_SINK@ -5%")), { + ["locked"] = true, + ["repeating"] = true +}) +hl.bind("XF86AudioMute", (hl.dsp.exec_cmd("pactl -- set-sink-mute @DEFAULT_SINK@ toggle")), { + ["locked"] = true, + ["repeating"] = true +}) +hl.bind("XF86MonBrightnessUp", (hl.dsp.exec_cmd("brightnessctl set 5%+")), { + ["locked"] = true, + ["repeating"] = true +}) +hl.bind("XF86MonBrightnessDown", (hl.dsp.exec_cmd("brightnessctl set 5%-")), { + ["locked"] = true, + ["repeating"] = true +}) +hl.bind("switch:off:Lid Switch", (hl.dsp.exec_cmd("m=$(hyprctl monitors | awk '$1==\"Monitor\"{print $2; exit}'); [ -n \"$m\" ] && hyprctl keyword monitor \"$m, preferred, auto, auto\"")), { + ["locked"] = true +}) +hl.bind("switch:on:Lid Switch", (hl.dsp.exec_cmd("m=$(hyprctl monitors | awk '$1==\"Monitor\"{print $2; exit}'); [ -n \"$m\" ] && hyprctl keyword monitor \"$m, disable\"")), { + ["locked"] = true +}) +hl.bind((mainMod .. " + 1"), (hl.dsp.focus({ workspace = 1 }))) +hl.bind((mainMod .. " + SHIFT + 1"), (hl.dsp.window.move({ workspace = 1 }))) +hl.bind((mainMod .. " + 2"), (hl.dsp.focus({ workspace = 2 }))) +hl.bind((mainMod .. " + SHIFT + 2"), (hl.dsp.window.move({ workspace = 2 }))) +hl.bind((mainMod .. " + 3"), (hl.dsp.focus({ workspace = 3 }))) +hl.bind((mainMod .. " + SHIFT + 3"), (hl.dsp.window.move({ workspace = 3 }))) +hl.bind((mainMod .. " + 4"), (hl.dsp.focus({ workspace = 4 }))) +hl.bind((mainMod .. " + SHIFT + 4"), (hl.dsp.window.move({ workspace = 4 }))) +hl.bind((mainMod .. " + 5"), (hl.dsp.focus({ workspace = 5 }))) +hl.bind((mainMod .. " + SHIFT + 5"), (hl.dsp.window.move({ workspace = 5 }))) +hl.bind((mainMod .. " + 6"), (hl.dsp.focus({ workspace = 6 }))) +hl.bind((mainMod .. " + SHIFT + 6"), (hl.dsp.window.move({ workspace = 6 }))) +hl.bind((mainMod .. " + 7"), (hl.dsp.focus({ workspace = 7 }))) +hl.bind((mainMod .. " + SHIFT + 7"), (hl.dsp.window.move({ workspace = 7 }))) +hl.bind((mainMod .. " + 8"), (hl.dsp.focus({ workspace = 8 }))) +hl.bind((mainMod .. " + SHIFT + 8"), (hl.dsp.window.move({ workspace = 8 }))) +hl.bind((mainMod .. " + 9"), (hl.dsp.focus({ workspace = 9 }))) +hl.bind((mainMod .. " + SHIFT + 9"), (hl.dsp.window.move({ workspace = 9 }))) +hl.bind((mainMod .. " + 0"), (hl.dsp.focus({ workspace = 10 }))) +hl.bind((mainMod .. " + SHIFT + 0"), (hl.dsp.window.move({ workspace = 10 }))) + +-- settings.config +hl.config({ + ["animations"] = { + ["enabled"] = true + }, + ["cursor"] = { + ["inactive_timeout"] = 3, + ["no_hardware_cursors"] = false + }, + ["decoration"] = { + ["blur"] = { + ["brightness"] = 0.9, + ["contrast"] = 1.1, + ["enabled"] = true, + ["ignore_opacity"] = true, + ["new_optimizations"] = true, + ["passes"] = 2, + ["size"] = 4, + ["vibrancy"] = 0.1 + }, + ["rounding"] = 4, + ["shadow"] = { + ["color"] = "rgba(00000066)", + ["enabled"] = true, + ["range"] = 12, + ["render_power"] = 3 + } + }, + ["dwindle"] = { + ["preserve_split"] = true + }, + ["general"] = { + ["border_size"] = 2, + ["col"] = { + ["active_border"] = { + ["angle"] = 45, + ["colors"] = { + "rgba(" .. theme.bare.accentVolt .. "ee)", + "rgba(" .. theme.bare.accentVoltLight .. "ee)" + } + }, + ["inactive_border"] = "rgba(" .. theme.bare.textPrimary .. "14)" + }, + ["gaps_in"] = 5, + ["gaps_out"] = 10, + ["layout"] = "dwindle" + }, + ["group"] = { + ["col"] = { + ["border_active"] = { + ["angle"] = 45, + ["colors"] = { + "rgba(" .. theme.bare.accentVolt .. "ee)", + "rgba(" .. theme.bare.accentVoltLight .. "ee)" + } + }, + ["border_inactive"] = "rgba(" .. theme.bare.textPrimary .. "14)" + }, + ["groupbar"] = { + ["col"] = { + ["active"] = "rgba(" .. theme.bare.accentVolt .. "22)", + ["inactive"] = "rgba(" .. theme.bare.textPrimary .. "08)" + }, + ["font_family"] = "JetBrainsMono Nerd Font", + ["font_size"] = 14 + } + }, + ["input"] = { + ["follow_mouse"] = 1, + ["kb_layout"] = "us", + ["repeat_delay"] = 300, + ["repeat_rate"] = 50, + ["sensitivity"] = 0, + ["touchpad"] = { + ["natural_scroll"] = false + } + }, + ["master"] = { + ["new_status"] = "master" + }, + ["misc"] = { + ["disable_hyprland_logo"] = true, + ["disable_splash_rendering"] = true + } +}) + +-- settings.env +hl.env("XCURSOR_SIZE", "24") +hl.env("XDG_SESSION_TYPE", "wayland") + +-- monitors (generated from hypr.monitors) +for _, m in ipairs(monitors) do + hl.monitor(m) +end +-- settings.window_rule +hl.window_rule({ + ["match"] = { + ["class"] = "Signal" + }, + ["name"] = "signal-messaging", + ["workspace"] = "special:messaging" +}) +hl.window_rule({ + ["match"] = { + ["class"] = "vesktop" + }, + ["name"] = "vesktop-messaging", + ["workspace"] = "special:messaging" +}) +hl.window_rule({ + ["float"] = false, + ["match"] = { + ["class"] = "ghidra-Ghidra" + }, + ["name"] = "ghidra-tile" +}) + +-- settings.workspace_rule +hl.workspace_rule({ + ["on_created_empty"] = "[] togglegroup", + ["workspace"] = "name:messaging" +}) + + +-- Start the systemd user session target after importing the Wayland +-- environment, so the desktop services declared in home/hypr/services.nix +-- and home/hypr/quickshell.nix (hypridle, hyprpaper, mako, cliphist, +-- quickshell, polkit agent) are pulled in. This replaces the hook that the +-- home-manager hyprland module used to generate. +hl.on("hyprland.start", function() + hl.exec_cmd("dbus-update-activation-environment --systemd DISPLAY HYPRLAND_INSTANCE_SIGNATURE WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE && systemctl --user start graphical-session.target") +end) diff --git a/home/hypr/hyprland.nix b/home/hypr/hyprland.nix index febe876..7e0813a 100644 --- a/home/hypr/hyprland.nix +++ b/home/hypr/hyprland.nix @@ -2,11 +2,47 @@ lib, config, theme, + my, ... }: let - mkLua = lib.generators.mkLuaInline; + # Minimal Lua value renderer for the generated fragments. + toLua = + v: + if builtins.isString v then + ''"${v}"'' + else if builtins.isBool v then + (if v then "true" else "false") + else if builtins.isInt v || builtins.isFloat v then + toString v + else if v == null then + "nil" + else if builtins.isList v then + "{ " + lib.concatMapStringsSep ", " toLua v + " }" + else if builtins.isAttrs v then + "{ " + lib.concatStringsSep ", " (lib.mapAttrsToList (k: val: "${k} = ${toLua val}") v) + " }" + else + throw "toLua: unsupported value type"; + + themeLua = "return " + toLua { inherit (theme) colors bare; }; + + monitorsLua = + "return " + + toLua ( + map ( + m: + { + inherit (m) + output + mode + position + scale + ; + } + // lib.optionalAttrs (m.vrr != null) { inherit (m) vrr; } + ) config.hypr.monitors + ); in { # ------------------------------------------------------------------------- @@ -14,8 +50,8 @@ in # # Hosts declare their displays once, in hosts//default.nix, via: # home-manager.users..hypr.monitors = [ { ... } ]; - # and the setting flows into Hyprland here. The default (empty output + - # preferred mode) works on any machine with any display. + # The list is rendered to ~/.config/hypr/monitors.lua, which the + # hand-maintained hyprland.lua pulls in with require("monitors"). # ------------------------------------------------------------------------- options.hypr.monitors = lib.mkOption { description = "Hyprland monitor rules for this host"; @@ -59,563 +95,13 @@ in }; config = { - wayland.windowManager.hyprland.settings.monitor = map ( - m: - { - inherit (m) - output - mode - position - scale - ; - } - // (lib.optionalAttrs (m.vrr != null) { inherit (m) vrr; }) - ) config.hypr.monitors; + # Live, hand-maintained config: symlinked straight to the repo so edits + # apply on `hyprctl reload` without a rebuild. + xdg.configFile."hypr/hyprland.lua".source = + config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/hypr/hyprland.lua"; - wayland.windowManager.hyprland = { - enable = true; - configType = "lua"; - package = null; # Hyprland package is managed system-wide via NixOS programs.hyprland - - settings = { - # ----------------------------------------------------------------------- - # 1. Variables & Modifiers (Rendered as Lua local variables) - # ----------------------------------------------------------------------- - mainMod = { - _var = "SUPER"; - }; - terminal = { - _var = "foot"; - }; - browser = { - _var = "brave"; - }; - - # ----------------------------------------------------------------------- - # 2. Monitors (hl.monitor) - declared per-host via hypr.monitors - # (see hosts//default.nix); defaults to any display @ preferred. - # ----------------------------------------------------------------------- - - # ----------------------------------------------------------------------- - # 3. Environment Variables (hl.env) - # ----------------------------------------------------------------------- - env = [ - { - _args = [ - "XCURSOR_SIZE" - "24" - ]; - } - { - _args = [ - "XDG_SESSION_TYPE" - "wayland" - ]; - } - ]; - - # ----------------------------------------------------------------------- - # 4. Main Configuration (hl.config) - Strictly validates against Hyprland 0.55+ schema - # ----------------------------------------------------------------------- - config = { - input = { - kb_layout = "us"; - repeat_delay = 300; - repeat_rate = 50; - follow_mouse = 1; - sensitivity = 0; - touchpad = { - natural_scroll = false; - }; - }; - - cursor = { - inactive_timeout = 3; - # Hardware cursors ON (explicit). Note: this replaces the old - # WLR_NO_HARDWARE_CURSORS=1, which forced software rendering. - no_hardware_cursors = false; - }; - - general = { - gaps_in = 5; - gaps_out = 10; - border_size = 2; - col = { - # Silicon Volt: electric lime gradient on focus, glass gray at rest - active_border = { - colors = [ - "rgba(${theme.bare.accentVolt}ee)" - "rgba(${theme.bare.accentVoltLight}ee)" - ]; - angle = 45; - }; - inactive_border = "rgba(${theme.bare.textPrimary}14)"; - }; - layout = "dwindle"; - }; - - group = { - groupbar = { - font_size = 14; - font_family = "JetBrainsMono Nerd Font"; - col = { - active = "rgba(${theme.bare.accentVolt}22)"; - inactive = "rgba(${theme.bare.textPrimary}08)"; - }; - }; - - col = { - border_active = { - colors = [ - "rgba(${theme.bare.accentVolt}ee)" - "rgba(${theme.bare.accentVoltLight}ee)" - ]; - angle = 45; - }; - border_inactive = "rgba(${theme.bare.textPrimary}14)"; - }; - }; - - misc = { - disable_hyprland_logo = true; - disable_splash_rendering = true; - }; - - decoration = { - rounding = 4; - - # Subtle glass blur to match the brutalist-glass aesthetic - blur = { - enabled = true; - size = 4; - passes = 2; - new_optimizations = true; - ignore_opacity = true; - contrast = 1.1; - brightness = 0.9; - vibrancy = 0.1; - }; - - shadow = { - enabled = true; - range = 12; - render_power = 3; - color = "rgba(00000066)"; - }; - }; - - animations = { - enabled = true; - }; - - dwindle = { - preserve_split = true; - }; - - master = { - new_status = "master"; - }; - }; - - # ----------------------------------------------------------------------- - # 5. Curves & Animations (hl.curve & hl.animation) - # ----------------------------------------------------------------------- - curve = [ - { - _args = [ - "myBezier" - { - type = "bezier"; - points = [ - [ - 0.05 - 0.9 - ] - [ - 0.1 - 1.05 - ] - ]; - } - ]; - } - ]; - - animation = [ - { - leaf = "windows"; - enabled = true; - speed = 7; - bezier = "myBezier"; - } - { - leaf = "windowsOut"; - enabled = true; - speed = 7; - bezier = "default"; - style = "popin 80%"; - } - { - leaf = "border"; - enabled = true; - speed = 10; - bezier = "default"; - } - { - leaf = "borderangle"; - enabled = true; - speed = 8; - bezier = "default"; - } - { - leaf = "fade"; - enabled = true; - speed = 7; - bezier = "default"; - } - { - leaf = "workspaces"; - enabled = true; - speed = 6; - bezier = "default"; - } - ]; - - # ----------------------------------------------------------------------- - # 6. Autostart Daemons & Apps - # ----------------------------------------------------------------------- - # Desktop daemons (quickshell, hyprpaper, mako, cliphist and the - # polkit agent) are declared as systemd user services in - # home/hypr/services.nix and home/hypr/quickshell.nix, tied to the - # Wayland session target instead of exec-once. - - # ----------------------------------------------------------------------- - # 7. Keybindings (hl.bind) - # ----------------------------------------------------------------------- - bind = [ - # Launchers & Window Controls - { - _args = [ - (mkLua "mainMod .. \" + Q\"") - (mkLua "hl.dsp.exec_cmd(terminal)") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + C\"") - (mkLua "hl.dsp.window.close()") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + SPACE\"") - (mkLua "hl.dsp.exec_cmd(\"rofi -show combi -show-icons\")") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + N\"") - (mkLua "hl.dsp.exec_cmd(\"swaync-client -t -sw\")") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + TAB\"") - (mkLua "hl.dsp.exec_cmd(\"qs ipc call overview toggle\")") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + V\"") - (mkLua "hl.dsp.window.float({ action = \"toggle\" })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + R\"") - (mkLua "hl.dsp.exec_cmd(\"rofi -show combi\")") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + G\"") - (mkLua "hl.dsp.group.toggle()") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + F\"") - (mkLua "hl.dsp.window.fullscreen({ fullscreen = 0 })") - ]; - } - - # Move focus with mainMod + vim - { - _args = [ - (mkLua "mainMod .. \" + h\"") - (mkLua "hl.dsp.focus({ direction = \"left\" })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + l\"") - (mkLua "hl.dsp.focus({ direction = \"right\" })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + k\"") - (mkLua "hl.dsp.focus({ direction = \"up\" })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + j\"") - (mkLua "hl.dsp.focus({ direction = \"down\" })") - ]; - } - - # Move focus within a grouped page - { - _args = [ - (mkLua "mainMod .. \" + e\"") - (mkLua "hl.dsp.group.next()") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + d\"") - (mkLua "hl.dsp.group.prev()") - ]; - } - - # Move window or group with vim keys - { - _args = [ - (mkLua "mainMod .. \" + SHIFT + h\"") - (mkLua "hl.dsp.window.move({ direction = \"left\" })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + SHIFT + l\"") - (mkLua "hl.dsp.window.move({ direction = \"right\" })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + SHIFT + k\"") - (mkLua "hl.dsp.window.move({ direction = \"up\" })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + SHIFT + j\"") - (mkLua "hl.dsp.window.move({ direction = \"down\" })") - ]; - } - - # Scroll through existing workspaces with mainMod + scroll - { - _args = [ - (mkLua "mainMod .. \" + mouse_down\"") - (mkLua "hl.dsp.focus({ workspace = \"e+1\" })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + mouse_up\"") - (mkLua "hl.dsp.focus({ workspace = \"e-1\" })") - ]; - } - - # Lock shortcut - { - _args = [ - (mkLua "mainMod .. \" + S\"") - (mkLua "hl.dsp.exec_cmd(\"hyprlock\")") - ]; - } - - # Quick access applications - { - _args = [ - (mkLua "mainMod .. \" + b\"") - (mkLua "hl.dsp.exec_cmd(browser)") - ]; - } - - # Scratch workspace - { - _args = [ - (mkLua "mainMod .. \" + RETURN\"") - (mkLua "hl.dsp.workspace.toggle_special(\"scratch\")") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + SHIFT + RETURN\"") - (mkLua "hl.dsp.window.move({ workspace = \"special:scratch\" })") - ]; - } - - # Messaging workspace - { - _args = [ - (mkLua "mainMod .. \" + M\"") - (mkLua "hl.dsp.workspace.toggle_special(\"messaging\")") - ]; - } - - # Move/resize windows with mouse and dragging - { - _args = [ - (mkLua "mainMod .. \" + mouse:272\"") - (mkLua "hl.dsp.window.drag()") - { mouse = true; } - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + mouse:273\"") - (mkLua "hl.dsp.window.resize()") - { mouse = true; } - ]; - } - - # Function Row Keys - { - _args = [ - "XF86AudioRaiseVolume" - (mkLua "hl.dsp.exec_cmd(\"pactl -- set-sink-volume @DEFAULT_SINK@ +5%\")") - { repeating = true; } - ]; - } - { - _args = [ - "XF86AudioLowerVolume" - (mkLua "hl.dsp.exec_cmd(\"pactl -- set-sink-volume @DEFAULT_SINK@ -5%\")") - { - repeating = true; - locked = true; - } - ]; - } - { - _args = [ - "XF86AudioMute" - (mkLua "hl.dsp.exec_cmd(\"pactl -- set-sink-mute @DEFAULT_SINK@ toggle\")") - { - repeating = true; - locked = true; - } - ]; - } - { - _args = [ - "XF86MonBrightnessUp" - (mkLua "hl.dsp.exec_cmd(\"brightnessctl set 5%+\")") - { - repeating = true; - locked = true; - } - ]; - } - { - _args = [ - "XF86MonBrightnessDown" - (mkLua "hl.dsp.exec_cmd(\"brightnessctl set 5%-\")") - { - repeating = true; - locked = true; - } - ]; - } - - # Lid switch - generically detects the embedded (eDP*) panel on any laptop - { - _args = [ - "switch:off:Lid Switch" - (mkLua "hl.dsp.exec_cmd(\"m=$(hyprctl monitors | awk '$1==\\\"Monitor\\\"{print $2; exit}'); [ -n \\\"$m\\\" ] && hyprctl keyword monitor \\\"$m, preferred, auto, auto\\\"\")") - { locked = true; } - ]; - } - { - _args = [ - "switch:on:Lid Switch" - (mkLua "hl.dsp.exec_cmd(\"m=$(hyprctl monitors | awk '$1==\\\"Monitor\\\"{print $2; exit}'); [ -n \\\"$m\\\" ] && hyprctl keyword monitor \\\"$m, disable\\\"\")") - { locked = true; } - ]; - } - ] - # Workspace 1-9 switching and moving - ++ (lib.concatMap (i: [ - { - _args = [ - (mkLua "mainMod .. \" + ${toString i}\"") - (mkLua "hl.dsp.focus({ workspace = ${toString i} })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + SHIFT + ${toString i}\"") - (mkLua "hl.dsp.window.move({ workspace = ${toString i} })") - ]; - } - ]) (lib.range 1 9)) - # Workspace 10 switching and moving (mapped to key 0) - ++ [ - { - _args = [ - (mkLua "mainMod .. \" + 0\"") - (mkLua "hl.dsp.focus({ workspace = 10 })") - ]; - } - { - _args = [ - (mkLua "mainMod .. \" + SHIFT + 0\"") - (mkLua "hl.dsp.window.move({ workspace = 10 })") - ]; - } - ]; - - # ----------------------------------------------------------------------- - # 8. Workspace Rules (hl.workspace_rule) - # ----------------------------------------------------------------------- - workspace_rule = [ - { - workspace = "name:messaging"; - on_created_empty = "[] togglegroup"; - } - ]; - - # ----------------------------------------------------------------------- - # 9. Window Rules (hl.window_rule) - # ----------------------------------------------------------------------- - window_rule = [ - { - name = "signal-messaging"; - match = { - class = "Signal"; - }; - workspace = "special:messaging"; - } - { - name = "vesktop-messaging"; - match = { - class = "vesktop"; - }; - workspace = "special:messaging"; - } - { - name = "ghidra-tile"; - match = { - class = "ghidra-Ghidra"; - }; - float = false; - } - ]; - }; - }; + # Generated fragments required by the live config. + xdg.configFile."hypr/theme.lua".text = themeLua; + xdg.configFile."hypr/monitors.lua".text = monitorsLua; }; } diff --git a/home/hypr/quickshell.nix b/home/hypr/quickshell.nix index 2533d9c..d7bc393 100644 --- a/home/hypr/quickshell.nix +++ b/home/hypr/quickshell.nix @@ -1,4 +1,10 @@ -{ pkgs, inputs, ... }: +{ + pkgs, + inputs, + config, + my, + ... +}: let quickshellPkg = @@ -16,11 +22,10 @@ in { # Quickshell overview module for Hyprland (Super+Tab workspace overview). # Vendored from: https://github.com/Shanu-Kumawat/quickshell-overview - # Placed at the quickshell config root so a bare `qs` finds shell.qml. - xdg.configFile."quickshell" = { - source = ./quickshell; - recursive = true; - }; + # Symlinked straight from the repo so edits are live; placed at the + # quickshell config root so a bare `qs` finds shell.qml. + xdg.configFile."quickshell".source = + config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/hypr/quickshell"; # Run quickshell as a systemd user service tied to the Wayland session. programs.quickshell = { diff --git a/home/nvim/default.nix b/home/nvim/default.nix index 5f1ce84..b897b8e 100644 --- a/home/nvim/default.nix +++ b/home/nvim/default.nix @@ -2,6 +2,7 @@ config, pkgs, lib, + my, ... }: @@ -72,15 +73,14 @@ in ]; }; - # Symlink LazyVim configuration to ~/.config/nvim recursively. This includes - # lazy-lock.json, which pins every plugin to a specific commit for - # reproducible builds. To update plugins, run `:Lazy update` in a writable - # copy and commit the resulting lazy-lock.json back to this repo, since the - # linked file is read-only. - xdg.configFile."nvim" = { - source = ./config; - recursive = true; - }; + # Live LazyVim config symlinked straight from the repo: edits (and + # `:Lazy update` writes to lazy-lock.json) apply without a rebuild. + xdg.configFile."nvim/init.lua".source = + config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/nvim/config/init.lua"; + xdg.configFile."nvim/lua".source = + config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/nvim/config/lua"; + xdg.configFile."nvim/lazy-lock.json".source = + config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/nvim/config/lazy-lock.json"; # Symlink precompiled Treesitter parsers directly into ~/.config/nvim/parser xdg.configFile."nvim/parser".source = "${treesitterParsers}/parser";