diff --git a/flake.lock b/flake.lock
index 0ee35fc..a065293 100644
--- a/flake.lock
+++ b/flake.lock
@@ -146,11 +146,11 @@
]
},
"locked": {
- "lastModified": 1778503501,
- "narHash": "sha256-08L/X4/do7nET4rzidJ76eV/1r+mB7DchVpdPypsghc=",
+ "lastModified": 1779113444,
+ "narHash": "sha256-/L61sT1PIKmGWIQpIh0uJGH/ANvcsf6y4alxtb9kelg=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "85ba629c79449badf4338117c27f0ee92b4b9f1a",
+ "rev": "74f170c62d57f90e656841f1f699e6bdf40f0a24",
"type": "github"
},
"original": {
diff --git a/profiles/contexts/desktop/wm/hyprland.nix b/profiles/contexts/desktop/wm/hyprland.nix
index 75e3cd8..c14b80c 100644
--- a/profiles/contexts/desktop/wm/hyprland.nix
+++ b/profiles/contexts/desktop/wm/hyprland.nix
@@ -1,4 +1,6 @@
-# Hyprland window manager with keybindings, window rules, idle/lock, and hyprpaper.
+# Hyprland window manager — Lua config for Hyprland 0.55+
+# Home-Manager configType = "lua" generates hyprland.lua using hl.* API.
+# API reference: https://wiki.hypr.land/Configuring/Start/
{
config,
lib,
@@ -6,218 +8,217 @@
}:
with lib; let
cfg = config.desktop.wm.hyprland;
+ p = config.colorScheme.palette;
in {
options.desktop.wm.hyprland.enable = mkEnableOption "Hyprland window manager";
config = mkIf cfg.enable {
wayland.windowManager.hyprland = {
+ configType = "lua";
+
settings = {
- xwayland = {
- force_zero_scaling = true;
+ # ── Lua local variables (rendered as: local mainMod = "SUPER") ──
+ mainMod = {_var = "SUPER";};
+ terminal = {_var = "ghostty";};
+
+ # ── Config sections (rendered as: hl.config({...})) ──
+ config = {
+ xwayland = {force_zero_scaling = true;};
+
+ input = {
+ kb_layout = "de,us";
+ kb_variant = "";
+ kb_model = "";
+ kb_rules = "";
+ kb_options = "ctrl:nocaps";
+ follow_mouse = 1;
+ };
+
+ general = {
+ gaps_in = 5;
+ gaps_out = 5;
+ border_size = 1;
+ col = {
+ active_border = {colors = ["rgba(9742b5ee)" "rgba(9742b5ee)"]; angle = 45;};
+ inactive_border = "rgba(${p.base03}aa)";
+ };
+ layout = "dwindle";
+ };
+
+ decoration = {
+ shadow = {
+ enabled = true;
+ range = 60;
+ render_power = 3;
+ color = "rgba(${p.base00}66)";
+ scale = 0.97;
+ };
+ rounding = 8;
+ blur = {
+ enabled = true;
+ size = 3;
+ passes = 3;
+ };
+ active_opacity = 0.9;
+ inactive_opacity = 0.5;
+ };
+
+ animations = {enabled = true;};
+
+ dwindle = {
+ pseudotile = true;
+ preserve_split = true;
+ };
+
+ master = {new_status = "master";};
};
- exec-once = [
- "hyprpanel"
- "while ! hyprpaper-random; do sleep 0.5; done"
- "wl-paste --type text --watch cliphist store"
- "wl-paste --type image --watch cliphist store"
- "vibetyper"
- ];
-
+ # ── Environment variables (rendered as: hl.env("KEY", "VALUE")) ──
env = [
- "XCURSOR_SIZE,32"
- "HYPRCURSOR_THEME,Bibata-Modern-Ice"
- "WLR_NO_HARDWARE_CURSORS,1"
- "GTK_THEME,Dracula"
- "XDG_CURRENT_DESKTOP,Hyprland"
- "XDG_SESSION_TYPE,wayland"
- "XDG_SESSION_DESKTOP,Hyprland"
- "XKB_DEFAULT_LAYOUT,de"
- "NIXOS_OZONE_WL,1"
+ { _args = ["XCURSOR_SIZE" "32"]; }
+ { _args = ["HYPRCURSOR_THEME" "Bibata-Modern-Ice"]; }
+ { _args = ["WLR_NO_HARDWARE_CURSORS" "1"]; }
+ { _args = ["GTK_THEME" "Dracula"]; }
+ { _args = ["XDG_CURRENT_DESKTOP" "Hyprland"]; }
+ { _args = ["XDG_SESSION_TYPE" "wayland"]; }
+ { _args = ["XDG_SESSION_DESKTOP" "Hyprland"]; }
+ { _args = ["XKB_DEFAULT_LAYOUT" "de"]; }
+ { _args = ["NIXOS_OZONE_WL" "1"]; }
];
- input = {
- kb_layout = "de,us";
- kb_variant = "";
- kb_model = "";
- kb_rules = "";
- kb_options = "ctrl:nocaps";
- follow_mouse = 1;
- };
-
- general = {
- gaps_in = 5;
- gaps_out = 5;
- border_size = 1;
- "col.active_border" = "rgba(9742b5ee) rgba(9742b5ee) 45deg";
- "col.inactive_border" = "rgba(${config.colorScheme.palette.base03}aa)";
- layout = "dwindle";
- };
-
- decoration = {
- shadow = {
- enabled = true;
- range = 60;
- render_power = 3;
- color = "rgba(${config.colorScheme.palette.base00}66)";
- offset = "1 2";
- scale = 0.97;
- };
- rounding = 8;
- blur = {
- enabled = true;
- size = 3;
- passes = 3;
- };
- active_opacity = 0.9;
- inactive_opacity = 0.5;
- };
-
- animations = {
- enabled = true;
- bezier = "myBezier, 0.05, 0.9, 0.1, 1.05";
- animation = [
- "windows, 1, 7, myBezier"
- "windowsOut, 1, 7, default, popin 80%"
- "border, 1, 10, default"
- "borderangle, 1, 8, default"
- "fade, 1, 7, default"
- "workspaces, 1, 6, default"
- ];
- };
-
- dwindle = {
- pseudotile = true;
- preserve_split = true;
- };
-
- master = {
- new_status = "master";
- };
-
+ # ── Per-device config (rendered as: hl.device({...})) ──
device = [
- {
- name = "epic-mouse-v1";
- sensitivity = -0.5;
- }
- {
- name = "zsa-technology-labs-moonlander-mark-i";
- kb_layout = "us";
- }
- {
- name = "keychron-keychron-k7";
- kb_layout = "us";
- }
- ];
-
- windowrule = [
- "match:class file_progress, float on"
- "match:class confirm, float on"
- "match:class dialog, float on"
- "match:class download, float on"
- "match:class notification, float on"
- "match:class error, float on"
- "match:class splash, float on"
- "match:class confirmreset, float on"
- "match:title Open File, float on"
- "match:title branchdialog, float on"
- "match:class pavucontrol-qt, float on"
- "match:class pavucontrol, float on"
- "match:class class:^(espanso)$, float on"
- "match:class wlogout, fullscreen on"
- "match:title wlogout, float on"
- "match:title wlogout, fullscreen on"
- "match:class mpv, float on"
- "match:class mpv, idle_inhibit focus"
- "match:class mpv, opacity 1.0 override"
- "match:title ^(Media viewer)$, float on"
- "match:title ^(Volume Control)$, float on"
- "match:title ^(Picture-in-Picture)$, float on"
- "match:title ^(floating-pomodoro)$, float on"
- "match:title ^(floating-pomodoro)$, size 250 50"
- "match:title ^(floating-pomodoro)$, move 12 (monitor_h-150)"
- "match:title ^(floating-pomodoro)$, pin on"
- "match:initial_title .*streamlabs.com.*, float on"
- "match:initial_title .*streamlabs.com.*, pin on"
- "match:initial_title .*streamlabs.com.*, size 800 400"
- "match:initial_title .*alert-box.*, move 100%-820 102"
- "match:initial_title .*chat-box.*, move 100%-820 512"
- "match:initial_title .*streamlabs.com.*, opacity 0.5 override"
- "match:initial_title .*streamlabs.com.*, idle_inhibit focus"
- "match:initial_title .*streamlabs.com.*, no_anim on"
- "match:initial_title .*streamlabs.com.*, decorate off"
- "match:initial_title .*streamlabs.com.*, no_shadow on"
- "match:initial_title .*streamlabs.com.*, no_blur on"
- "match:class ^vibe-typer$, match:title ^Recording Indicator$, no_blur on"
- "border_color rgb(ffffff), match:xwayland 1"
- ];
-
- "$mainMod" = "SUPER";
- "$terminal" = "ghostty";
-
- bind = [
- "$mainMod, return, exec, $terminal nu -c zellij-ps"
- "$mainMod, t, exec, $terminal -e nu -c 'nitch; exec nu'"
- "$mainMod SHIFT, t, exec, launch-timer"
- "$mainMod, n, exec, $terminal -e nvim"
- "$mainMod, z, exec, uwsm app -- zeditor"
- "$mainMod, o, exec, hyprctl dispatch setprop activewindow opaque toggle"
- "$mainMod, r, exec, hyprctl dispatch focuswindow \"initialtitle:.*alert-box.*\" && hyprctl dispatch moveactive exact 4300 102 && hyprctl dispatch focuswindow \"initialtitle:.*chat-box.*\" && hyprctl dispatch moveactive exact 4300 512"
- "$mainMod, b, exec, uwsm app -- thunar"
- "$mainMod SHIFT, B, exec, uwsm app -- vivaldi"
- "$mainMod, Escape, exec, uwsm app -- wlogout -p layer-shell"
- "$mainMod, Space, togglefloating"
- "$mainMod, q, killactive"
- "$mainMod, M, exit"
- "$mainMod, F, fullscreen"
- "$mainMod SHIFT, V, togglefloating"
- "$mainMod, D, exec, uwsm app -- rofi -show drun -run-command \"uwsm app -- {cmd}\""
- "$mainMod, V, exec, uwsm app -- cliphist list | rofi -dmenu | cliphist decode | wl-copy"
- "$mainMod, C, exec, bash -c 'FILE=/tmp/screenshot_$(date +%s).png; grim -g \"$(slurp)\" \"$FILE\" && ksnip \"$FILE\"'"
- "$mainMod SHIFT, S, exec, uwsm app -- rofi -show emoji"
- "$mainMod, P, exec, uwsm app -- rofi-pass"
- "$mainMod SHIFT, P, pseudo"
- "$mainMod, R, exec, stt-ptt start"
- "$mainMod, S, exec, stt-ptt start"
- "$mainMod, J, togglesplit"
- "$mainMod, h, movefocus, l"
- "$mainMod, l, movefocus, r"
- "$mainMod, k, movefocus, u"
- "$mainMod, j, movefocus, d"
- "$mainMod, 1, workspace, 1"
- "$mainMod, 2, workspace, 2"
- "$mainMod, 3, workspace, 3"
- "$mainMod, 4, workspace, 4"
- "$mainMod, 5, workspace, 5"
- "$mainMod, 6, workspace, 6"
- "$mainMod, 7, workspace, 7"
- "$mainMod, 8, workspace, 8"
- "$mainMod, 9, workspace, 9"
- "$mainMod, 0, workspace, 10"
- "$mainMod SHIFT, 1, movetoworkspace, 1"
- "$mainMod SHIFT, 2, movetoworkspace, 2"
- "$mainMod SHIFT, 3, movetoworkspace, 3"
- "$mainMod SHIFT, 4, movetoworkspace, 4"
- "$mainMod SHIFT, 5, movetoworkspace, 5"
- "$mainMod SHIFT, 6, movetoworkspace, 6"
- "$mainMod SHIFT, 7, movetoworkspace, 7"
- "$mainMod SHIFT, 8, movetoworkspace, 8"
- "$mainMod SHIFT, 9, movetoworkspace, 9"
- "$mainMod SHIFT, 0, movetoworkspace, 10"
- "$mainMod, mouse_down, workspace, e+1"
- "$mainMod, mouse_up, workspace, e-1"
- ];
-
- bindr = [
- "$mainMod, R, exec, stt-ptt stop"
- "$mainMod, S, exec, stt-ptt format-stop"
- ];
-
- bindm = [
- "$mainMod, mouse:272, movewindow"
- "$mainMod, mouse:273, resizewindow"
+ {name = "epic-mouse-v1"; sensitivity = -0.5;}
+ {name = "zsa-technology-labs-moonlander-mark-i"; kb_layout = "us";}
+ {name = "keychron-keychron-k7"; kb_layout = "us";}
];
};
+
+ # ── Complex Lua: curves, animations, binds, window rules, startup ──
+ extraConfig = ''
+ -- Bezier curves
+ hl.curve("myBezier", { type = "bezier", points = { {0.05, 0.9}, {0.1, 1.05} } })
+
+ -- Animations
+ hl.animation({ leaf = "windows", enabled = true, speed = 7, bezier = "myBezier" })
+ hl.animation({ leaf = "windowsOut", enabled = true, speed = 7, bezier = "default", style = "popin 80%" })
+ hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "default" })
+ hl.animation({ leaf = "borderangle", enabled = true, speed = 8, bezier = "default" })
+ hl.animation({ leaf = "fade", enabled = true, speed = 7, bezier = "default" })
+ hl.animation({ leaf = "workspaces", enabled = true, speed = 6, bezier = "default" })
+
+ -- Keybinds: terminals & editors
+ hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd(terminal .. " nu -c zellij-ps"))
+ hl.bind(mainMod .. " + T", hl.dsp.exec_cmd(terminal .. " -e nu -c 'nitch; exec nu'"))
+ hl.bind(mainMod .. " + Shift + T", hl.dsp.exec_cmd("launch-timer"))
+ hl.bind(mainMod .. " + N", hl.dsp.exec_cmd(terminal .. " -e nvim"))
+ hl.bind(mainMod .. " + Z", hl.dsp.exec_cmd("uwsm app -- zeditor"))
+
+ -- Keybinds: misc
+ hl.bind(mainMod .. " + O", hl.dsp.exec_cmd("hyprctl dispatch setprop activewindow opaque toggle"))
+ hl.bind(mainMod .. " + B", hl.dsp.exec_cmd("uwsm app -- thunar"))
+ hl.bind(mainMod .. " + Shift + B", hl.dsp.exec_cmd("uwsm app -- vivaldi"))
+ hl.bind(mainMod .. " + Escape", hl.dsp.exec_cmd("uwsm app -- wlogout -p layer-shell"))
+
+ -- Keybinds: window management
+ hl.bind(mainMod .. " + Space", hl.dsp.window.float({ action = "toggle" }))
+ hl.bind(mainMod .. " + Q", hl.dsp.window.close())
+ hl.bind(mainMod .. " + M", hl.dsp.exit())
+ hl.bind(mainMod .. " + F", hl.dsp.window.fullscreen())
+ hl.bind(mainMod .. " + Shift + V", hl.dsp.window.float({ action = "toggle" }))
+
+ -- Keybinds: launchers
+ hl.bind(mainMod .. " + D", hl.dsp.exec_cmd('uwsm app -- rofi -show drun -run-command "uwsm app -- {cmd}"'))
+ hl.bind(mainMod .. " + V", hl.dsp.exec_cmd("uwsm app -- cliphist list | rofi -dmenu | cliphist decode | wl-copy"))
+ hl.bind(mainMod .. " + C", hl.dsp.exec_cmd("bash -c 'FILE=/tmp/screenshot_$(date +%s).png; grim -g \"$(slurp)\" \"$FILE\" && ksnip \"$FILE\"'"))
+ hl.bind(mainMod .. " + Shift + S", hl.dsp.exec_cmd("uwsm app -- rofi -show emoji"))
+ hl.bind(mainMod .. " + P", hl.dsp.exec_cmd("uwsm app -- rofi-pass"))
+ hl.bind(mainMod .. " + Shift + P", hl.dsp.window.pseudo())
+
+ -- Keybinds: STT push-to-talk
+ hl.bind(mainMod .. " + R", hl.dsp.exec_cmd("stt-ptt start"))
+ hl.bind(mainMod .. " + R", hl.dsp.exec_cmd("stt-ptt stop"), { release = true })
+ hl.bind(mainMod .. " + S", hl.dsp.exec_cmd("stt-ptt start"))
+ hl.bind(mainMod .. " + S", hl.dsp.exec_cmd("stt-ptt format-stop"), { release = true })
+
+ -- Keybinds: focus
+ 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" }))
+
+ -- Keybinds: workspaces (1-10)
+ for i = 1, 10 do
+ local key = i % 10
+ hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i }))
+ hl.bind(mainMod .. " + Shift + " .. key, hl.dsp.window.move({ workspace = i }))
+ end
+ hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
+ hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
+
+ -- Keybinds: mouse (drag & resize)
+ hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
+ hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
+
+ -- Window rules: floating dialogs
+ for _, cls in ipairs({
+ "file_progress", "confirm", "dialog", "download",
+ "notification", "error", "splash", "confirmreset",
+ }) do
+ hl.window_rule({ match = { class = cls }, float = true })
+ end
+
+ -- Window rules: title-based floating
+ hl.window_rule({ match = { title = "Open File" }, float = true })
+ hl.window_rule({ match = { title = "branchdialog" }, float = true })
+
+ -- Window rules: audio/video
+ hl.window_rule({ match = { class = "pavucontrol-qt" }, float = true })
+ hl.window_rule({ match = { class = "pavucontrol" }, float = true })
+ hl.window_rule({ match = { class = "^espanso$" }, float = true })
+
+ -- Window rules: wlogout
+ hl.window_rule({ match = { class = "wlogout" }, fullscreen = true })
+ hl.window_rule({ match = { title = "wlogout" }, float = true, fullscreen = true })
+
+ -- Window rules: mpv
+ hl.window_rule({ match = { class = "mpv" }, float = true, idle_inhibit = "focus", opacity = 1.0 })
+
+ -- Window rules: misc floating
+ hl.window_rule({ match = { title = "^Media viewer$" }, float = true })
+ hl.window_rule({ match = { title = "^Volume Control$" }, float = true })
+ hl.window_rule({ match = { title = "^Picture-in-Picture$" }, float = true })
+
+ -- Window rules: pomodoro (consolidated)
+ hl.window_rule({
+ match = { title = "^floating-pomodoro$" },
+ float = true, size = { 250, 50 }, move = { 12, "monitor_h-150" }, pin = true,
+ })
+
+ -- Window rules: streamlabs (consolidated)
+ hl.window_rule({
+ match = { initial_title = ".*streamlabs.com.*" },
+ float = true, pin = true, size = { 800, 400 }, opacity = 0.5,
+ idle_inhibit = "focus", no_anim = true, decorate = false, no_shadow = true, no_blur = true,
+ })
+ hl.window_rule({ match = { initial_title = ".*alert-box.*" }, move = { "100%-820", 102 } })
+ hl.window_rule({ match = { initial_title = ".*chat-box.*" }, move = { "100%-820", 512 } })
+
+ -- Window rules: vibe-typer
+ hl.window_rule({ match = { class = "^vibe-typer$", title = "^Recording Indicator$" }, no_blur = true })
+
+ -- Window rules: xwayland border
+ hl.window_rule({ match = { xwayland = true }, border_color = "rgb(ffffff)" })
+
+ -- Startup commands
+ hl.on("hyprland.start", function()
+ hl.exec_cmd("hyprpanel")
+ hl.exec_cmd("while ! hyprpaper-random; do sleep 0.5; done")
+ hl.exec_cmd("wl-paste --type text --watch cliphist store")
+ hl.exec_cmd("wl-paste --type image --watch cliphist store")
+ hl.exec_cmd("vibetyper")
+ end)
+ '';
};
services.hypridle = {
@@ -248,12 +249,12 @@ in {
enable = true;
settings = {
"$font" = "JetBrainsMono Nerd Font";
- "$base" = "rgb(${config.colorScheme.palette.base00})";
- "$text" = "rgb(${config.colorScheme.palette.base05})";
- "$textAlpha" = "${config.colorScheme.palette.base05}";
- "$accentAlpha" = "${config.colorScheme.palette.base0D}";
- "$red" = "rgb(${config.colorScheme.palette.base08})";
- "$yellow" = "rgb(${config.colorScheme.palette.base0A})";
+ "$base" = "rgb(${p.base00})";
+ "$text" = "rgb(${p.base05})";
+ "$textAlpha" = "${p.base05}";
+ "$accentAlpha" = "${p.base0D}";
+ "$red" = "rgb(${p.base08})";
+ "$yellow" = "rgb(${p.base0A})";
general = {
hide_cursor = true;
@@ -263,7 +264,7 @@ in {
monitor = "";
path = "${config.home.homeDirectory}/.config/hypr/wallpapers/wallhaven-lmmo8r.jpg";
blur_passes = 0;
- color = "rgb(${config.colorScheme.palette.base00})";
+ color = "rgb(${p.base00})";
};
label = [
@@ -297,16 +298,16 @@ in {
dots_size = 0.2;
dots_spacing = 0.2;
dots_center = true;
- outer_color = "rgb(${config.colorScheme.palette.base0D})";
- inner_color = "rgb(${config.colorScheme.palette.base00})";
- font_color = "rgb(${config.colorScheme.palette.base05})";
+ outer_color = "rgb(${p.base0D})";
+ inner_color = "rgb(${p.base00})";
+ font_color = "rgb(${p.base05})";
fade_on_empty = false;
- placeholder_text = '' Logged in as $USER'';
+ placeholder_text = '' Logged in as $USER'';
hide_input = false;
- check_color = "rgb(${config.colorScheme.palette.base0D})";
- fail_color = "rgb(${config.colorScheme.palette.base08})";
+ check_color = "rgb(${p.base0D})";
+ fail_color = "rgb(${p.base08})";
fail_text = ''$FAIL ($ATTEMPTS)'';
- capslock_color = "rgb(${config.colorScheme.palette.base0A})";
+ capslock_color = "rgb(${p.base0A})";
position = "0, -35";
halign = "center";
valign = "center";