- Add hyprland.lua.j2 Ansible template with full Lua DSL config - Update tasks to deploy hyprland.lua instead of hyprland.conf - Add docs/tech/hyprland-lua-migration.md with API reference, dispatcher mapping, pitfalls, and validation commands - Update keybindings doc to reflect .lua config format Co-authored-by: Junie <junie@jetbrains.com>
6.0 KiB
6.0 KiB
Hyprland Lua Config Migration (0.55+)
Summary
Hyprland 0.55 deprecates the legacy hyprlang (.conf) format in favor of a native Lua
DSL (hyprland.lua). Both formats coexist during transition, but .lua is the future.
Configuration File Priority
| File | Format | Priority |
|---|---|---|
~/.config/hypr/hyprland.lua |
Lua DSL (hl.* API) |
Preferred (0.55+) |
~/.config/hypr/hyprland.conf |
Legacy hyprlang | Deprecated |
When both exist, Hyprland loads .lua first. If .lua is present, .conf is ignored.
Lua API Quick Reference
-- Variables
local terminal = "kitty"
local mainMod = "SUPER"
-- Config blocks
hl.config({ general = { gaps_in = 5 } })
hl.env("XCURSOR_SIZE", "24")
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "auto" })
-- Keybindings (single combined key string)
hl.bind(mainMod .. " + Q", hl.dsp.window.close())
hl.bind(mainMod .. " + T", hl.dsp.exec_cmd(terminal))
-- Window/workspace rules
hl.window_rule({ name = "my-rule", match = { class = ".*" }, suppress_event = "maximize" })
hl.workspace_rule({ workspace = 1, monitor = "DP-1", default = true })
-- Autostart
hl.on("hyprland.start", function() hl.exec_cmd("waybar &") end)
-- Gestures, animations
hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })
hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } })
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, spring = "easy" })
Dispatcher Functions (hl.dsp.*)
| Lua API | Legacy .conf equivalent |
|---|---|
hl.dsp.exec_cmd("cmd") |
exec, cmd |
hl.dsp.window.close() |
killactive |
hl.dsp.window.float({ action = "toggle" }) |
togglefloating |
hl.dsp.window.pseudo() |
pseudo |
hl.dsp.window.fullscreen({ mode = 0 }) |
fullscreen, 0 |
hl.dsp.window.move({ direction = "left" }) |
movewindow, l |
hl.dsp.window.move({ workspace = 2 }) |
movetoworkspace, 2 |
hl.dsp.window.drag() / .resize() |
mouse bind |
hl.dsp.focus({ direction = "left" }) |
movefocus, l |
hl.dsp.focus({ workspace = 2 }) |
workspace, 2 |
hl.dsp.focus({ workspace = "e+1" }) |
workspace, e+1 |
hl.dsp.workspace.toggle_special("magic") |
togglespecialworkspace |
hl.dsp.layout("togglesplit") |
layoutmsg, togglesplit |
Pitfalls Learned
| Issue | Fix |
|---|---|
hl.dsp.focus({ direction = "next" }) |
Invalid. Only left/right/up/down accepted. Use hl.dsp.exec_cmd("hyprctl dispatch cyclenext") instead. |
tap-to-click = true |
Invalid Lua. Hyphens not allowed in identifiers. Use tap_to_click (Hyprland accepts underscores). |
hl.bind("", "Print", ...) |
Invalid. Lua API uses single key string: hl.bind("Print", ...). |
hl.bind(mainMod, "Q", ...) |
Invalid. Combine into one string: hl.bind(mainMod .. " + Q", ...). |
resizeactive not in hl.dsp.* |
Use hl.dsp.exec_cmd("hyprctl dispatch resizeactive -40 0") as wrapper. |
luac -p false positives |
Standard luac can't validate Hyprland's custom hl.* API. Use hyprctl configerrors at runtime instead. |
Validation
# Runtime validation (requires running Hyprland session)
hyprctl configerrors
# Over SSH (find instance signature first)
HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/$(id -u)/hypr/) hyprctl configerrors
# Syntax-only check (limited - won't catch hl.* API issues)
luac -p ~/.config/hypr/hyprland.lua
conf-to-lua Migration Mapping
Legacy .conf |
Lua .lua |
|---|---|
$mainMod = SUPER |
local mainMod = "SUPER" |
monitor=DP-1,... |
hl.monitor({ output = "DP-1", ... }) |
env = KEY,VALUE |
hl.env("KEY", "VALUE") |
input { ... } |
hl.config({ input = { ... } }) |
general { ... } |
hl.config({ general = { ... } }) |
bind = MOD, KEY, exec, cmd |
hl.bind(mod .. " + KEY", hl.dsp.exec_cmd("cmd")) |
bind = MOD, KEY, killactive |
hl.bind(mod .. " + KEY", hl.dsp.window.close()) |
bind = MOD, KEY, togglefloating |
hl.bind(mod .. " + KEY", hl.dsp.window.float({ action = "toggle" })) |
bind = MOD, KEY, fullscreen, 0 |
hl.bind(mod .. " + KEY", hl.dsp.window.fullscreen({ mode = 0 })) |
bind = MOD, KEY, movefocus, l |
hl.bind(mod .. " + KEY", hl.dsp.focus({ direction = "left" })) |
bind = MOD, KEY, movewindow, l |
hl.bind(mod .. " + KEY", hl.dsp.window.move({ direction = "left" })) |
bind = MOD, KEY, workspace, N |
hl.bind(mod .. " + KEY", hl.dsp.focus({ workspace = N })) |
bind = MOD, KEY, cyclenext |
hl.bind(mod .. " + KEY", hl.dsp.exec_cmd("hyprctl dispatch cyclenext")) |
bind = MOD, KEY, pseudo |
hl.bind(mod .. " + KEY", hl.dsp.window.pseudo()) |
bind = MOD, KEY, layoutmsg, togglesplit |
hl.bind(mod .. " + KEY", hl.dsp.layout("togglesplit")) |
bind = , Print, exec, cmd |
hl.bind("Print", hl.dsp.exec_cmd("cmd")) |
bind = MOD, KEY, exit |
hl.bind(mod .. " + KEY", hl.dsp.exec_cmd("hyprctl dispatch exit")) |
device { name = "..."; ... } |
hl.device({ name = "...", ... }) |
workspace = 1, monitor:DP-1, default:true |
hl.workspace_rule({ workspace = 1, monitor = "DP-1", default = true }) |
exec-once = cmd |
hl.on("hyprland.start", function() hl.exec_cmd("cmd") end) |
col.active_border = rgba(...) 45deg |
col = { active_border = { colors = {"rgba(...)"}, angle = 45 } } |
Multi-Machine Setup
This project manages configs for two machines with identical keybindings but different hardware:
| Aspect | Workstation (pfeddersheim) | MacBook Pro 16,1 |
|---|---|---|
| GPU | NVIDIA (6 env vars) | Intel UHD 630 + AMD RX 5500M (no env vars) |
| Monitors | 3 fixed (DP-1, DVI-D-1, HDMI-A-1) | Built-in + HDMI dock (auto-detect) |
| Input | de layout, left-handed mouse | de + mac_nodeadkeys + caps:swapescape, touchpad |
| Touchpad | N/A | natural_scroll, gestures, disable_while_typing |
| Media keys | N/A | Volume, brightness, playerctl |
| Window rules | None | Suppress maximize, XWayland fix |
| Config format | .lua (Ansible-managed) |
.lua (manual) |