Files
jaandJunie fe7f133136 feat(hyprland): migrate config from .conf to .lua format (Hyprland 0.55+)
- 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>
2026-06-15 06:43:23 +02:00

126 lines
6.0 KiB
Markdown

# 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
```lua
-- 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
```bash
# 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) |