110 lines
4.3 KiB
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
|