9c7bf54cf1
Port all layout management to Hyprland v0.55 Lua API: - Per-workspace layout toggle/cycle via hl.workspace_rule + hl.get_active_window() - Layout-aware move/nav/resize as pure Lua functions - Group smart-join via hl.get_active_window().grouped - mfact exact handlers in all custom scroll/swap layouts - No io.popen, no exec_cmd, no IPC deadlock risk
366 lines
15 KiB
Cheetah
366 lines
15 KiB
Cheetah
local mainMod = "SUPER"
|
|
|
|
-- Apps
|
|
local terminal = "uwsm app -- kitty"
|
|
local term_tmux = "uwsm app -- kitty -e tmux"
|
|
local term_tmux_append = "uwsm app -- kitty -e tmux a"
|
|
local editor = "uwsm app -- kitty -e nvim"
|
|
local alteditor = "uwsm app -- zeditor"
|
|
local filemanager = "uwsm app -- nautilus"
|
|
local launcher = "uwsm app -- owlry -p app,cmd,system,ssh"
|
|
local clipman = "uwsm app -- owlry -m clipboard"
|
|
local browser = "uwsm app -- firefox"
|
|
local browserprv = "uwsm app -- firefox --private-window"
|
|
local browsernewinst = "uwsm app -- firefox --new-instance"
|
|
local altbrowser = "uwsm app -- chromium"
|
|
local taskman = "uwsm app -- owlry -m uuctl"
|
|
local pwdmgr = "uwsm app -- bitwarden-desktop"
|
|
local soundctl = "uwsm app -- pwvucontrol"
|
|
|
|
local notcenter = "uwsm app -- swaync-client -t -sw"
|
|
local notdnd = "uwsm app -- swaync-client -d"
|
|
local nothide = "uwsm app -- swaync-client --hide-latest"
|
|
|
|
-- First-class launchers
|
|
hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd(terminal))
|
|
hl.bind(mainMod .. " + SHIFT + Return", hl.dsp.exec_cmd(term_tmux))
|
|
hl.bind(mainMod .. " + CTRL + Return", hl.dsp.exec_cmd(term_tmux_append))
|
|
hl.bind(mainMod .. " + F1", hl.dsp.exec_cmd("hypr-show-binds"))
|
|
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(filemanager))
|
|
hl.bind(mainMod .. " + W", hl.dsp.exec_cmd(browser))
|
|
hl.bind(mainMod .. " + SHIFT + W", hl.dsp.exec_cmd(browserprv))
|
|
hl.bind(mainMod .. " + CTRL + W", hl.dsp.exec_cmd(altbrowser))
|
|
hl.bind(mainMod .. " + ALT + W", hl.dsp.exec_cmd(browsernewinst))
|
|
hl.bind(mainMod .. " + Space", hl.dsp.exec_cmd(launcher))
|
|
|
|
-- Secondary launchers
|
|
hl.bind(mainMod .. " + SHIFT + E", hl.dsp.exec_cmd(editor))
|
|
hl.bind(mainMod .. " + CTRL + E", hl.dsp.exec_cmd(alteditor))
|
|
hl.bind(mainMod .. " + X", hl.dsp.exec_cmd(taskman))
|
|
hl.bind(mainMod .. " + C", hl.dsp.exec_cmd(clipman))
|
|
hl.bind(mainMod .. " + F4", hl.dsp.exec_cmd(soundctl))
|
|
|
|
-- Quick Workspaces submap
|
|
hl.bind(mainMod .. " + A", hl.dsp.submap("quickws"))
|
|
hl.define_submap("quickws", function()
|
|
hl.bind("z", function() hl.dispatch(hl.dsp.focus({ workspace = 1 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
|
hl.bind("d", function() hl.dispatch(hl.dsp.focus({ workspace = 2 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
|
hl.bind("a", function() hl.dispatch(hl.dsp.focus({ workspace = 3 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
|
hl.bind("x", function() hl.dispatch(hl.dsp.focus({ workspace = 4 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
|
hl.bind("s", function() hl.dispatch(hl.dsp.focus({ workspace = 5 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
|
hl.bind("c", function() hl.dispatch(hl.dsp.focus({ workspace = 6 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
|
hl.bind("Escape", hl.dsp.submap("reset"))
|
|
hl.bind("Return", hl.dsp.submap("reset"))
|
|
end)
|
|
|
|
-- Notifications
|
|
hl.bind(mainMod .. " + grave", hl.dsp.exec_cmd(notcenter))
|
|
hl.bind(mainMod .. " + SHIFT + grave", hl.dsp.exec_cmd(notdnd))
|
|
hl.bind(mainMod .. " + CTRL + grave", hl.dsp.exec_cmd(nothide))
|
|
|
|
-- Session
|
|
hl.bind(mainMod .. " + Pause", hl.dsp.exec_cmd("hyprlock"))
|
|
hl.bind(mainMod .. " + SHIFT + Pause", hl.dsp.exec_cmd("owlry-power-menu"))
|
|
hl.bind(mainMod .. " + End", hl.dsp.exec_cmd("hyprlock"))
|
|
hl.bind(mainMod .. " + SHIFT + End", hl.dsp.exec_cmd("owlry-power-menu"))
|
|
|
|
-- Window management
|
|
hl.bind(mainMod .. " + Q", hl.dsp.window.close())
|
|
hl.bind(mainMod .. " + SHIFT + Q", hl.dsp.window.kill())
|
|
hl.bind(mainMod .. " + F", hl.dsp.window.float())
|
|
hl.bind(mainMod .. " + SHIFT + F", hl.dsp.window.fullscreen({ action = "toggle" }))
|
|
hl.bind(mainMod .. " + P", hl.dsp.window.pin())
|
|
hl.bind(mainMod .. " + U", hl.dsp.focus({ urgent_or_last = true }))
|
|
hl.bind(mainMod .. " + V", hl.dsp.window.center())
|
|
|
|
-- Special workspaces
|
|
hl.bind(mainMod .. " + SHIFT + Space", hl.dsp.workspace.toggle_special())
|
|
hl.bind(mainMod .. " + CTRL + Space", hl.dsp.window.move({ workspace = "special" }))
|
|
hl.bind(mainMod .. " + N", hl.dsp.workspace.toggle_special("passwordmgr"))
|
|
|
|
-- Monitor focus
|
|
hl.bind(mainMod .. " + I", hl.dsp.focus({ monitor = "l" }))
|
|
hl.bind(mainMod .. " + O", hl.dsp.focus({ monitor = "r" }))
|
|
hl.bind(mainMod .. " + SHIFT + I", hl.dsp.workspace.move({ monitor = "l" }))
|
|
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" }
|
|
|
|
local function active_ws_id()
|
|
local w = hl.get_active_window()
|
|
return w and w.workspace and w.workspace.id
|
|
end
|
|
|
|
local function cur_ws_layout()
|
|
local id = active_ws_id()
|
|
return (id and ws_layouts[id]) or "master"
|
|
end
|
|
|
|
local function set_ws_layout(layout)
|
|
local id = active_ws_id()
|
|
hl.notification.create({ text = "Layout: " .. layout, timeout = 1800, icon = "info" })
|
|
if not id then return end
|
|
ws_layouts[id] = layout
|
|
hl.workspace_rule({ workspace = tostring(id), layout = layout })
|
|
end
|
|
|
|
local function ws_toggle_ms()
|
|
set_ws_layout(cur_ws_layout() == "master" and "scrolling" or "master")
|
|
end
|
|
|
|
local function ws_cycle()
|
|
local cur = cur_ws_layout()
|
|
local next_layout = ws_cycle_order[1]
|
|
for i, v in ipairs(ws_cycle_order) do
|
|
if v == cur then
|
|
next_layout = ws_cycle_order[(i % #ws_cycle_order) + 1]
|
|
break
|
|
end
|
|
end
|
|
set_ws_layout(next_layout)
|
|
end
|
|
|
|
local function move_window(dir)
|
|
return function()
|
|
local cur = cur_ws_layout()
|
|
if cur == "scrolling" and (dir == "l" or dir == "r") then
|
|
hl.dispatch(hl.dsp.layout("swapcol " .. dir))
|
|
elseif cur ~= "monocle" then
|
|
hl.dispatch(hl.dsp.window.move({ direction = dir }))
|
|
end
|
|
end
|
|
end
|
|
|
|
local function nav(dir)
|
|
return function()
|
|
local cur = cur_ws_layout()
|
|
if cur == "scrolling" then
|
|
hl.dispatch(hl.dsp.layout("focus " .. dir))
|
|
elseif cur == "master" and dir == "d" then
|
|
hl.dispatch(hl.dsp.layout("addmaster"))
|
|
elseif cur == "master" and dir == "u" then
|
|
hl.dispatch(hl.dsp.layout("removemaster"))
|
|
else
|
|
hl.dispatch(hl.dsp.focus({ direction = dir }))
|
|
end
|
|
end
|
|
end
|
|
|
|
local function resize_h(sign)
|
|
return function()
|
|
local cur = cur_ws_layout()
|
|
if cur == "scrolling" then
|
|
hl.dispatch(hl.dsp.layout("colresize " .. sign .. "0.1"))
|
|
elseif cur ~= "monocle" then
|
|
hl.dispatch(hl.dsp.window.resize({ x = sign == "+" and 25 or -25, y = 0, relative = true }))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Layout
|
|
hl.bind(mainMod .. " + comma", ws_toggle_ms)
|
|
hl.bind(mainMod .. " + period", ws_cycle)
|
|
|
|
-- Split ratio (ALT+1-9 = 10%-90%, ALT+0 = 95%)
|
|
-- mfact exact for master/custom layouts, colresize for built-in scrolling
|
|
local mfact_layouts = {
|
|
["master"] = true,
|
|
["lua:master-scroll"] = true,
|
|
["lua:slave-master-scroll"] = true,
|
|
["lua:center-master-scroll"] = true,
|
|
["lua:top-master-scroll"] = true,
|
|
["lua:center-master-scroll-v"] = true,
|
|
["lua:master-swap"] = true,
|
|
["lua:slave-master-swap"] = true,
|
|
["lua:top-master-swap"] = true,
|
|
}
|
|
local function layout_ratio(ratio)
|
|
return function()
|
|
local cur = hl.get_config("general.layout")
|
|
if mfact_layouts[cur] then
|
|
hl.dispatch(hl.dsp.layout("mfact exact " .. ratio))
|
|
elseif cur == "scrolling" then
|
|
hl.dispatch(hl.dsp.layout("colresize " .. ratio))
|
|
end
|
|
end
|
|
end
|
|
local function layout_ratio_delta(delta)
|
|
return function()
|
|
local cur = hl.get_config("general.layout")
|
|
local sign = delta > 0 and "+" or ""
|
|
if mfact_layouts[cur] then
|
|
hl.dispatch(hl.dsp.layout("mfact " .. sign .. delta))
|
|
elseif cur == "scrolling" then
|
|
hl.dispatch(hl.dsp.layout("colresize " .. sign .. delta))
|
|
end
|
|
end
|
|
end
|
|
for i = 1, 9 do
|
|
hl.bind(mainMod .. " + ALT + " .. i, layout_ratio(i / 10))
|
|
end
|
|
hl.bind(mainMod .. " + ALT + 0", layout_ratio(0.95))
|
|
hl.bind(mainMod .. " + ALT + comma", layout_ratio_delta(-0.05))
|
|
hl.bind(mainMod .. " + ALT + period", layout_ratio_delta(0.05))
|
|
|
|
-- Scrolling layout: resize ALL columns
|
|
for i = 1, 9 do
|
|
local ratio = i / 10
|
|
hl.bind(mainMod .. " + CTRL + ALT + " .. i, function()
|
|
if hl.get_config("general.layout") == "scrolling" then
|
|
hl.dispatch(hl.dsp.layout("colresize all " .. ratio))
|
|
end
|
|
end)
|
|
end
|
|
hl.bind(mainMod .. " + CTRL + ALT + 0", function()
|
|
if hl.get_config("general.layout") == "scrolling" then
|
|
hl.dispatch(hl.dsp.layout("colresize all 0.95"))
|
|
end
|
|
end)
|
|
|
|
-- Smart Gaps Toggle
|
|
hl.bind(mainMod .. " + SHIFT + G", function()
|
|
local gapsIn = hl.get_config("general.gaps_in")
|
|
local current = type(gapsIn) == "table" and (gapsIn.top or 0) or (gapsIn or 0)
|
|
if current ~= 0 then
|
|
hl.config({ general = { gaps_in = 0, gaps_out = 0 } })
|
|
hl.notification.create({ text = "Gaps: OFF", timeout = 2000, icon = "info" })
|
|
else
|
|
hl.config({ general = { gaps_in = 5, gaps_out = 5 } })
|
|
hl.notification.create({ text = "Gaps: ON", timeout = 2000, icon = "ok" })
|
|
end
|
|
end)
|
|
|
|
-- Global Focus Notification (Lua Event)
|
|
hl.on("window.active", function(w)
|
|
if w ~= nil and w.title ~= nil then
|
|
print("Focused: " .. w.title)
|
|
end
|
|
end)
|
|
|
|
-- Vim-like navigation
|
|
hl.bind(mainMod .. " + H", hl.dsp.focus({ direction = "l" }))
|
|
hl.bind(mainMod .. " + L", hl.dsp.focus({ direction = "r" }))
|
|
hl.bind(mainMod .. " + K", hl.dsp.focus({ direction = "u" }))
|
|
hl.bind(mainMod .. " + J", hl.dsp.focus({ direction = "d" }))
|
|
|
|
-- Move window (layout-aware)
|
|
hl.bind(mainMod .. " + SHIFT + H", move_window("l"))
|
|
hl.bind(mainMod .. " + SHIFT + L", move_window("r"))
|
|
hl.bind(mainMod .. " + SHIFT + K", move_window("u"))
|
|
hl.bind(mainMod .. " + SHIFT + J", move_window("d"))
|
|
|
|
-- Resize submap
|
|
hl.bind(mainMod .. " + R", hl.dsp.submap("resize"))
|
|
hl.define_submap("resize", function()
|
|
hl.bind("h", hl.dsp.window.resize({ x = -25, y = 0, relative = true }), { repeating = true })
|
|
hl.bind("l", hl.dsp.window.resize({ x = 25, y = 0, relative = true }), { repeating = true })
|
|
hl.bind("k", hl.dsp.window.resize({ x = 0, y = -25, relative = true }), { repeating = true })
|
|
hl.bind("j", hl.dsp.window.resize({ x = 0, y = 25, relative = true }), { repeating = true })
|
|
hl.bind("SHIFT + h", hl.dsp.window.resize({ x = -60, y = 0, relative = true }), { repeating = true })
|
|
hl.bind("SHIFT + l", hl.dsp.window.resize({ x = 60, y = 0, relative = true }), { repeating = true })
|
|
hl.bind("SHIFT + k", hl.dsp.window.resize({ x = 0, y = -60, relative = true }), { repeating = true })
|
|
hl.bind("SHIFT + j", hl.dsp.window.resize({ x = 0, y = 60, relative = true }), { repeating = true })
|
|
hl.bind("Escape", hl.dsp.submap("reset"))
|
|
hl.bind("Return", hl.dsp.submap("reset"))
|
|
end)
|
|
|
|
-- Zoom submap
|
|
hl.bind(mainMod .. " + M", hl.dsp.submap("zoom"))
|
|
hl.define_submap("zoom", function()
|
|
hl.bind("equal", hl.dsp.exec_cmd("hypr-zoom-step +0.2"))
|
|
hl.bind("minus", hl.dsp.exec_cmd("hypr-zoom-step -0.2"))
|
|
hl.bind("0", hl.dsp.exec_cmd("hypr-zoom-step reset"))
|
|
hl.bind("Escape", hl.dsp.submap("reset"))
|
|
hl.bind("Return", hl.dsp.submap("reset"))
|
|
end)
|
|
|
|
-- Workspace cycling
|
|
hl.bind(mainMod .. " + Tab", hl.dsp.focus({ workspace = "m+1" }))
|
|
hl.bind(mainMod .. " + SHIFT + Tab", hl.dsp.focus({ workspace = "m-1" }))
|
|
|
|
-- Switch Workspaces 1-10 (IDs 21-30 in setup)
|
|
for i = 1, 9 do
|
|
hl.bind(mainMod .. " + " .. i, hl.dsp.focus({ workspace = 20 + i }))
|
|
hl.bind(mainMod .. " + SHIFT + " .. i, hl.dsp.window.move({ workspace = 20 + i }))
|
|
end
|
|
hl.bind(mainMod .. " + 0", hl.dsp.focus({ workspace = 30 }))
|
|
hl.bind(mainMod .. " + SHIFT + 0", hl.dsp.window.move({ workspace = 30 }))
|
|
|
|
-- Groups
|
|
local function smart_group(dir)
|
|
local w = hl.get_active_window()
|
|
local grouped = w and type(w.grouped) == "table" and #w.grouped > 0
|
|
if grouped then
|
|
hl.dispatch(hl.dsp.window.move({ out_of_group = true }))
|
|
else
|
|
hl.dispatch(hl.dsp.window.move({ into_or_create_group = dir }))
|
|
end
|
|
end
|
|
|
|
hl.bind(mainMod .. " + G", hl.dsp.group.toggle())
|
|
hl.bind(mainMod .. " + CTRL + G", hl.dsp.window.move({ out_of_group = true }))
|
|
hl.bind(mainMod .. " + CTRL + H", hl.dsp.group.prev())
|
|
hl.bind(mainMod .. " + CTRL + L", hl.dsp.group.next())
|
|
hl.bind(mainMod .. " + CTRL + J", hl.dsp.group.move_window({ forward = true }))
|
|
hl.bind(mainMod .. " + CTRL + K", hl.dsp.group.move_window({ forward = false }))
|
|
hl.bind(mainMod .. " + CTRL + SHIFT + H", function() smart_group("l") end)
|
|
hl.bind(mainMod .. " + CTRL + SHIFT + J", function() smart_group("d") end)
|
|
hl.bind(mainMod .. " + CTRL + SHIFT + K", function() smart_group("u") end)
|
|
hl.bind(mainMod .. " + CTRL + SHIFT + L", function() smart_group("r") end)
|
|
hl.bind(mainMod .. " + Z", hl.dsp.group.next())
|
|
hl.bind(mainMod .. " + SHIFT + Z", hl.dsp.group.prev())
|
|
|
|
-- Layout-aware navigation
|
|
hl.bind(mainMod .. " + ALT + H", nav("l"))
|
|
hl.bind(mainMod .. " + ALT + L", nav("r"))
|
|
hl.bind(mainMod .. " + ALT + J", nav("d"))
|
|
hl.bind(mainMod .. " + ALT + K", nav("u"))
|
|
hl.bind(mainMod .. " + ALT + Tab", nav("r"))
|
|
hl.bind(mainMod .. " + ALT + SHIFT + Tab", nav("l"))
|
|
hl.bind(mainMod .. " + ALT + SHIFT + J", resize_h("-"))
|
|
hl.bind(mainMod .. " + ALT + SHIFT + K", resize_h("+"))
|
|
|
|
-- Mouse binds
|
|
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
|
|
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
|
|
|
|
-- master-scroll slave scrolling (mouse wheel + bracket fallback)
|
|
-- Guard by layout name: built-in and swap layouts error on unknown scroll messages.
|
|
local scroll_layouts = {
|
|
["lua:master-scroll"] = true,
|
|
["lua:slave-master-scroll"] = true,
|
|
["lua:center-master-scroll"] = true,
|
|
["lua:top-master-scroll"] = true,
|
|
["lua:center-master-scroll-v"] = true,
|
|
}
|
|
local function layout_scroll(dir)
|
|
return function()
|
|
if scroll_layouts[hl.get_config("general.layout")] then
|
|
hl.dispatch(hl.dsp.layout(dir))
|
|
end
|
|
end
|
|
end
|
|
hl.bind(mainMod .. " + mouse_down", layout_scroll("scrollup"), { mouse = true })
|
|
hl.bind(mainMod .. " + mouse_up", layout_scroll("scrolldown"), { mouse = true })
|
|
hl.bind(mainMod .. " + bracketright", layout_scroll("scrolldown"))
|
|
hl.bind(mainMod .. " + bracketleft", layout_scroll("scrollup"))
|
|
|
|
-- Screenshots
|
|
hl.bind("Print", hl.dsp.exec_cmd("grimblast --notify copy screen"))
|
|
hl.bind(mainMod .. " + Print", hl.dsp.exec_cmd("owlry-screenshot-menu"))
|
|
hl.bind("SHIFT + Print", hl.dsp.exec_cmd("uwsm app -- kitty --class=scrrec -e wf-recorder -f ~/Videos/scrrec.mkv -y -g \"$(slurp)\""))
|
|
|
|
-- Multimedia
|
|
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"), { repeating = true })
|
|
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { repeating = true })
|
|
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true })
|
|
hl.bind("SHIFT + XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%+"), { repeating = true })
|
|
hl.bind("SHIFT + XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%-"), { repeating = true })
|
|
hl.bind("SHIFT + XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true })
|
|
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
|
|
hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
|
|
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
|
|
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
|