mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-02 11:32:42 +00:00
328 lines
18 KiB
Lua
328 lines
18 KiB
Lua
-- ============================================================================
|
|
-- Personal overrides, ported from the niri config.
|
|
-- Append to ~/.config/hypr/hyprland.lua, after require("default.hypr.omarchy").
|
|
-- Every rebind of an Omarchy default is unbound first. The comment after each
|
|
-- unbind says what the key did before.
|
|
-- ============================================================================
|
|
|
|
-- ─── Window management ──────────────────────────────────────────────────────
|
|
|
|
-- Spawn Terminal
|
|
-- hl.unbind("SUPER + RETURN") -- was: toggle window transparency
|
|
|
|
-- Mod+Q -> close. Omarchy already binds this, so it is only made explicit.
|
|
hl.unbind("SUPER + Q")
|
|
o.bind("SUPER + Q", "Close window", hl.dsp.window.close())
|
|
|
|
-- Mod+W was tabbed display. Here it flips the workspace layout, and sets every
|
|
-- column to 50% of the screen (niri's default-column-width) when the
|
|
-- workspace turns to scrolling. Dwindle needs no widths, because it re-tiles
|
|
-- on its own. Needs ~/.local/bin/omarchy-toggle-layout.
|
|
hl.unbind("SUPER + W") -- was: close window
|
|
o.bind("SUPER + W", "Toggle workspace layout", "omarchy-toggle-layout 0.5")
|
|
|
|
-- Mod+BackSpace -> last window.
|
|
hl.unbind("SUPER + BACKSPACE") -- was: toggle window transparency
|
|
o.bind("SUPER + BACKSPACE", "Focus last window", hl.dsp.focus({ last = true }))
|
|
|
|
-- Mod+Tab -> switch windows inside the focused workspace. Omarchy parks the
|
|
-- workspace hop here, but SUPER + CTRL + J/K already does that, so the key is
|
|
-- free. SUPER + CTRL + TAB (former workspace) keeps its default job.
|
|
--
|
|
-- cyclenext is deliberately not used: it walks into a hidden special
|
|
-- workspace and drags the scratchpad into view. This ring is built from the
|
|
-- windows of the active workspace alone, and a workspace lives on exactly one
|
|
-- monitor, so focus never leaves the display. Grouped windows stay in the
|
|
-- ring (focusing a hidden member just flips the group to that tab); anything
|
|
-- else hidden is skipped. stable_id fixes the order, so repeated presses walk
|
|
-- the same ring instead of following focus history.
|
|
local function cycle_in_workspace(step)
|
|
return function()
|
|
local active = hl.get_active_window()
|
|
local workspace = active and active.workspace or hl.get_active_workspace()
|
|
if not workspace then
|
|
return
|
|
end
|
|
|
|
local ring = {}
|
|
for _, window in ipairs(hl.get_workspace_windows(workspace.id)) do
|
|
if window.mapped and (window.group or not window.hidden) then
|
|
ring[#ring + 1] = window
|
|
end
|
|
end
|
|
if #ring < 2 then
|
|
return
|
|
end
|
|
table.sort(ring, function(a, b)
|
|
return a.stable_id < b.stable_id
|
|
end)
|
|
|
|
local current = 1
|
|
for index, window in ipairs(ring) do
|
|
if active and window.address == active.address then
|
|
current = index
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Raise after the focus, so a floating window comes up on top instead of
|
|
-- staying buried, the way Omarchy's ALT + TAB does.
|
|
hl.dispatch(hl.dsp.focus({ window = ring[(current - 1 + step) % #ring + 1] }))
|
|
hl.dispatch(hl.dsp.window.bring_to_top())
|
|
end
|
|
end
|
|
|
|
hl.unbind("SUPER + TAB") -- was: next workspace
|
|
hl.unbind("SUPER + SHIFT + TAB") -- was: previous workspace
|
|
o.bind("SUPER + TAB", "Focus on next window in workspace", cycle_in_workspace(1))
|
|
o.bind("SUPER + SHIFT + TAB", "Focus on previous window in workspace", cycle_in_workspace(-1))
|
|
|
|
-- Tabs. Hyprland groups are the nearest thing to a niri tabbed column.
|
|
-- SUPER + G (group) and SUPER + ALT + TAB (next in group) are Omarchy defaults.
|
|
|
|
-- ─── Focus and swap: hjkl ───────────────────────────────────────────────────
|
|
|
|
-- Focus steps through the windows of a group first, and leaves the group only
|
|
-- at the ends. This is the focus half of the smart-move behavior below, and
|
|
-- Hyprland does it on its own, so no script is needed.
|
|
hl.config({ binds = { movefocus_cycles_groupfirst = true } })
|
|
|
|
hl.unbind("SUPER + J") -- was: toggle window split
|
|
hl.unbind("SUPER + K") -- was: keybindings menu
|
|
hl.unbind("SUPER + L") -- was: toggle workspace layout
|
|
|
|
o.bind("SUPER + H", "Focus on left window", hl.dsp.focus({ direction = "l" }))
|
|
o.bind("SUPER + J", "Focus on below window", hl.dsp.focus({ direction = "d" }))
|
|
o.bind("SUPER + K", "Focus on above window", hl.dsp.focus({ direction = "u" }))
|
|
o.bind("SUPER + L", "Focus on right window", hl.dsp.focus({ direction = "r" }))
|
|
|
|
-- The old SUPER + J job, moved to a free key. Dwindle only; toggling needs
|
|
-- dwindle.preserve_split, which Omarchy already sets.
|
|
o.bind("SUPER + D", "Toggle window split", hl.dsp.layout("togglesplit"))
|
|
|
|
-- Smart move: merge into the neighboring group if there is one, else swap.
|
|
-- A floating window is parked in a monitor corner instead, clockwise from
|
|
-- the bottom left: H bottom-left, J top-left, K top-right, L bottom-right.
|
|
-- Needs ~/.local/bin/omarchy-smart-move.
|
|
o.bind("SUPER + SHIFT + H", "Move or merge left / float to bottom-left", "omarchy-smart-move l")
|
|
o.bind("SUPER + SHIFT + J", "Move or merge down / float to top-left", "omarchy-smart-move d")
|
|
o.bind("SUPER + SHIFT + K", "Move or merge up / float to top-right", "omarchy-smart-move u")
|
|
o.bind("SUPER + SHIFT + L", "Move or merge right / float to bottom-right", "omarchy-smart-move r")
|
|
|
|
-- Picture-in-picture for any window: float + pin, 30% of the focused
|
|
-- monitor on both axes, bottom-left corner; press again to tile it back.
|
|
-- Needs ~/.local/bin/omarchy-pip (omarchy-summon shares it). SUPER + SHIFT
|
|
-- + T is free in Omarchy; SUPER + T (toggle floating) stays as is.
|
|
o.bind("SUPER + SHIFT + T", "Toggle picture-in-picture", "omarchy-pip")
|
|
|
|
-- ─── Workspaces and monitors ────────────────────────────────────────────────
|
|
|
|
hl.unbind("SUPER + CTRL + K") -- was: Herdr keybindings
|
|
o.bind("SUPER + CTRL + J", "Next workspace", hl.dsp.focus({ workspace = "e+1" }))
|
|
o.bind("SUPER + CTRL + K", "Previous workspace", hl.dsp.focus({ workspace = "e-1" }))
|
|
|
|
-- Scratch workspaces. Omarchy's SUPER + S is taken by the region screenshot
|
|
-- below, so the main toggle (Tern) lands on SUPER + CTRL + S, with
|
|
-- SUPER + A (free in Omarchy) as the home-row alias. Claude Desktop has its
|
|
-- own scratch workspace on SUPER + CTRL + ALT + L. SUPER + ALT + S (send
|
|
-- window to the scratchpad) is still the Omarchy default and needs no line.
|
|
--
|
|
-- All of them go through ~/.local/bin/omarchy-toggle-scratch rather than the
|
|
-- raw special-workspace toggle: it opens each workspace on its home monitor
|
|
-- (the raw toggle uses the focused one), and on reveal it starts whichever
|
|
-- resident is missing instead of sliding an empty workspace into view. The
|
|
-- residents are pinned to their workspaces by window rules in
|
|
-- hypr/hyprland.lua. Fastmail and Beeper have their own scratch workspaces
|
|
-- under SUPER + CTRL + ALT + E / M in the Applications block below.
|
|
hl.unbind("SUPER + CTRL + S") -- was: share menu
|
|
hl.unbind("SUPER + SHIFT + A") -- was: ChatGPT web app
|
|
|
|
o.bind("SUPER + A", "Toggle scratch workspace", "omarchy-toggle-scratch scratchpad")
|
|
o.bind("SUPER + CTRL + ALT + H", "Toggle Tern scratch workspace", "omarchy-toggle-scratch scratchpad")
|
|
o.bind("SUPER + CTRL + ALT + L", "Toggle Claude scratch workspace", "omarchy-toggle-scratch claude")
|
|
|
|
-- Workspaces 1 to 4 (Studio Display) on Q W E R and 5 to 8 (laptop panel) on
|
|
-- Y U I O: ALT to focus, ALT + SHIFT to send the window there. The pinning
|
|
-- lives in hypr/hyprland.lua, so focusing a workspace also moves to its
|
|
-- monitor. No Omarchy default uses ALT with a letter, so nothing is unbound.
|
|
for index, key in ipairs({ "Q", "W", "E", "R", "Y", "U", "I", "O" }) do
|
|
local workspace = tostring(index)
|
|
|
|
o.bind("ALT + " .. key, "Switch to workspace " .. workspace, hl.dsp.focus({ workspace = workspace }))
|
|
o.bind("ALT + SHIFT + " .. key, "Move window to workspace " .. workspace, hl.dsp.window.move({ workspace = workspace }))
|
|
end
|
|
|
|
hl.unbind("SUPER + O") -- was: pop window out
|
|
hl.unbind("SUPER + SHIFT + O") -- was: Obsidian (moved to SUPER + CTRL + ALT + O)
|
|
o.bind("SUPER + O", "Focus next monitor", hl.dsp.focus({ monitor = "+1" }))
|
|
o.bind("SUPER + SHIFT + O", "Move window to next monitor", hl.dsp.window.move({ monitor = "+1" }))
|
|
|
|
o.bind("ALT + SHIFT + J", "Move window to next monitor", hl.dsp.window.move({ monitor = "+1" }))
|
|
o.bind("ALT + SHIFT + K", "Move window to previous monitor", hl.dsp.window.move({ monitor = "-1" }))
|
|
|
|
-- Named workspaces, in place of niri-dynamic-workspaces.
|
|
o.bind("SUPER + SHIFT + CTRL + ALT + W", "Workspace w", hl.dsp.focus({ workspace = "name:w" }))
|
|
o.bind("SUPER + SHIFT + CTRL + ALT + H", "Workspace h", hl.dsp.focus({ workspace = "name:h" }))
|
|
o.bind("SUPER + CTRL + ALT + X", "Workspace x", hl.dsp.focus({ workspace = "name:x" }))
|
|
|
|
-- ─── Sizing ─────────────────────────────────────────────────────────────────
|
|
|
|
-- Mod+R / Mod+Shift+R. Needs ~/.local/bin/omarchy-cycle-window-size.
|
|
o.bind("SUPER + R", "Cycle window size", "omarchy-cycle-window-size")
|
|
o.bind("SUPER + SHIFT + R", "Cycle window size back", "omarchy-cycle-window-size prev")
|
|
|
|
-- Alt+Shift+hjkl. Hyprland resizes by pixels, not by percent, so 100 px
|
|
-- replaces the niri 10%.
|
|
hl.unbind("SUPER + CTRL + H") -- was: toggle window gaps (now Google Home; SUPER + comma shrinks width)
|
|
hl.unbind("SUPER + CTRL + L") -- was: toggle window gaps
|
|
hl.unbind("SUPER + CTRL + K") -- was: toggle window gaps
|
|
hl.unbind("SUPER + CTRL + J") -- was: toggle window gaps
|
|
|
|
o.bind("SUPER + CTRL + L", "Grow window width", hl.dsp.window.resize({ x = 100, y = 0, relative = true }))
|
|
o.bind("SUPER + CTRL + K", "Shrink window height", hl.dsp.window.resize({ x = 0, y = -100, relative = true }))
|
|
o.bind("SUPER + CTRL + J", "Grow window height", hl.dsp.window.resize({ x = 0, y = 100, relative = true }))
|
|
|
|
-- Same width steps on SUPER + , / SUPER + . (the < and > keys). Key names are
|
|
-- the lower-case xkb keysyms; "COMMA"/"PERIOD" would not match.
|
|
hl.unbind("SUPER + comma") -- was: dismiss last notification (now SUPER + SHIFT + BACKSPACE)
|
|
o.bind("SUPER + comma", "Shrink window width", hl.dsp.window.resize({ x = -100, y = 0, relative = true }))
|
|
o.bind("SUPER + period", "Grow window width", hl.dsp.window.resize({ x = 100, y = 0, relative = true }))
|
|
|
|
-- The notification dismiss SUPER + comma gave up. SUPER + SHIFT + comma still
|
|
-- dismisses all.
|
|
hl.unbind("SUPER + SHIFT + BACKSPACE") -- was: toggle window gaps
|
|
o.bind("SUPER + SHIFT + BACKSPACE", "Dismiss last notification", "omarchy-shell notifications dismissOne")
|
|
|
|
-- Column fitting. These are scrolling layout messages, so they do nothing on
|
|
-- a dwindle workspace. Mod+F (expand to available width) has no exact match;
|
|
-- the Omarchy default SUPER + ALT + F (full width) is the closest.
|
|
o.bind("SUPER + ALT + C", "Fit active column", hl.dsp.layout("fit active"))
|
|
o.bind("SUPER + SHIFT + ALT + C", "Fit visible columns", hl.dsp.layout("fit visible"))
|
|
|
|
-- Super+C (niri center-column). Floating windows go through centerwindow.
|
|
-- On a scrolling workspace the column is re-focused with focus_fit_method
|
|
-- briefly set to 0 (center), then restored, so plain focus moves keep
|
|
-- niri's center-focused-column "never". A relative "move" can't do this: the
|
|
-- tape stops at its first column, so the leftmost window never reaches the
|
|
-- middle. Dwindle and master have nothing to scroll; the key does nothing.
|
|
local function center_window()
|
|
local window = hl.get_active_window()
|
|
if not window then
|
|
return
|
|
end
|
|
if window.floating then
|
|
hl.dispatch(hl.dsp.window.center())
|
|
return
|
|
end
|
|
if not window.workspace or window.workspace.tiled_layout ~= "scrolling" then
|
|
return
|
|
end
|
|
local fit_method = hl.get_config("scrolling.focus_fit_method")
|
|
hl.config({ scrolling = { focus_fit_method = 0 } })
|
|
hl.dispatch(hl.dsp.focus({ window = window }))
|
|
hl.config({ scrolling = { focus_fit_method = fit_method } })
|
|
end
|
|
|
|
hl.unbind("SUPER + C") -- was: universal copy
|
|
o.bind("SUPER + C", "Center window / column", center_window)
|
|
|
|
-- ─── Applications ───────────────────────────────────────────────────────────
|
|
-- Same modifier block as the niri config. Each one focuses an open window
|
|
-- before it launches a new one. The launch command uses the same desktop
|
|
-- entry the vicinae URLs named, so no binary names are guessed.
|
|
|
|
hl.unbind("SUPER + CTRL + A") -- was: audio panel
|
|
|
|
o.bind("SUPER + CTRL + ALT + J", "Terminals", "omarchy-cycle-terminal")
|
|
o.bind("SUPER + CTRL + ALT + K", "Browsers", "omarchy-cycle-browser")
|
|
o.bind("SUPER + CTRL + ALT + G", "Ghostty", "omarchy-cycle ghostty")
|
|
o.bind("SUPER + CTRL + ALT + O", "Obsidian", "omarchy-cycle obsidian")
|
|
o.bind("SUPER + CTRL + ALT + F", "File manager", "omarchy-launch-or-focus nautilus \"gtk-launch org.gnome.Nautilus\"")
|
|
-- Fastmail and Beeper each live on their own scratch workspace (window rules
|
|
-- in hypr/hyprland.lua); the toggle reveals it on the focused monitor and
|
|
-- starts the app only when no window of it exists -- the web-app launcher
|
|
-- would otherwise open a second window every time.
|
|
o.bind("SUPER + CTRL + ALT + E", "Fastmail", "omarchy-toggle-scratch fastmail")
|
|
o.bind("SUPER + CTRL + ALT + M", "Beeper", "omarchy-toggle-scratch beeper")
|
|
o.bind("SUPER + CTRL + H", "Google Home", "omarchy-toggle-scratch home")
|
|
-- YouTube is a floating pinned window in the bottom-left corner (window rule
|
|
-- in hypr/hyprland.lua). The summon launches it when no window exists,
|
|
-- otherwise carries the window to the focused monitor and focuses it.
|
|
o.bind("SUPER + CTRL + ALT + Y", "YouTube", "omarchy-summon youtube") -- was: web apps cycle
|
|
hl.unbind("SUPER + SHIFT + Y") -- was: YouTube via the desktop entry (new window every press)
|
|
o.bind("SUPER + SHIFT + Y", "YouTube", "omarchy-summon youtube")
|
|
|
|
hl.unbind("SUPER + CTRL + ALT + T") -- was: show time
|
|
-- Pattern is the exact class from `hyprctl clients`; the window rule in
|
|
-- hypr/hyprland.lua sends it to workspace 5 on eDP-1 fullscreen.
|
|
o.bind("SUPER + CTRL + ALT + T", "tldraw offline", "omarchy-launch-or-focus tldraw-offline \"gtk-launch tldraw-offline\"")
|
|
|
|
hl.unbind("SUPER + CTRL + ALT + W") -- was: toggle weather
|
|
-- gtk-launch takes the desktop-entry id: TLDraw.desktop, not tldraw_offline,
|
|
-- which does not exist (the old command could only focus, never launch).
|
|
|
|
hl.unbind("SUPER + SHIFT + M") -- was: Spotify
|
|
hl.unbind("SUPER + SHIFT + G") -- was: Signal
|
|
-- The ssh/mosh command and tmux keymaps live in ~/.config/kitty/goliath.conf
|
|
-- (kitty cannot take --config from a `launch` action, hence a separate kitty
|
|
-- process, same trick as SUPER + T / SUPER + A). kitty-goliath is the app-id
|
|
-- looknfeel.lua's terminal blur rule expects.
|
|
o.bind("SUPER + SHIFT + G", "Goliath", { launch = "kitty --class=kitty-goliath --title=goliath --config ~/.config/kitty/goliath.conf" })
|
|
|
|
-- ─── Screenshots ────────────────────────────────────────────────────────────
|
|
|
|
hl.unbind("SUPER + S") -- was: toggle scratchpad (now SUPER + CTRL + S)
|
|
hl.unbind("SUPER + SHIFT + S") -- was: Google Maps
|
|
o.bind("SUPER + S", "Screenshot region", "omarchy-capture-screenshot region")
|
|
o.bind("SUPER + SHIFT + S", "Screenshot window", "omarchy-capture-screenshot windows")
|
|
-- Monitor capture moved off SUPER + CTRL + S when the scratchpad took that key.
|
|
o.bind("SUPER + SHIFT + ALT + S", "Screenshot monitor", "omarchy-capture-screenshot fullscreen")
|
|
|
|
-- ─── Session ────────────────────────────────────────────────────────────────
|
|
|
|
hl.unbind("SUPER + SHIFT + E") -- was: Email (Fastmail is on SUPER + CTRL + ALT + E)
|
|
o.bind("SUPER + SHIFT + E", "System menu", "omarchy-menu toggle system")
|
|
o.bind("SUPER + SHIFT + Q", "System menu", "omarchy-menu toggle system")
|
|
|
|
-- ─── Dictation ──────────────────────────────────────────────────────────────
|
|
|
|
o.bind("SUPER + F24", "Dictate to Hermes", "voxtype record toggle --profile hermes")
|
|
|
|
-- ─── Menus, in place of vicinae ─────────────────────────────────────────────
|
|
-- Same keys as the niri config, pointed at the Omarchy menus.
|
|
|
|
-- Mod+Space (vicinae toggle) needs no line: SUPER + SPACE is already the
|
|
-- Omarchy menu. SUPER + ALT + SPACE is the apps menu.
|
|
|
|
hl.unbind("SUPER + SHIFT + SPACE") -- was: toggle top bar
|
|
hl.unbind("SUPER + P") -- was: pseudo window
|
|
hl.unbind("SUPER + SPACE") -- was: pseudo window
|
|
|
|
-- Omarchy's SUPER + SPACE is ALT + SPACE, so the key is free in Omarchy.
|
|
-- o.bind("ALT + SPACE", "Toggle Launcher", "omarchy-menu toggle launcher")
|
|
|
|
-- Mod+Shift+Return was the vicinae window switcher. ScrollOverview on
|
|
-- ALT + TAB below takes that job, so SUPER + SHIFT + RETURN keeps its default
|
|
-- job of opening the browser.
|
|
--
|
|
-- flea --default: begin. Written by `flea --default`; `flea --default off` removes the block whole.
|
|
hl.unbind("SUPER + SHIFT + F")
|
|
o.bind("SUPER + SHIFT + F", "File manager", { launch = 'flea --gui' })
|
|
hl.unbind("SUPER + ALT + SHIFT + F")
|
|
o.bind("SUPER + ALT + SHIFT + F", "File manager (cwd)", { launch = 'flea --gui "$(omarchy-cmd-terminal-cwd)"' })
|
|
-- flea --default: end.
|
|
|
|
-- flea --picker: begin. Written by `flea --picker`; `flea --picker off` removes the block whole.
|
|
o.window("com.thisisgm.flea.picker", { tag = "+floating-window" })
|
|
-- flea --picker: end.
|
|
|
|
-- niri-style: SUPER + F maximizes (Omarchy's SUPER + ALT + F "Full width"),
|
|
-- SUPER + SHIFT + F goes true fullscreen. Kept below the flea blocks so it
|
|
-- wins over the file-manager bind `flea --default` writes onto
|
|
-- SUPER + SHIFT + F; flea stays on SUPER + ALT + SHIFT + F, Nautilus on
|
|
-- SUPER + CTRL + ALT + F.
|
|
hl.unbind("SUPER + F") -- was: full screen
|
|
o.bind("SUPER + F", "Maximize", hl.dsp.window.fullscreen({ mode = "maximized" }))
|
|
hl.unbind("SUPER + SHIFT + F") -- was: file manager (flea --gui)
|
|
o.bind("SUPER + SHIFT + F", "Full screen", hl.dsp.window.fullscreen({ mode = "fullscreen" }))
|