diff --git a/dot_config/hypr/hyprland.d.lua/keybinds.lua.tmpl b/dot_config/hypr/hyprland.d.lua/keybinds.lua.tmpl index 3af29ed..e92b08a 100644 --- a/dot_config/hypr/hyprland.d.lua/keybinds.lua.tmpl +++ b/dot_config/hypr/hyprland.d.lua/keybinds.lua.tmpl @@ -22,26 +22,27 @@ 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)) +hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd(terminal), { description = "Terminal" }) +hl.bind(mainMod .. " + SHIFT + Return", hl.dsp.exec_cmd(term_tmux), { description = "Terminal (tmux)" }) +hl.bind(mainMod .. " + CTRL + Return", hl.dsp.exec_cmd(term_tmux_append), { description = "Terminal (attach tmux)" }) +hl.bind(mainMod .. " + F1", hl.dsp.exec_cmd("qs ipc call keybinds toggle"), { description = "Show keybinds" }) +hl.bind(mainMod .. " + ALT + F1", hl.dsp.exec_cmd("hypr-show-binds"), { description = "Show raw binds (owlry)" }) +hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(filemanager), { description = "File manager" }) +hl.bind(mainMod .. " + W", hl.dsp.exec_cmd(browser), { description = "Browser" }) +hl.bind(mainMod .. " + SHIFT + W", hl.dsp.exec_cmd(browserprv), { description = "Browser (private)" }) +hl.bind(mainMod .. " + CTRL + W", hl.dsp.exec_cmd(altbrowser), { description = "Chromium" }) +hl.bind(mainMod .. " + ALT + W", hl.dsp.exec_cmd(browsernewinst),{ description = "Firefox (new instance)" }) +hl.bind(mainMod .. " + Space", hl.dsp.exec_cmd(launcher), { description = "App 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)) +hl.bind(mainMod .. " + SHIFT + E", hl.dsp.exec_cmd(editor), { description = "Neovim" }) +hl.bind(mainMod .. " + CTRL + E", hl.dsp.exec_cmd(alteditor), { description = "Zed editor" }) +hl.bind(mainMod .. " + X", hl.dsp.exec_cmd(taskman), { description = "Task manager" }) +hl.bind(mainMod .. " + C", hl.dsp.exec_cmd(clipman), { description = "Clipboard history" }) +hl.bind(mainMod .. " + F4", hl.dsp.exec_cmd(soundctl), { description = "Audio mixer" }) -- Quick Workspaces submap -hl.bind(mainMod .. " + A", hl.dsp.submap("quickws")) +hl.bind(mainMod .. " + A", hl.dsp.submap("quickws"), { description = "Quick workspace jump" }) 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) @@ -54,35 +55,35 @@ hl.define_submap("quickws", function() 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)) +hl.bind(mainMod .. " + grave", hl.dsp.exec_cmd(notcenter), { description = "Notification center" }) +hl.bind(mainMod .. " + SHIFT + grave", hl.dsp.exec_cmd(notdnd), { description = "Toggle do-not-disturb" }) +hl.bind(mainMod .. " + CTRL + grave", hl.dsp.exec_cmd(nothide), { description = "Hide latest notification" }) -- 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")) +hl.bind(mainMod .. " + Pause", hl.dsp.exec_cmd("hyprlock"), { description = "Lock screen" }) +hl.bind(mainMod .. " + SHIFT + Pause", hl.dsp.exec_cmd("owlry-power-menu"), { description = "Power menu" }) +hl.bind(mainMod .. " + End", hl.dsp.exec_cmd("hyprlock"), { description = "Lock screen" }) +hl.bind(mainMod .. " + SHIFT + End", hl.dsp.exec_cmd("owlry-power-menu"), { description = "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()) +hl.bind(mainMod .. " + Q", hl.dsp.window.close(), { description = "Close window" }) +hl.bind(mainMod .. " + SHIFT + Q", hl.dsp.window.kill(), { description = "Kill window" }) +hl.bind(mainMod .. " + F", hl.dsp.window.float(), { description = "Toggle float" }) +hl.bind(mainMod .. " + SHIFT + F", hl.dsp.window.fullscreen({ action = "toggle" }), { description = "Toggle fullscreen" }) +hl.bind(mainMod .. " + P", hl.dsp.window.pin(), { description = "Pin window" }) +hl.bind(mainMod .. " + U", hl.dsp.focus({ urgent_or_last = true }), { description = "Focus urgent / last" }) +hl.bind(mainMod .. " + V", hl.dsp.window.center(), { description = "Center window" }) -- 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")) +hl.bind(mainMod .. " + SHIFT + Space", hl.dsp.workspace.toggle_special(), { description = "Toggle scratchpad" }) +hl.bind(mainMod .. " + CTRL + Space", hl.dsp.window.move({ workspace = "special" }), { description = "Move to scratchpad" }) +hl.bind(mainMod .. " + N", hl.dsp.workspace.toggle_special("passwordmgr"), { description = "Password manager scratchpad" }) -- 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" })) +hl.bind(mainMod .. " + I", hl.dsp.focus({ monitor = "l" }), { description = "Focus left monitor" }) +hl.bind(mainMod .. " + O", hl.dsp.focus({ monitor = "r" }), { description = "Focus right monitor" }) +hl.bind(mainMod .. " + SHIFT + I", hl.dsp.workspace.move({ monitor = "l" }), { description = "Move workspace to left monitor" }) +hl.bind(mainMod .. " + SHIFT + O", hl.dsp.workspace.move({ monitor = "r" }), { description = "Move workspace to right monitor" }) -- ─── Workspace layout state ─────────────────────────────────────────────────── local ws_layouts = {} @@ -176,8 +177,8 @@ local function resize_h(sign) end -- Layout -hl.bind(mainMod .. " + comma", ws_toggle_ms) -hl.bind(mainMod .. " + period", ws_cycle) +hl.bind(mainMod .. " + comma", ws_toggle_ms, { description = "Toggle master/scroll layout" }) +hl.bind(mainMod .. " + period", ws_cycle, { description = "Cycle workspace layout" }) -- Split ratio (ALT+1-9 = 10%-90%, ALT+0 = 95%) -- mfact exact for master/custom layouts, colresize for built-in scrolling @@ -214,11 +215,11 @@ local function layout_ratio_delta(delta) end end for i = 1, 9 do - hl.bind(mainMod .. " + ALT + " .. i, layout_ratio(i / 10)) + hl.bind(mainMod .. " + ALT + " .. i, layout_ratio(i / 10), { description = "Split 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)) +hl.bind(mainMod .. " + ALT + 0", layout_ratio(0.95), { description = "Split ratio 95%" }) +hl.bind(mainMod .. " + ALT + comma", layout_ratio_delta(-0.05), { description = "Split ratio -5%" }) +hl.bind(mainMod .. " + ALT + period", layout_ratio_delta(0.05), { description = "Split ratio +5%" }) -- Scrolling layout: resize ALL columns for i = 1, 9 do @@ -227,13 +228,13 @@ for i = 1, 9 do if hl.get_config("general.layout") == "scrolling" then hl.dispatch(hl.dsp.layout("colresize all " .. ratio)) end - end) + end, { description = "All columns " .. (i * 10) .. "% (scrolling)" }) 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) +end, { description = "All columns 95% (scrolling)" }) -- Smart Gaps Toggle hl.bind(mainMod .. " + SHIFT + G", function() @@ -246,7 +247,7 @@ hl.bind(mainMod .. " + SHIFT + G", function() hl.config({ general = { gaps_in = 5, gaps_out = 5 } }) hl.notification.create({ text = "Gaps: ON", timeout = 2000, icon = "ok" }) end -end) +end, { description = "Toggle gaps" }) -- Global Focus Notification (Lua Event) hl.on("window.active", function(w) @@ -256,19 +257,19 @@ hl.on("window.active", function(w) 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" })) +hl.bind(mainMod .. " + H", hl.dsp.focus({ direction = "l" }), { description = "Focus left" }) +hl.bind(mainMod .. " + L", hl.dsp.focus({ direction = "r" }), { description = "Focus right" }) +hl.bind(mainMod .. " + K", hl.dsp.focus({ direction = "u" }), { description = "Focus up" }) +hl.bind(mainMod .. " + J", hl.dsp.focus({ direction = "d" }), { description = "Focus 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")) +hl.bind(mainMod .. " + SHIFT + H", move_window("l"), { description = "Move window left" }) +hl.bind(mainMod .. " + SHIFT + L", move_window("r"), { description = "Move window right" }) +hl.bind(mainMod .. " + SHIFT + K", move_window("u"), { description = "Move window up" }) +hl.bind(mainMod .. " + SHIFT + J", move_window("d"), { description = "Move window down" }) -- Resize submap -hl.bind(mainMod .. " + R", hl.dsp.submap("resize")) +hl.bind(mainMod .. " + R", hl.dsp.submap("resize"), { description = "Resize mode" }) 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 }) @@ -283,7 +284,7 @@ hl.define_submap("resize", function() end) -- Zoom submap -hl.bind(mainMod .. " + M", hl.dsp.submap("zoom")) +hl.bind(mainMod .. " + M", hl.dsp.submap("zoom"), { description = "Zoom mode" }) 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")) @@ -293,16 +294,16 @@ hl.define_submap("zoom", function() end) -- Workspace cycling -hl.bind(mainMod .. " + Tab", hl.dsp.focus({ workspace = "m+1" })) -hl.bind(mainMod .. " + SHIFT + Tab", hl.dsp.focus({ workspace = "m-1" })) +hl.bind(mainMod .. " + Tab", hl.dsp.focus({ workspace = "m+1" }), { description = "Next workspace" }) +hl.bind(mainMod .. " + SHIFT + Tab", hl.dsp.focus({ workspace = "m-1" }), { description = "Previous workspace" }) -- 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 })) + hl.bind(mainMod .. " + " .. i, hl.dsp.focus({ workspace = 20 + i }), { description = "Workspace " .. i }) + hl.bind(mainMod .. " + SHIFT + " .. i, hl.dsp.window.move({ workspace = 20 + i }), { description = "Move to workspace " .. i }) end -hl.bind(mainMod .. " + 0", hl.dsp.focus({ workspace = 30 })) -hl.bind(mainMod .. " + SHIFT + 0", hl.dsp.window.move({ workspace = 30 })) +hl.bind(mainMod .. " + 0", hl.dsp.focus({ workspace = 30 }), { description = "Workspace 10" }) +hl.bind(mainMod .. " + SHIFT + 0", hl.dsp.window.move({ workspace = 30 }), { description = "Move to workspace 10" }) -- Groups local function smart_group(dir) @@ -315,31 +316,31 @@ local function smart_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()) +hl.bind(mainMod .. " + G", hl.dsp.group.toggle(), { description = "Toggle group" }) +hl.bind(mainMod .. " + CTRL + G", hl.dsp.window.move({ out_of_group = true }), { description = "Leave group" }) +hl.bind(mainMod .. " + CTRL + H", hl.dsp.group.prev(), { description = "Group: previous tab" }) +hl.bind(mainMod .. " + CTRL + L", hl.dsp.group.next(), { description = "Group: next tab" }) +hl.bind(mainMod .. " + CTRL + J", hl.dsp.group.move_window({ forward = true }), { description = "Group: move tab forward" }) +hl.bind(mainMod .. " + CTRL + K", hl.dsp.group.move_window({ forward = false }), { description = "Group: move tab back" }) +hl.bind(mainMod .. " + CTRL + SHIFT + H", function() smart_group("l") end, { description = "Smart group left" }) +hl.bind(mainMod .. " + CTRL + SHIFT + J", function() smart_group("d") end, { description = "Smart group down" }) +hl.bind(mainMod .. " + CTRL + SHIFT + K", function() smart_group("u") end, { description = "Smart group up" }) +hl.bind(mainMod .. " + CTRL + SHIFT + L", function() smart_group("r") end, { description = "Smart group right" }) +hl.bind(mainMod .. " + Z", hl.dsp.group.next(), { description = "Group: next tab" }) +hl.bind(mainMod .. " + SHIFT + Z", hl.dsp.group.prev(), { description = "Group: previous tab" }) -- 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("+")) +hl.bind(mainMod .. " + ALT + H", nav("l"), { description = "Nav left (layout-aware)" }) +hl.bind(mainMod .. " + ALT + L", nav("r"), { description = "Nav right (layout-aware)" }) +hl.bind(mainMod .. " + ALT + J", nav("d"), { description = "Nav down / add master" }) +hl.bind(mainMod .. " + ALT + K", nav("u"), { description = "Nav up / remove master" }) +hl.bind(mainMod .. " + ALT + Tab", nav("r"), { description = "Nav right (layout-aware)" }) +hl.bind(mainMod .. " + ALT + SHIFT + Tab", nav("l"), { description = "Nav left (layout-aware)" }) +hl.bind(mainMod .. " + ALT + SHIFT + J", resize_h("-"), { description = "Shrink split" }) +hl.bind(mainMod .. " + ALT + SHIFT + K", resize_h("+"), { description = "Grow split" }) -- Mouse binds -hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) +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) @@ -360,22 +361,22 @@ local function layout_scroll(dir) 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")) +hl.bind(mainMod .. " + bracketright", layout_scroll("scrolldown"), { description = "Scroll slave pane down" }) +hl.bind(mainMod .. " + bracketleft", layout_scroll("scrollup"), { description = "Scroll slave pane up" }) -- 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)\"")) +hl.bind("Print", hl.dsp.exec_cmd("grimblast --notify copy screen"), { description = "Screenshot: full screen" }) +hl.bind(mainMod .. " + Print", hl.dsp.exec_cmd("owlry-screenshot-menu"), { description = "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)\""), { description = "Screen record selection" }) -- 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 }) +hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"), { repeating = true, description = "Volume up" }) +hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { repeating = true, description = "Volume down" }) +hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true, description = "Mute" }) +hl.bind("SHIFT + XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%+"), { repeating = true, description = "Mic volume up" }) +hl.bind("SHIFT + XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%-"), { repeating = true, description = "Mic volume down" }) +hl.bind("SHIFT + XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"),{ locked = true, description = "Mic mute" }) +hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true, description = "Play/pause" }) +hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true, description = "Play/pause" }) +hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true, description = "Next track" }) +hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true, description = "Previous track" }) diff --git a/dot_config/nvim/ftplugin/tex.lua b/dot_config/nvim/ftplugin/tex.lua index 1a24997..08fe8fe 100644 --- a/dot_config/nvim/ftplugin/tex.lua +++ b/dot_config/nvim/ftplugin/tex.lua @@ -1,3 +1,6 @@ +-- Source-visible editing by default; toggle with uc +vim.opt_local.conceallevel = 0 + -- Spell-check (tweak languages to your needs) vim.opt_local.spell = true vim.opt_local.spelllang = { "en_gb", "de_de", "fr" } diff --git a/dot_config/nvim/lua/lsp/texlab.lua b/dot_config/nvim/lua/lsp/texlab.lua index 331c298..d0f6062 100644 --- a/dot_config/nvim/lua/lsp/texlab.lua +++ b/dot_config/nvim/lua/lsp/texlab.lua @@ -3,7 +3,7 @@ vim.lsp.config("texlab", { texlab = { build = { executable = "latexmk", - args = { "-pdf", "-interaction=nonstopmode", "-synctex=1", "%f" }, + args = { "-lualatex", "-interaction=nonstopmode", "-synctex=1", "%f" }, onSave = false, forwardSearchAfter = false, }, diff --git a/dot_config/nvim/lua/options.lua b/dot_config/nvim/lua/options.lua index acc8012..a242720 100644 --- a/dot_config/nvim/lua/options.lua +++ b/dot_config/nvim/lua/options.lua @@ -60,3 +60,19 @@ opt.completeopt = { "menu", "menuone", "noselect" } opt.mouse = "a" opt.fileencoding = "utf-8" opt.shortmess:append("c") + +vim.diagnostic.config({ + virtual_text = { prefix = "●", source = "if_many" }, + float = { border = "rounded", source = true }, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = " ", + [vim.diagnostic.severity.WARN] = " ", + [vim.diagnostic.severity.HINT] = " ", + [vim.diagnostic.severity.INFO] = " ", + }, + }, + underline = true, + update_in_insert = false, + severity_sort = true, +}) diff --git a/dot_config/nvim/lua/plugins/editing.lua b/dot_config/nvim/lua/plugins/editing.lua index 6d2e061..4cfc69f 100644 --- a/dot_config/nvim/lua/plugins/editing.lua +++ b/dot_config/nvim/lua/plugins/editing.lua @@ -57,6 +57,7 @@ return { html = { "prettier" }, css = { "prettier" }, markdown = { "prettier" }, + tex = { "latexindent" }, ["jinja.html"] = { "djlint" }, }, format_on_save = { @@ -65,4 +66,21 @@ return { }, }, }, + + -- Doc-stub generation: JSDoc, Javadoc, rustdoc + { + "danymat/neogen", + cmd = "Neogen", + keys = { + { "lg", function() require("neogen").generate() end, desc = "Generate doc" }, + }, + opts = { + snippet_engine = "luasnip", + languages = { + java = { template = { annotation_convention = "javadoc" } }, + typescript = { template = { annotation_convention = "tsdoc" } }, + rust = { template = { annotation_convention = "rustdoc" } }, + }, + }, + }, } diff --git a/dot_config/nvim/lua/plugins/ui.lua b/dot_config/nvim/lua/plugins/ui.lua index 5584239..42d6a82 100644 --- a/dot_config/nvim/lua/plugins/ui.lua +++ b/dot_config/nvim/lua/plugins/ui.lua @@ -147,6 +147,9 @@ return { local map = vim.keymap.set map("n", "uw", function() vim.opt.wrap = not vim.wo.wrap end, { desc = "Toggle wrap" }) map("n", "uf", "zA", { desc = "Toggle all folds" }) + map("n", "uc", function() + vim.opt_local.conceallevel = vim.o.conceallevel == 0 and 2 or 0 + end, { desc = "Toggle conceal" }) end, }, diff --git a/dot_config/nvim/lua/plugins/vimtex.lua b/dot_config/nvim/lua/plugins/vimtex.lua index d971a78..865fc04 100644 --- a/dot_config/nvim/lua/plugins/vimtex.lua +++ b/dot_config/nvim/lua/plugins/vimtex.lua @@ -5,22 +5,23 @@ return { ft = { "tex", "plaintex", "bib" }, init = function() -- Must be set before vimtex loads - vim.g.vimtex_view_method = "zathura" + -- zathura-synctex: Wayland-native viewer (DBus forward search, hyprctl focus, no xdotool) + vim.g.vimtex_view_method = "general" + vim.g.vimtex_view_general_viewer = "zathura-synctex" + vim.g.vimtex_view_general_options = "--synctex-forward @line:@col:@tex @pdf" vim.g.vimtex_compiler_method = "latexmk" + vim.g.vimtex_compiler_latexmk_engines = { _ = "-lualatex" } vim.g.vimtex_compiler_latexmk = { options = { - "-pdf", "-shell-escape", "-verbose", "-file-line-error", "-synctex=1", "-interaction=nonstopmode", - "-lualatex", }, } - -- Disable vimtex completion (blink.cmp handles it via cmp-vimtex) - vim.g.vimtex_complete_enabled = 0 - -- Disable vimtex's own syntax (use treesitter instead, with vim regex fallback) + -- Must stay 1: cmp-vimtex uses VimTeX's completion engine for \begin/\end items + vim.g.vimtex_complete_enabled = 1 vim.g.vimtex_syntax_enabled = 1 vim.g.vimtex_syntax_conceal_disable = 0 end, diff --git a/dot_config/quickshell/keybinds/KeybindsWindow.qml b/dot_config/quickshell/keybinds/KeybindsWindow.qml new file mode 100644 index 0000000..c5c9511 --- /dev/null +++ b/dot_config/quickshell/keybinds/KeybindsWindow.qml @@ -0,0 +1,368 @@ +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import Quickshell.Wayland +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import "../shared" as Shared + +Scope { + id: root + + property bool keybindsVisible: false + property bool animating: false + property int activeTab: 0 + property var allBinds: [] + property var filteredBinds: [] + property string targetMonitor: Shared.Config.monitor + + readonly property var tabMasks: [64, 65, 68, 72] + readonly property var tabLabels: ["SUPER", "SUPER+SHIFT", "SUPER+CTRL", "SUPER+ALT"] + + IpcHandler { + target: "keybinds" + function toggle(): void { root.toggle() } + } + + function toggle() { + if (root.keybindsVisible) { + root.keybindsVisible = false + closeTimer.restart() + } else { + root.targetMonitor = Hyprland.focusedMonitor?.name ?? Shared.Config.monitor + root.animating = true + root.activeTab = 0 + root.keybindsVisible = true + bindsProc.running = false + bindsProc.running = true + } + } + + function updateFilter() { + const mask = root.tabMasks[root.activeTab] + root.filteredBinds = root.allBinds.filter(function(b) { + return b.modmask === mask + && !b.mouse + && b.submap === "" + && !b.key.startsWith("_") + }) + } + + function formatKey(k) { + if (k.length === 1) return k.toUpperCase() + const pretty = { + "Return": "↵", + "BackSpace": "⌫", + "Tab": "⇥", + "space": "Space", + "Prior": "PgUp", + "Next": "PgDn", + "comma": ",", + "period": ".", + "semicolon": ";", + "apostrophe": "'", + "grave": "`", + "minus": "-", + "equal": "=", + "bracketleft": "[", + "bracketright": "]", + "backslash": "\\", + "slash": "/", + } + return pretty[k] ?? (k.length > 10 ? k.slice(0, 9) + "…" : k) + } + + function formatAction(b) { + if (b.has_description && b.description !== "") return b.description + if (b.dispatcher === "exec") { + let a = b.arg + let idx = a.indexOf("-- ") + let cmd = idx >= 0 ? a.slice(idx + 3) : a + return cmd.length > 48 ? cmd.slice(0, 45) + "…" : cmd + } + let s = b.dispatcher + (b.arg ? " " + b.arg : "") + return s.length > 48 ? s.slice(0, 45) + "…" : s + } + + onAllBindsChanged: updateFilter() + onActiveTabChanged: updateFilter() + + Timer { + id: closeTimer + interval: 220 + onTriggered: root.animating = false + } + + + Process { + id: bindsProc + command: ["hyprctl", "binds", "-j"] + stdout: StdioCollector { + onStreamFinished: { + try { root.allBinds = JSON.parse(this.text) } catch(e) {} + } + } + } + + Variants { + model: Quickshell.screens + + delegate: Component { + PanelWindow { + required property var modelData + screen: modelData + + WlrLayershell.namespace: "quickshell:keybinds" + WlrLayershell.layer: WlrLayer.Overlay + surfaceFormat { opaque: false } + exclusionMode: ExclusionMode.Ignore + focusable: true + + visible: modelData.name === root.targetMonitor + && (root.keybindsVisible || root.animating) + + anchors { top: true; bottom: true; left: true; right: true } + color: "transparent" + + // Dim backdrop + Rectangle { + anchors.fill: parent + color: Qt.rgba(0, 0, 0, 0.45) + opacity: root.keybindsVisible ? 1.0 : 0.0 + Behavior on opacity { NumberAnimation { duration: 180; easing.type: Easing.OutCubic } } + } + + // Click on backdrop to close + MouseArea { + anchors.fill: parent + onClicked: { + root.keybindsVisible = false + closeTimer.restart() + } + } + + // Card + Rectangle { + id: card + anchors.centerIn: parent + width: 760 + height: 560 + z: 1 + radius: Shared.Theme.radiusNormal + color: Shared.Theme.popoutBackground + border.width: 1 + border.color: Shared.Theme.borderSubtle + focus: true + + opacity: root.keybindsVisible ? 1.0 : 0.0 + scale: root.keybindsVisible ? 1.0 : 0.97 + + Behavior on opacity { NumberAnimation { duration: 180; easing.type: Easing.OutCubic } } + Behavior on scale { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } } + + Keys.onEscapePressed: { + root.keybindsVisible = false + closeTimer.restart() + } + + // Consume clicks so backdrop doesn't close + MouseArea { anchors.fill: parent; z: -1 } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Shared.Theme.popoutPadding + spacing: Shared.Theme.popoutSpacing + + // ── Header ────────────────────────────── + RowLayout { + Layout.fillWidth: true + spacing: 12 + + // Icon badge — styled like the key chips + Rectangle { + implicitWidth: 34 + implicitHeight: 28 + radius: 6 + color: Qt.alpha(Shared.Theme.accent, Shared.Theme.opacityLight) + border.width: 1 + border.color: Qt.alpha(Shared.Theme.accent, 0.35) + + Text { + anchors.centerIn: parent + text: "⌨" + color: Shared.Theme.accent + font.pixelSize: 16 + font.family: Shared.Theme.fontFamily + } + } + + Text { + text: "Keybinds" + color: Shared.Theme.text + font.pixelSize: Shared.Theme.fontLarge + font.family: Shared.Theme.fontFamily + font.bold: true + } + + Item { Layout.fillWidth: true } + + Text { + text: "ESC / click outside to close" + color: Shared.Theme.overlay0 + font.pixelSize: Shared.Theme.fontSmall + font.family: Shared.Theme.fontFamily + } + } + + // ── Tabs ──────────────────────────────── + RowLayout { + Layout.fillWidth: true + spacing: 6 + + Repeater { + model: root.tabLabels + + Rectangle { + required property string modelData + required property int index + + readonly property bool current: root.activeTab === index + + implicitHeight: 30 + implicitWidth: tabText.implicitWidth + 20 + radius: Shared.Theme.radiusSmall + color: current + ? Qt.alpha(Shared.Theme.accent, Shared.Theme.opacityLight) + : Qt.alpha(Shared.Theme.surface0, 0.5) + border.width: 1 + border.color: current + ? Qt.alpha(Shared.Theme.accent, Shared.Theme.opacityMedium) + : Shared.Theme.borderSubtle + + Behavior on color { ColorAnimation { duration: 120 } } + Behavior on border.color { ColorAnimation { duration: 120 } } + + Text { + id: tabText + anchors.centerIn: parent + text: modelData + color: parent.current ? Shared.Theme.accent : Shared.Theme.subtext0 + font.pixelSize: Shared.Theme.fontSmall + font.family: Shared.Theme.fontFamily + font.bold: parent.current + + Behavior on color { ColorAnimation { duration: 120 } } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.activeTab = index + } + } + } + + Item { Layout.fillWidth: true } + + Text { + text: root.filteredBinds.length + " binds" + color: Shared.Theme.overlay0 + font.pixelSize: Shared.Theme.fontSmall + font.family: Shared.Theme.fontFamily + } + } + + // ── Divider ───────────────────────────── + Rectangle { + Layout.fillWidth: true + implicitHeight: 1 + color: Shared.Theme.borderSubtle + } + + // ── Bind list ─────────────────────────── + ListView { + id: bindsList + Layout.fillWidth: true + Layout.fillHeight: true + model: root.filteredBinds + clip: true + spacing: 2 + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + width: 6 + } + + delegate: Rectangle { + required property var modelData + required property int index + + width: bindsList.width - 8 + implicitHeight: 34 + radius: Shared.Theme.radiusSmall + color: rowMouse.containsMouse + ? Qt.alpha(Shared.Theme.surface0, 0.6) + : "transparent" + + Behavior on color { ColorAnimation { duration: 80 } } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 8 + anchors.rightMargin: 8 + spacing: 12 + + Rectangle { + implicitHeight: 22 + implicitWidth: Math.max(keyLabel.implicitWidth + 14, 36) + radius: 5 + color: Qt.alpha(Shared.Theme.accent, Shared.Theme.opacityLight) + border.width: 1 + border.color: Qt.alpha(Shared.Theme.accent, 0.25) + + Text { + id: keyLabel + anchors.centerIn: parent + text: root.formatKey(modelData.key) + color: Shared.Theme.accent + font.pixelSize: Shared.Theme.fontSmall + font.family: Shared.Theme.fontFamily + font.bold: true + } + } + + Text { + Layout.fillWidth: true + text: root.formatAction(modelData) + color: Shared.Theme.text + font.pixelSize: Shared.Theme.fontSize + font.family: Shared.Theme.fontFamily + elide: Text.ElideRight + } + + Text { + visible: modelData.dispatcher !== "exec" + && modelData.dispatcher !== "" + && modelData.dispatcher !== "lua" + text: modelData.dispatcher + color: Shared.Theme.overlay0 + font.pixelSize: Shared.Theme.fontSmall + font.family: Shared.Theme.fontFamily + } + } + + MouseArea { + id: rowMouse + anchors.fill: parent + hoverEnabled: true + } + } + } + } + } + } + } + } +} diff --git a/dot_config/quickshell/shell.qml b/dot_config/quickshell/shell.qml index d223a50..f0dd6b6 100644 --- a/dot_config/quickshell/shell.qml +++ b/dot_config/quickshell/shell.qml @@ -4,10 +4,12 @@ import Quickshell import "notifications" import "osd" import "lock" +import "keybinds" ShellRoot { NotificationDaemon { id: notifDaemon } Bar { notifModel: notifDaemon.trackedNotifications; notifDaemon: notifDaemon } Osd {} IdleScreen { id: idleScreen } + KeybindsWindow {} } diff --git a/dot_local/bin/executable_zathura-synctex b/dot_local/bin/executable_zathura-synctex new file mode 100644 index 0000000..8d3366e --- /dev/null +++ b/dot_local/bin/executable_zathura-synctex @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Wayland-native VimTeX viewer: Zathura + DBus forward search + hyprctl focus +# No xdotool required. +# +# VimTeX config: +# vimtex_view_method = "general" +# vimtex_view_general_viewer = "zathura-synctex" +# vimtex_view_general_options = "--synctex-forward @line:@col:@tex @pdf" + +synctex="" +if [[ "${1:-}" == "--synctex-forward" ]]; then + synctex="$2" + pdf="$(realpath "$3")" +else + pdf="$(realpath "$1")" +fi + +# Inverse search reuses the nvim socket inherited from the calling nvim instance +inverse="nvim --server ${NVIM} --remote-send ':VimtexInverseSearch %{line}:%{column} \"%{input}\"\\'" + +# Find a zathura process that has this exact PDF path in its cmdline +find_pid() { + for p in $(pgrep -x zathura 2>/dev/null); do + tr '\0' '\n' < "/proc/$p/cmdline" 2>/dev/null | grep -qxF "$pdf" \ + && echo "$p" && return + done +} + +pid="$(find_pid)" + +if [[ -n "$pid" ]]; then + # Window already open: forward search via DBus, then focus + if [[ -n "$synctex" ]]; then + line="${synctex%%:*}" + rest="${synctex#*:}"; col="${rest%%:*}"; input="${rest#*:}" + gdbus call --session \ + --dest "org.pwmt.zathura.PID-${pid}" \ + --object-path /org/pwmt/zathura \ + --method org.pwmt.zathura.SynctexView \ + "$input" "$line" "$col" >/dev/null 2>&1 || true + fi + hyprctl dispatch "hl.dsp.focus({ window = \"pid:${pid}\" })" >/dev/null 2>&1 || true +else + # Open fresh zathura with inverse search hook + if [[ -n "$synctex" ]]; then + zathura -x "$inverse" --synctex-forward "$synctex" "$pdf" & + else + zathura -x "$inverse" "$pdf" & + fi + zpid=$! + # Give the window ~400ms to map, then focus it + (sleep 0.4 && hyprctl dispatch "hl.dsp.focus({ window = \"pid:${zpid}\" })" >/dev/null 2>&1) & +fi