diff --git a/dot_config/hypr/hyprland.d.lua/keybinds.lua.tmpl b/dot_config/hypr/hyprland.d.lua/keybinds.lua.tmpl index 05aa3e5..ae36117 100644 --- a/dot_config/hypr/hyprland.d.lua/keybinds.lua.tmpl +++ b/dot_config/hypr/hyprland.d.lua/keybinds.lua.tmpl @@ -10,6 +10,9 @@ 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" @@ -24,7 +27,10 @@ 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 .. " + 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 @@ -59,7 +65,7 @@ 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.kill()) +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" })) @@ -78,9 +84,140 @@ 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", hl.dsp.exec_cmd("hypr-workspace-layout toggle-ms")) -hl.bind(mainMod .. " + period", hl.dsp.exec_cmd("hypr-workspace-layout cycle")) +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() @@ -108,20 +245,35 @@ 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 -hl.bind(mainMod .. " + SHIFT + H", hl.dsp.exec_cmd("hypr-workspace-layout move-left")) -hl.bind(mainMod .. " + SHIFT + L", hl.dsp.exec_cmd("hypr-workspace-layout move-right")) -hl.bind(mainMod .. " + SHIFT + K", hl.dsp.exec_cmd("hypr-workspace-layout move-up")) -hl.bind(mainMod .. " + SHIFT + J", hl.dsp.exec_cmd("hypr-workspace-layout move-down")) +-- 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("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 @@ -137,16 +289,10 @@ hl.bind(mainMod .. " + 0", hl.dsp.focus({ workspace = 30 })) hl.bind(mainMod .. " + SHIFT + 0", hl.dsp.window.move({ workspace = 30 })) -- Groups -local function is_grouped() - local h = io.popen("hyprctl activewindow -j 2>/dev/null") - if not h then return false end - local out = h:read("*a"); h:close() - local arr = out:match('"grouped":%[(.-)%]') - return arr ~= nil and arr ~= "" and arr ~= "null" -end - local function smart_group(dir) - if is_grouped() then + 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 })) @@ -167,14 +313,14 @@ 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", hl.dsp.exec_cmd("hypr-workspace-layout nav-prev")) -hl.bind(mainMod .. " + ALT + L", hl.dsp.exec_cmd("hypr-workspace-layout nav-next")) -hl.bind(mainMod .. " + ALT + J", hl.dsp.exec_cmd("hypr-workspace-layout nav-down")) -hl.bind(mainMod .. " + ALT + K", hl.dsp.exec_cmd("hypr-workspace-layout nav-up")) -hl.bind(mainMod .. " + ALT + Tab", hl.dsp.exec_cmd("hypr-workspace-layout nav-next")) -hl.bind(mainMod .. " + ALT + SHIFT + Tab", hl.dsp.exec_cmd("hypr-workspace-layout nav-prev")) -hl.bind(mainMod .. " + ALT + SHIFT + J", hl.dsp.exec_cmd("hypr-workspace-layout resize-shrink-h")) -hl.bind(mainMod .. " + ALT + SHIFT + K", hl.dsp.exec_cmd("hypr-workspace-layout resize-grow-h")) +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 }) @@ -207,6 +353,13 @@ 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("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 }) diff --git a/dot_config/hypr/hyprland.d.lua/layout.lua b/dot_config/hypr/hyprland.d.lua/layout.lua index 487b7c7..68c5bb1 100644 --- a/dot_config/hypr/hyprland.d.lua/layout.lua +++ b/dot_config/hypr/hyprland.d.lua/layout.lua @@ -115,6 +115,9 @@ hl.layout.register("master-scroll", { if msg == "scrolldown" then ms.offset = math.min(ms.offset + 1, max_off); return true elseif msg == "scrollup" then ms.offset = math.max(ms.offset - 1, 0); return true elseif msg == "reset" then ms.offset = 0; return true + else + local v = msg:match("^mfact exact (.+)$") + if v then mfact = math.max(0.1, math.min(0.95, tonumber(v) or mfact)); return true end end end, }) @@ -144,6 +147,9 @@ hl.layout.register("slave-master-scroll", { if msg == "scrolldown" then sm.offset = math.min(sm.offset + 1, max_off); return true elseif msg == "scrollup" then sm.offset = math.max(sm.offset - 1, 0); return true elseif msg == "reset" then sm.offset = 0; return true + else + local v = msg:match("^mfact exact (.+)$") + if v then mfact = math.max(0.1, math.min(0.95, tonumber(v) or mfact)); return true end end end, }) @@ -218,6 +224,9 @@ hl.layout.register("center-master-scroll", { if addr and cm_right_addrs[addr] then return scroll_col(cm_right, -1) end elseif msg == "reset" then cm_left.offset = 0; cm_right.offset = 0; return true + else + local v = msg:match("^mfact exact (.+)$") + if v then mfact = math.max(0.1, math.min(0.95, tonumber(v) or mfact)); return true end end end, }) @@ -312,6 +321,9 @@ hl.layout.register("top-master-scroll", { if msg == "scrolldown" then tm.offset = math.min(tm.offset + 1, max_off); return true elseif msg == "scrollup" then tm.offset = math.max(tm.offset - 1, 0); return true elseif msg == "reset" then tm.offset = 0; return true + else + local v = msg:match("^mfact exact (.+)$") + if v then mfact_v = math.max(0.1, math.min(0.95, tonumber(v) or mfact_v)); return true end end end, }) @@ -381,6 +393,9 @@ hl.layout.register("center-master-scroll-v", { if addr and cm_vbot_addrs[addr] then return scroll_row(cm_vbot, cm_vbot_addrs, -1) end elseif msg == "reset" then cm_vtop.offset = 0; cm_vbot.offset = 0; return true + else + local v = msg:match("^mfact exact (.+)$") + if v then mfact_v = math.max(0.1, math.min(0.95, tonumber(v) or mfact_v)); return true end end end, }) @@ -422,7 +437,11 @@ hl.layout.register("master-swap", { targets[si]:place({ x=slave_area.x, y=slave_area.y+(j-1)*h, w=slave_area.w, h=h }) end end, - layout_msg = function(_, msg) if msg == "recalc" then return true end end, + layout_msg = function(_, msg) + if msg == "recalc" then return true end + local v = msg:match("^mfact exact (.+)$") + if v then mfact = math.max(0.1, math.min(0.95, tonumber(v) or mfact)); return true end + end, }) -- ─── slave-master-swap: slaves left, master right ───────────────────────────── @@ -448,7 +467,11 @@ hl.layout.register("slave-master-swap", { targets[si]:place({ x=slave_area.x, y=slave_area.y+(j-1)*h, w=slave_area.w, h=h }) end end, - layout_msg = function(_, msg) if msg == "recalc" then return true end end, + layout_msg = function(_, msg) + if msg == "recalc" then return true end + local v = msg:match("^mfact exact (.+)$") + if v then mfact = math.max(0.1, math.min(0.95, tonumber(v) or mfact)); return true end + end, }) -- ─── top-master-swap: master top, slaves bottom row ─────────────────────────── @@ -471,7 +494,11 @@ hl.layout.register("top-master-swap", { targets[si]:place({ x=slave_area.x+(j-1)*w, y=slave_area.y, w=w, h=slave_area.h }) end end, - layout_msg = function(_, msg) if msg == "recalc" then return true end end, + layout_msg = function(_, msg) + if msg == "recalc" then return true end + local v = msg:match("^mfact exact (.+)$") + if v then mfact_v = math.max(0.1, math.min(0.95, tonumber(v) or mfact_v)); return true end + end, }) -- ─── Auto-scroll on focus + swap-on-focus ────────────────────────────────────