hypr: migrate layout management to native Lua

Replace hypr-workspace-layout shell script with native Lua functions in
keybinds.lua. Add custom Lua layouts (master-scroll variants, swap
variants) to the SUPER+period cycle. Implement swap-on-focus via
_G._hl_ws_layouts shared global so window.active handler can check
per-workspace layout without IPC or hl.get_workspaces().
This commit is contained in:
2026-05-12 04:19:10 +02:00
parent 9c7bf54cf1
commit 5cf2ad9f4a
2 changed files with 61 additions and 59 deletions
@@ -86,7 +86,20 @@ hl.bind(mainMod .. " + SHIFT + O", hl.dsp.workspace.move({ monitor = "r" }))
-- ─── Workspace layout state ───────────────────────────────────────────────────
local ws_layouts = {}
local ws_cycle_order = { "dwindle", "master", "scrolling", "monocle" }
_G._hl_ws_layouts = ws_layouts
local ws_cycle_order = {
"master",
"lua:master-scroll",
"lua:slave-master-scroll",
"lua:center-master-scroll",
"lua:top-master-scroll",
"lua:center-master-scroll-v",
"lua:master-swap",
"lua:slave-master-swap",
"lua:top-master-swap",
"scrolling",
"monocle",
}
local function active_ws_id()
local w = hl.get_active_window()
@@ -107,7 +120,10 @@ local function set_ws_layout(layout)
end
local function ws_toggle_ms()
set_ws_layout(cur_ws_layout() == "master" and "scrolling" or "master")
local cur = cur_ws_layout()
-- Any master variant → scrolling; scrolling/monocle → master
local next = (cur == "scrolling" or cur == "monocle") and "master" or "scrolling"
set_ws_layout(next)
end
local function ws_cycle()
+43 -57
View File
@@ -401,41 +401,48 @@ hl.layout.register("center-master-scroll-v", {
})
-- ─── Swap-on-focus layouts ────────────────────────────────────────────────────
-- Focusing any window promotes it to master; old master drops to slave area.
-- One address slot per layout; all updated on every focus event (only the
-- active layout's recalculate runs, so cross-pollution is harmless).
-- Focusing any window promotes it to master. recalculate reads the active
-- window directly via hl.get_active_window() — no external address tracking.
local swap_master_addr = nil -- shared: only one swap layout active at a time
local function find_master_idx(targets, addr)
if addr then
local function swap_master_idx(targets)
local aw = hl.get_active_window()
if aw and aw.address then
for i = 1, #targets do
local w = targets[i].window
if w and w.address == addr then return i end
if w and w.address == aw.address then return i end
end
end
return 1
end
-- ─── master-swap: master left, slaves right ───────────────────────────────────
hl.layout.register("master-swap", {
recalculate = function(ctx)
local targets = ctx.targets
local n = #targets
if n == 0 then return end
if n == 1 then targets[1]:place(ctx.area); return end
local midx = find_master_idx(targets, swap_master_addr)
local slave_area = ctx:split(ctx.area, "right", 1.0 - mfact)
local master_area = { x=ctx.area.x, y=ctx.area.y, w=slave_area.x-ctx.area.x, h=ctx.area.h }
targets[midx]:place(master_area)
local sidx = {}
for i = 1, n do if i ~= midx then sidx[#sidx+1] = i end end
local function swap_place_slaves(targets, midx, slave_area, vertical)
local sidx = {}
for i = 1, #targets do if i ~= midx then sidx[#sidx+1] = i end end
if #sidx == 0 then return end
if vertical then
local h = slave_area.h / #sidx
for j, si in ipairs(sidx) do
targets[si]:place({ x=slave_area.x, y=slave_area.y+(j-1)*h, w=slave_area.w, h=h })
end
else
local w = slave_area.w / #sidx
for j, si in ipairs(sidx) do
targets[si]:place({ x=slave_area.x+(j-1)*w, y=slave_area.y, w=w, h=slave_area.h })
end
end
end
-- ─── master-swap: master left, slaves right ───────────────────────────────────
hl.layout.register("master-swap", {
recalculate = function(ctx)
local targets, n = ctx.targets, #ctx.targets
if n == 0 then return end
if n == 1 then targets[1]:place(ctx.area); return end
local midx = swap_master_idx(targets)
local slave_area = ctx:split(ctx.area, "right", 1.0 - mfact)
local master_area = { x=ctx.area.x, y=ctx.area.y, w=slave_area.x-ctx.area.x, h=ctx.area.h }
targets[midx]:place(master_area)
swap_place_slaves(targets, midx, slave_area, true)
end,
layout_msg = function(_, msg)
if msg == "recalc" then return true end
@@ -447,25 +454,14 @@ hl.layout.register("master-swap", {
-- ─── slave-master-swap: slaves left, master right ─────────────────────────────
hl.layout.register("slave-master-swap", {
recalculate = function(ctx)
local targets = ctx.targets
local n = #targets
local targets, n = ctx.targets, #ctx.targets
if n == 0 then return end
if n == 1 then targets[1]:place(ctx.area); return end
local midx = find_master_idx(targets, swap_master_addr)
local slave_area = ctx:split(ctx.area, "left", 1.0 - mfact)
local master_area = {
x = slave_area.x + slave_area.w, y = ctx.area.y,
w = ctx.area.w - slave_area.w, h = ctx.area.h,
}
local midx = swap_master_idx(targets)
local slave_area = ctx:split(ctx.area, "left", 1.0 - mfact)
local master_area = { x=slave_area.x+slave_area.w, y=ctx.area.y, w=ctx.area.w-slave_area.w, h=ctx.area.h }
targets[midx]:place(master_area)
local sidx = {}
for i = 1, n do if i ~= midx then sidx[#sidx+1] = i end end
local h = slave_area.h / #sidx
for j, si in ipairs(sidx) do
targets[si]:place({ x=slave_area.x, y=slave_area.y+(j-1)*h, w=slave_area.w, h=h })
end
swap_place_slaves(targets, midx, slave_area, true)
end,
layout_msg = function(_, msg)
if msg == "recalc" then return true end
@@ -477,22 +473,14 @@ hl.layout.register("slave-master-swap", {
-- ─── top-master-swap: master top, slaves bottom row ───────────────────────────
hl.layout.register("top-master-swap", {
recalculate = function(ctx)
local targets = ctx.targets
local n = #targets
local targets, n = ctx.targets, #ctx.targets
if n == 0 then return end
if n == 1 then targets[1]:place(ctx.area); return end
local midx = find_master_idx(targets, swap_master_addr)
local slave_area = ctx:split(ctx.area, "bottom", 1.0 - mfact_v)
local midx = swap_master_idx(targets)
local slave_area = ctx:split(ctx.area, "bottom", 1.0 - mfact_v)
local master_area = { x=ctx.area.x, y=ctx.area.y, w=ctx.area.w, h=slave_area.y-ctx.area.y }
targets[midx]:place(master_area)
local sidx = {}
for i = 1, n do if i ~= midx then sidx[#sidx+1] = i end end
local w = slave_area.w / #sidx
for j, si in ipairs(sidx) do
targets[si]:place({ x=slave_area.x+(j-1)*w, y=slave_area.y, w=w, h=slave_area.h })
end
swap_place_slaves(targets, midx, slave_area, false)
end,
layout_msg = function(_, msg)
if msg == "recalc" then return true end
@@ -507,9 +495,6 @@ local all_col_states = { ms, sm, cm_left, cm_right, tm, cm_vtop, cm_vbot }
hl.on("window.active", function(w)
if w == nil or w.address == nil then return end
-- Promote focused window to master in any active swap layout
swap_master_addr = w.address
-- Auto-scroll: if the focused window is a peek slot, slide it into view
for _, state in ipairs(all_col_states) do
if w.address == state.peek_bottom_addr then
@@ -519,9 +504,10 @@ hl.on("window.active", function(w)
end
end
-- For swap layouts (no scroll dispatched), trigger a recalculate.
-- Guard by name so built-in layouts never receive an unknown message.
local cur = hl.get_config("general.layout")
-- Trigger recalc for swap layouts so the focused window becomes master.
-- _G._hl_ws_layouts is set by keybinds.lua and tracks per-workspace layout.
local ws_id = w.workspace and w.workspace.id
local cur = ws_id and _G._hl_ws_layouts and _G._hl_ws_layouts[ws_id]
if cur == "lua:master-swap" or cur == "lua:slave-master-swap" or cur == "lua:top-master-swap" then
hl.dispatch(hl.dsp.layout("recalc"))
end