hyprland: migrate to Lua config (Hyprland 0.55+)

Switch Gaming and Macbook to home-manager unstable which has
configType = "lua" support. Converts the full Hyprland config from
deprecated hyprlang to native Lua:

- flake: add home-manager unstable input for Gaming/Macbook
- hyprland.nix: configType = "lua", settings.config for static sections,
  extraConfig for env/startup/animations/binds/window-rules
- hosts: monitor config updated to hl.monitor({}) table format

Eliminates the windowrulev2 deprecation banner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
rope 2026-05-17 20:38:30 +01:00
parent 07ee7b86df
commit 2156ce4138
5 changed files with 246 additions and 217 deletions

View file

@ -4,13 +4,7 @@ let
hyprland-pkgs = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system};
isMacbook = config.networking.hostName == "FredOS-Macbook";
# GPU pinning env vars — only needed on the dual-GPU gaming box (card1 = Navi 22).
gpuEnv = lib.optionals (config.networking.hostName == "FredOS-Gaming") [
"AQ_DRM_DEVICES,/dev/dri/card1"
"WLR_DRM_DEVICES,/dev/dri/card1"
"DRI_PRIME,pci-0000_03_00_0"
];
isGaming = !isMacbook;
# hyprutils-0.13.1 requires GLIBCXX_3.4.34 (GCC 15) but Hyprland's RPATH
# was patched to gcc-14.3.0-lib which only provides up to GLIBCXX_3.4.33.
@ -50,7 +44,7 @@ in
programs.hyprland = {
enable = true;
xwayland.enable = true;
package = hyprland-wrapped;
package = hyprland-wrapped;
portalPackage = hyprland-pkgs.xdg-desktop-portal-hyprland;
};
@ -103,157 +97,84 @@ in
home-manager.users.fred = { config, lib, pkgs, inputs, ... }: {
wayland.windowManager.hyprland = {
enable = true;
configType = "lua";
systemd.variables = [ "--all" ];
package = hyprland-wrapped;
settings = {
# monitor is set per-host in hosts/FredOS-{Gaming,Macbook}.nix
# hl.config({...}) — all static named-section configuration.
# monitor is set per-host in hosts/FredOS-{Gaming,Macbook}.nix.
config = {
general = {
gaps_in = 6;
gaps_out = 12;
border_size = 2;
layout = "dwindle";
resize_on_border = true;
};
# Battle.net tray icon leaks as a tiny floating XWayland window with
# class steam_app_0 and an empty title — send it to the void.
windowrulev2 = [
"workspace special silent, class:^(steam_app_0)$, title:^()$, floating:1"
];
decoration = {
rounding = 8;
blur = {
enabled = true;
};
};
env = [
# Clear the LD_PRELOAD set by the start-hyprland wrapper so it
# doesn't leak into child processes (Steam, games, etc.).
"LD_PRELOAD,"
render = {
direct_scanout = false;
};
# Keep these in sync with stylix.cursor in settings/stylix.nix.
"XCURSOR_THEME,Bibata-Modern-Ice"
"XCURSOR_SIZE,24"
"HYPRCURSOR_THEME,Bibata-Modern-Ice"
"HYPRCURSOR_SIZE,24"
# Prefer native Wayland backends where the app supports it.
"ELECTRON_OZONE_PLATFORM_HINT,wayland" # Vesktop, VSCodium
"MOZ_ENABLE_WAYLAND,1" # Zen / Firefox family
"QT_QPA_PLATFORM,wayland;xcb" # Qt apps, XWayland fallback
"SDL_VIDEODRIVER,wayland" # SDL apps
"_JAVA_AWT_WM_NONREPARENTING,1" # Java tiling fix
] ++ gpuEnv;
"$mod" = "SUPER";
"$term" = "ghostty";
"$menu" = "killall anyrun || anyrun";
exec-once = [
"mako"
"nm-applet --indicator"
"wl-paste --type text --watch cliphist store"
"wl-paste --type image --watch cliphist store"
"hyprctl setcursor Bibata-Modern-Ice 24"
"swayosd-server"
] ++ lib.optionals isMacbook [ "hypridle" ];
general = {
gaps_in = 6;
gaps_out = 12;
border_size = 2;
layout = "dwindle";
resize_on_border = true;
};
decoration = {
rounding = 8;
blur = {
animations = {
enabled = true;
};
};
render = {
direct_scanout = false;
};
input = {
kb_layout = "gb,no";
kb_options = "grp:alt_shift_toggle";
follow_mouse = 1;
accel_profile = "flat";
sensitivity = 0;
} // lib.optionalAttrs isMacbook {
touchpad = {
"tap-to-click" = true;
tap_button_map = "lrm";
natural_scroll = true;
};
};
animations = {
enabled = true;
# speed = tenths of a second; 1 = 0.1s ≈ instant.
bezier = [
"snap, 0.05, 0.9, 0.1, 1.0"
];
animation = [
"windows, 1, 1, snap"
"windowsOut, 1, 1, snap, popin 80%"
"layers, 1, 1, snap"
"border, 1, 2, default"
"fade, 1, 1, default"
"workspaces, 1, 1, snap"
];
};
cursor = {
no_warps = true;
};
input = {
kb_layout = "gb,no";
kb_options = "grp:alt_shift_toggle";
follow_mouse = 1;
accel_profile = "flat";
sensitivity = 0;
} // lib.optionalAttrs isMacbook {
touchpad = {
tap-to-click = true;
tap_button_map = "lrm"; # 1-finger=left, 2-finger=right, 3-finger=middle
natural_scroll = true;
dwindle = {
preserve_split = true;
};
misc = {
disable_hyprland_logo = true;
disable_splash_rendering = true;
# Apps demanding attention don't get to yank focus — they'll
# show as urgent in the bar instead.
focus_on_activate = false;
vrr = 2;
};
# vfr moved from misc: to debug: in 0.55.0
debug = {
vfr = false; # keep compositor ticking, don't idle between frames
disable_logs = false;
};
};
};
cursor = {
no_warps = true; # don't teleport the cursor on focus changes
};
dwindle = {
preserve_split = true;
# New windows split the focused container 50/50 — your usual
# 2-way layout falls out of dwindle's defaults.
};
misc = {
disable_hyprland_logo = true;
disable_splash_rendering = true;
# Apps demanding attention don't get to yank focus — they'll
# show as urgent in the bar instead. Pairs with cursor.no_warps
# below; without that, focus jumps drag the cursor with them.
focus_on_activate = false;
vrr = 2; # always on — should engage at the 180 Hz EDID mode
};
# vfr moved from misc: to debug: in 0.55.0
debug = {
vfr = false; # keep compositor ticking, don't idle between frames
disable_logs = false;
};
# Mirror the GNOME bindings so muscle memory carries over
bind = [
"$mod, T, exec, $term"
"$mod, E, exec, nemo"
"$mod, R, exec, $menu"
"$mod, Q, killactive"
"$mod SHIFT, E, exit"
# Floating / layout
"$mod, V, togglefloating"
"$mod, F, fullscreen, 0"
"$mod, P, pseudo"
"$mod, S, layoutmsg, togglesplit"
# Focus
"$mod, left, movefocus, l"
"$mod, right, movefocus, r"
"$mod, up, movefocus, u"
"$mod, down, movefocus, d"
"$mod, H, movefocus, l"
"$mod, K, movefocus, u"
"$mod, J, movefocus, d"
# L freed for hyprlock — arrow keys still handle right-focus.
# Power menu — toggle: kills anyrun if already open
"$mod, L, exec, killall anyrun || ${pkgs.writeShellScript "power-menu" ''
extraConfig =
let
powerMenu = pkgs.writeShellScript "power-menu" ''
choice=$(printf '%s\n' \
$' Lock' \
$' Logout' \
$' Reboot' \
$' Shutdown' \
$' Lock' \
$' Logout' \
$' Reboot' \
$' Shutdown' \
| ${pkgs.anyrun}/bin/anyrun \
--plugins "${pkgs.anyrun}/lib/libstdin.so" \
--show-results-immediately true \
@ -265,82 +186,153 @@ in
*Reboot) systemctl reboot ;;
*Shutdown) systemctl poweroff ;;
esac
''}"
# Move windows
"$mod SHIFT, left, movewindow, l"
"$mod SHIFT, right, movewindow, r"
"$mod SHIFT, up, movewindow, u"
"$mod SHIFT, down, movewindow, d"
# Workspaces
"$mod, 1, workspace, 1"
"$mod, 2, workspace, 2"
"$mod, 3, workspace, 3"
"$mod, 4, workspace, 4"
"$mod, 5, workspace, 5"
"$mod, 6, workspace, 6"
"$mod, 7, workspace, 7"
"$mod, 8, workspace, 8"
"$mod, 9, workspace, 9"
"$mod, 0, workspace, 10"
"$mod SHIFT, 1, movetoworkspacesilent, 1"
"$mod SHIFT, 2, movetoworkspacesilent, 2"
"$mod SHIFT, 3, movetoworkspacesilent, 3"
"$mod SHIFT, 4, movetoworkspacesilent, 4"
"$mod SHIFT, 5, movetoworkspacesilent, 5"
"$mod SHIFT, 6, movetoworkspacesilent, 6"
"$mod SHIFT, 7, movetoworkspacesilent, 7"
"$mod SHIFT, 8, movetoworkspacesilent, 8"
"$mod SHIFT, 9, movetoworkspacesilent, 9"
"$mod SHIFT, 0, movetoworkspacesilent, 10"
# Screenshots — Shift+Super+S matches your GNOME binding
"$mod SHIFT, S, exec, hyprshot -m region --clipboard-only"
", Print, exec, hyprshot -m output --clipboard-only"
# Settings shortcut — Super+I matches your GNOME binding
"$mod, I, exec, pavucontrol"
# Custom - shortcuts:
"$mod, z, exec, zen-beta"
];
bindm = [
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
];
bindel = [
", XF86AudioRaiseVolume, exec, swayosd-client --output-volume raise"
", XF86AudioLowerVolume, exec, swayosd-client --output-volume lower"
", XF86AudioMute, exec, swayosd-client --output-volume mute-toggle"
", XF86MonBrightnessUp, exec, swayosd-client --brightness raise"
", XF86MonBrightnessDown, exec, swayosd-client --brightness lower"
] ++ lib.optionals isMacbook [
", XF86KbdBrightnessUp, exec, ${pkgs.writeShellScript "kbd-bright-up" ''
'';
kbdBrightUp = pkgs.writeShellScript "kbd-bright-up" ''
${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight set +10%
brightness=$(${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight get)
max=$(${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight max)
echo $(( brightness * 100 / max )) > "$XDG_RUNTIME_DIR/wob.fifo"
''}"
", XF86KbdBrightnessDown, exec, ${pkgs.writeShellScript "kbd-bright-down" ''
'';
kbdBrightDown = pkgs.writeShellScript "kbd-bright-down" ''
${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight set 10%-
brightness=$(${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight get)
max=$(${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight max)
echo $(( brightness * 100 / max )) > "$XDG_RUNTIME_DIR/wob.fifo"
''}"
];
'';
in
''
-- Environment
hl.env("LD_PRELOAD", "")
hl.env("XCURSOR_THEME", "Bibata-Modern-Ice")
hl.env("XCURSOR_SIZE", "24")
hl.env("HYPRCURSOR_THEME", "Bibata-Modern-Ice")
hl.env("HYPRCURSOR_SIZE", "24")
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "wayland")
hl.env("MOZ_ENABLE_WAYLAND", "1")
hl.env("QT_QPA_PLATFORM", "wayland;xcb")
hl.env("SDL_VIDEODRIVER", "wayland")
hl.env("_JAVA_AWT_WM_NONREPARENTING", "1")
${lib.optionalString isGaming ''
-- GPU pinning Navi 22 is card1 on the dual-GPU gaming box.
hl.env("AQ_DRM_DEVICES", "/dev/dri/card1")
hl.env("WLR_DRM_DEVICES", "/dev/dri/card1")
hl.env("DRI_PRIME", "pci-0000_03_00_0")
''}
bindl = [
", XF86AudioPlay, exec, playerctl play-pause"
", XF86AudioNext, exec, playerctl next"
", XF86AudioPrev, exec, playerctl previous"
];
};
-- Startup
hl.on("hyprland.start", function()
hl.exec_cmd("mako")
hl.exec_cmd("nm-applet --indicator")
hl.exec_cmd("wl-paste --type text --watch cliphist store")
hl.exec_cmd("wl-paste --type image --watch cliphist store")
hl.exec_cmd("hyprctl setcursor Bibata-Modern-Ice 24")
hl.exec_cmd("swayosd-server")
${lib.optionalString isMacbook ''hl.exec_cmd("hypridle")''}
end)
-- Animation curve and definitions
hl.curve("snap", { type = "bezier", points = { {0.05, 0.9}, {0.1, 1.0} } })
hl.animation({ leaf = "windows", enabled = true, speed = 1, bezier = "snap" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1, bezier = "snap", style = "popin 80%" })
hl.animation({ leaf = "layers", enabled = true, speed = 1, bezier = "snap" })
hl.animation({ leaf = "border", enabled = true, speed = 2, bezier = "default" })
hl.animation({ leaf = "fade", enabled = true, speed = 1, bezier = "default" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 1, bezier = "snap" })
-- Window rules
-- Battle.net tray icon leaks as a tiny floating XWayland window.
hl.window_rule({
match = { class = "steam_app_0", title = "^$", float = true },
workspace = "special silent",
})
-- Binds
local mod = "SUPER"
-- Apps
hl.bind(mod .. " + T", hl.dsp.exec_cmd("ghostty"))
hl.bind(mod .. " + E", hl.dsp.exec_cmd("nemo"))
hl.bind(mod .. " + R", hl.dsp.exec_cmd("killall anyrun || anyrun"))
hl.bind(mod .. " + Q", hl.dsp.window.close())
hl.bind(mod .. " + SHIFT + E", hl.dsp.exit())
-- Floating / layout
hl.bind(mod .. " + V", hl.dsp.window.float({ action = "toggle" }))
hl.bind(mod .. " + F", hl.dsp.window.fullscreen({ state = 0 }))
hl.bind(mod .. " + P", hl.dsp.window.pseudo())
hl.bind(mod .. " + S", hl.dsp.layout("togglesplit"))
-- Focus
hl.bind(mod .. " + left", hl.dsp.focus({ direction = "left" }))
hl.bind(mod .. " + right", hl.dsp.focus({ direction = "right" }))
hl.bind(mod .. " + up", hl.dsp.focus({ direction = "up" }))
hl.bind(mod .. " + down", hl.dsp.focus({ direction = "down" }))
hl.bind(mod .. " + H", hl.dsp.focus({ direction = "left" }))
hl.bind(mod .. " + K", hl.dsp.focus({ direction = "up" }))
hl.bind(mod .. " + J", hl.dsp.focus({ direction = "down" }))
-- Power menu toggle: kills anyrun if already open
hl.bind(mod .. " + L", hl.dsp.exec_cmd("killall anyrun || ${powerMenu}"))
-- Move windows
hl.bind(mod .. " + SHIFT + left", hl.dsp.window.move({ direction = "left" }))
hl.bind(mod .. " + SHIFT + right", hl.dsp.window.move({ direction = "right" }))
hl.bind(mod .. " + SHIFT + up", hl.dsp.window.move({ direction = "up" }))
hl.bind(mod .. " + SHIFT + down", hl.dsp.window.move({ direction = "down" }))
-- Workspaces
hl.bind(mod .. " + 1", hl.dsp.workspace.change(1))
hl.bind(mod .. " + 2", hl.dsp.workspace.change(2))
hl.bind(mod .. " + 3", hl.dsp.workspace.change(3))
hl.bind(mod .. " + 4", hl.dsp.workspace.change(4))
hl.bind(mod .. " + 5", hl.dsp.workspace.change(5))
hl.bind(mod .. " + 6", hl.dsp.workspace.change(6))
hl.bind(mod .. " + 7", hl.dsp.workspace.change(7))
hl.bind(mod .. " + 8", hl.dsp.workspace.change(8))
hl.bind(mod .. " + 9", hl.dsp.workspace.change(9))
hl.bind(mod .. " + 0", hl.dsp.workspace.change(10))
hl.bind(mod .. " + SHIFT + 1", hl.dsp.window.move_to_workspace({ id = 1, silent = true }))
hl.bind(mod .. " + SHIFT + 2", hl.dsp.window.move_to_workspace({ id = 2, silent = true }))
hl.bind(mod .. " + SHIFT + 3", hl.dsp.window.move_to_workspace({ id = 3, silent = true }))
hl.bind(mod .. " + SHIFT + 4", hl.dsp.window.move_to_workspace({ id = 4, silent = true }))
hl.bind(mod .. " + SHIFT + 5", hl.dsp.window.move_to_workspace({ id = 5, silent = true }))
hl.bind(mod .. " + SHIFT + 6", hl.dsp.window.move_to_workspace({ id = 6, silent = true }))
hl.bind(mod .. " + SHIFT + 7", hl.dsp.window.move_to_workspace({ id = 7, silent = true }))
hl.bind(mod .. " + SHIFT + 8", hl.dsp.window.move_to_workspace({ id = 8, silent = true }))
hl.bind(mod .. " + SHIFT + 9", hl.dsp.window.move_to_workspace({ id = 9, silent = true }))
hl.bind(mod .. " + SHIFT + 0", hl.dsp.window.move_to_workspace({ id = 10, silent = true }))
-- Screenshots Shift+Super+S matches GNOME binding
hl.bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd("hyprshot -m region --clipboard-only"))
hl.bind("Print", hl.dsp.exec_cmd("hyprshot -m output --clipboard-only"))
-- Settings shortcut Super+I matches GNOME binding
hl.bind(mod .. " + I", hl.dsp.exec_cmd("pavucontrol"))
-- Custom shortcuts
hl.bind(mod .. " + Z", hl.dsp.exec_cmd("zen-beta"))
-- Mouse window manipulation
hl.bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
-- Volume / brightness (repeating)
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("swayosd-client --output-volume raise"), { repeating = true })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("swayosd-client --output-volume lower"), { repeating = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("swayosd-client --output-volume mute-toggle"), { repeating = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("swayosd-client --brightness raise"), { repeating = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("swayosd-client --brightness lower"), { repeating = true })
${lib.optionalString isMacbook ''
hl.bind("XF86KbdBrightnessUp", hl.dsp.exec_cmd("${kbdBrightUp}"), { repeating = true })
hl.bind("XF86KbdBrightnessDown", hl.dsp.exec_cmd("${kbdBrightDown}"), { repeating = true })
''}
-- Media keys (locked work through lockscreen)
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
'';
};
programs.anyrun = {
@ -408,7 +400,7 @@ in
enable = true;
settings = {
general = {
lock_cmd = "pidof hyprlock || hyprlock";
lock_cmd = "pidof hyprlock || hyprlock";
before_sleep_cmd = "loginctl lock-session";
after_sleep_cmd = "hyprctl dispatch dpms on";
};
@ -469,7 +461,7 @@ in
};
"custom/tray-handle" = {
format = ""; # Nerd Font arrow
format = ""; # Nerd Font arrow
tooltip = false;
};