Dotfiles/desktopenvs/hyprlua/hypr/layouts/init.lua

110 lines
4.3 KiB
Lua

-- Layout registry for the hyprlua session.
--
-- Adding a layout is a one-file drop-in: create `layouts/<name>.lua` returning a
-- spec table (see scrolling.lua for the shape) and it is auto-discovered here — no
-- other wiring. Exposes the global `layouts` table used by hyprland.lua (to pick the
-- default) and by the astal-menu popup (over `hyprctl eval`) to switch a workspace's
-- layout live. A JSON manifest is written for the menu to enumerate the choices.
local M = { registry = {}, order = {}, current = {} } -- current: ws(str) -> name
local HOME = os.getenv("HOME")
local DIR = HOME .. "/.config/hypr/layouts"
local MANIFEST = HOME .. "/.cache/astal-menu/layouts.json"
local STATE = HOME .. "/.cache/astal-menu/layouts-state.json"
local function scan()
local p = io.popen("ls '" .. DIR .. "' 2>/dev/null")
if not p then return {} end
local names = {}
for line in p:lines() do
local n = line:match("^(.+)%.lua$")
if n and n ~= "init" then names[#names + 1] = n end
end
p:close()
table.sort(names)
return names
end
local function jstr(s) return '"' .. tostring(s):gsub('\\', '\\\\'):gsub('"', '\\"') .. '"' end
-- Write layouts.json so the menu can list {name,label,directional,dirs,default_dir}.
function M.write_manifest()
local items = {}
for _, name in ipairs(M.order) do
local s = M.registry[name]
local dirs = {}
for _, d in ipairs(s.dirs or {}) do dirs[#dirs + 1] = jstr(d) end
local dlabels = {}
for _, d in ipairs(s.dir_labels or {}) do dlabels[#dlabels + 1] = jstr(d) end
items[#items + 1] = table.concat({
"{", '"name":', jstr(s.name),
',"label":', jstr(s.label or s.name),
',"directional":', tostring(s.directional or false),
',"dirs":[', table.concat(dirs, ","), "]",
',"dir_labels":[', table.concat(dlabels, ","), "]",
',"default_dir":', jstr(s.default_dir or ""),
',"fit_method":', tostring(s.fit_method or false),
',"stepper":', tostring(s.stepper or false), "}",
})
end
os.execute("mkdir -p '" .. MANIFEST:match("(.+)/[^/]+$") .. "' 2>/dev/null")
local f = io.open(MANIFEST, "w")
if f then f:write("[" .. table.concat(items, ",") .. "]\n"); f:close() end
end
function M.load()
for _, name in ipairs(scan()) do
local ok, spec = pcall(require, "layouts." .. name)
if ok and type(spec) == "table" and spec.name then
if not M.registry[spec.name] then M.order[#M.order + 1] = spec.name end
M.registry[spec.name] = spec
end
end
M.write_manifest()
return M
end
-- Global default layout (called once from hyprland.lua).
function M.set_default(name)
local s = M.registry[name]; if not s then return end
hl.config({ general = { layout = s.backend } })
if s.config then hl.config(s.config) end
end
-- Switch one workspace's layout live (called by the menu via hyprctl eval).
-- `dir` is applied for directional layouts (scrolling.direction is a global option,
-- so this affects every scrolling workspace — hyprland has no per-ws direction).
local function write_state()
local parts = {}
for ws, name in pairs(M.current) do
parts[#parts + 1] = jstr(ws) .. ":" .. jstr(name)
end
local f = io.open(STATE, "w")
if f then f:write("{" .. table.concat(parts, ",") .. "}\n"); f:close() end
end
function M.set(ws, name, dir)
local s = M.registry[name]; if not s then return end
ws = tostring(ws)
hl.workspace_rule({ workspace = ws, layout = s.backend })
if s.lua then
-- custom Lua layout: orientation/options are per-layout state, set via a message
-- to the active workspace's layout (not the global scrolling:direction option).
if dir and dir ~= "" then hl.dispatch(hl.dsp.layout("orient " .. dir)) end
elseif s.directional and dir and dir ~= "" then
hl.config({ scrolling = { direction = dir } })
end
M.current[ws] = name -- remember so the menu can show the real per-ws layout
write_state()
end
-- Toggle the scrolling "centered follow" (focus_fit_method 0=fit / 1=centered).
-- column_width stays < 1.0 (set in scrolling.lua) so neighbours remain clickable.
function M.set_fit(method)
hl.config({ scrolling = { focus_fit_method = tonumber(method) or 0 } })
end
_G.layouts = M.load()
return M