From 5bd7647f4866924a60574f9d6ef1d0beff1f0b7e Mon Sep 17 00:00:00 2001 From: m3ta-chiron Date: Mon, 18 May 2026 17:27:14 +0200 Subject: [PATCH] feat: migrate Hyprland config to Lua (Hyprland 0.55+) - Rewrite hyprland.nix to use configType = "lua" - Use Hyprland 0.55 Lua API: hl.config(), hl.bind(), hl.curve(), hl.animation(), hl.window_rule(), hl.workspace_rule(), hl.dsp.* - Consolidate window rules where possible - Update flake.lock: home-manager -> 74f170c6 (2026-05-18) - hypridle and hyprlock keep their own config format --- flake.lock | 6 +- profiles/contexts/desktop/wm/hyprland.nix | 427 +++++++++++----------- 2 files changed, 217 insertions(+), 216 deletions(-) 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";