From 84de3d3a2ee8746b7d2c339745f230fdcb7f5852 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 14 May 2026 12:51:03 +0200 Subject: [PATCH] Extract more helpers --- config/hypr/hyprland.lua | 2 +- config/hypr/input.lua | 4 ++-- default/hypr/apps/1password.lua | 3 +-- default/hypr/apps/bitwarden.lua | 10 ++++----- default/hypr/apps/browser.lua | 25 ++++++++------------- default/hypr/apps/davinci-resolve.lua | 4 ++-- default/hypr/apps/geforce.lua | 6 +---- default/hypr/apps/jetbrains.lua | 6 +---- default/hypr/apps/localsend.lua | 5 ++--- default/hypr/apps/moonlight.lua | 7 +----- default/hypr/apps/pip.lua | 20 +++++++++-------- default/hypr/apps/qemu.lua | 3 +-- default/hypr/apps/retroarch.lua | 10 +++++---- default/hypr/apps/steam.lua | 12 ++++------ default/hypr/apps/system.lua | 32 ++++++++++----------------- default/hypr/apps/telegram.lua | 2 +- default/hypr/apps/terminals.lua | 5 ++--- default/hypr/apps/typora.lua | 4 ++-- default/hypr/apps/webcam-overlay.lua | 14 +++++++----- default/hypr/helpers.lua | 14 ++++++++++++ default/hypr/windows.lua | 6 ++--- themes/kanagawa/hyprland.lua | 2 +- 22 files changed, 90 insertions(+), 106 deletions(-) diff --git a/config/hypr/hyprland.lua b/config/hypr/hyprland.lua index 0fdaf400..6836e3cc 100644 --- a/config/hypr/hyprland.lua +++ b/config/hypr/hyprland.lua @@ -21,4 +21,4 @@ require("hypr.autostart") require("default.hypr.toggles") -- Add any other personal Hyprland configuration below. --- hl.window_rule({ match = { class = "qemu" }, workspace = "5" }) +-- o.window("qemu", { workspace = "5" }) diff --git a/config/hypr/input.lua b/config/hypr/input.lua index d3ef0d20..03e32bde 100644 --- a/config/hypr/input.lua +++ b/config/hypr/input.lua @@ -43,8 +43,8 @@ hl.config({ }) -- Scroll nicely in the terminal. -hl.window_rule({ match = { class = "(Alacritty|kitty|foot)" }, scroll_touchpad = 1.5 }) -hl.window_rule({ match = { class = "com.mitchellh.ghostty" }, scroll_touchpad = 0.2 }) +o.window("(Alacritty|kitty|foot)", { scroll_touchpad = 1.5 }) +o.window("com.mitchellh.ghostty", { scroll_touchpad = 0.2 }) -- Enable touchpad gestures for changing workspaces. -- See https://wiki.hypr.land/Configuring/Advanced-and-Cool/Gestures/ diff --git a/default/hypr/apps/1password.lua b/default/hypr/apps/1password.lua index 0cb083b4..d57bf651 100644 --- a/default/hypr/apps/1password.lua +++ b/default/hypr/apps/1password.lua @@ -1,2 +1 @@ -hl.window_rule({ match = { class = "^(1[p|P]assword)$" }, no_screen_share = true }) -hl.window_rule({ match = { class = "^(1[p|P]assword)$" }, tag = "+floating-window" }) +o.window("^(1[p|P]assword)$", { no_screen_share = true, tag = "+floating-window" }) diff --git a/default/hypr/apps/bitwarden.lua b/default/hypr/apps/bitwarden.lua index 817d78ab..ec2e7bf6 100644 --- a/default/hypr/apps/bitwarden.lua +++ b/default/hypr/apps/bitwarden.lua @@ -1,6 +1,6 @@ -hl.window_rule({ match = { class = "^(Bitwarden)$" }, no_screen_share = true }) -hl.window_rule({ match = { class = "^(Bitwarden)$" }, tag = "+floating-window" }) +o.window("^(Bitwarden)$", { no_screen_share = true, tag = "+floating-window" }) --- Bitwarden Chrome Extension. -hl.window_rule({ match = { class = "chrome-nngceckbapebfimnlniiiahkandclblb-Default" }, no_screen_share = true }) -hl.window_rule({ match = { class = "chrome-nngceckbapebfimnlniiiahkandclblb-Default" }, tag = "+floating-window" }) +o.window("chrome-nngceckbapebfimnlniiiahkandclblb-Default", { + no_screen_share = true, + tag = "+floating-window", +}) diff --git a/default/hypr/apps/browser.lua b/default/hypr/apps/browser.lua index 4fc73a33..06bb1905 100644 --- a/default/hypr/apps/browser.lua +++ b/default/hypr/apps/browser.lua @@ -1,19 +1,12 @@ --- Browser types. -hl.window_rule({ match = { class = "((google-)?[cC]hrom(e|ium)|[bB]rave-browser|[mM]icrosoft-edge|Vivaldi-stable|helium)" }, tag = "+chromium-based-browser" }) -hl.window_rule({ match = { class = "([fF]irefox|zen|librewolf)" }, tag = "+firefox-based-browser" }) -hl.window_rule({ match = { tag = "chromium-based-browser" }, tag = "-default-opacity" }) -hl.window_rule({ match = { tag = "firefox-based-browser" }, tag = "-default-opacity" }) +-- Browser tags and styling. +o.window("((google-)?[cC]hrom(e|ium)|[bB]rave-browser|[mM]icrosoft-edge|Vivaldi-stable|helium)", { tag = "+chromium-based-browser" }) +o.window("([fF]irefox|zen|librewolf)", { tag = "+firefox-based-browser" }) +o.window({ tag = "chromium-based-browser" }, { tag = "-default-opacity", tile = true, opacity = "1.0 0.97" }) +o.window({ tag = "firefox-based-browser" }, { tag = "-default-opacity", opacity = "1.0 0.97" }) -- Video apps: remove chromium browser tag so they don't get opacity applied. -hl.window_rule({ match = { class = "(chrome-youtube.com__-Default|chrome-app.zoom.us__wc_home-Default)" }, tag = "-chromium-based-browser" }) -hl.window_rule({ match = { class = "(chrome-youtube.com__-Default|chrome-app.zoom.us__wc_home-Default)" }, tag = "-default-opacity" }) +o.window("(chrome-youtube.com__-Default|chrome-app.zoom.us__wc_home-Default)", { tag = "-chromium-based-browser" }) +o.window("(chrome-youtube.com__-Default|chrome-app.zoom.us__wc_home-Default)", { tag = "-default-opacity" }) --- Force chromium-based browsers into a tile to deal with --app bug. -hl.window_rule({ match = { tag = "chromium-based-browser" }, tile = true }) - --- Only a subtle opacity change, but not for video sites. -hl.window_rule({ match = { tag = "chromium-based-browser" }, opacity = "1.0 0.97" }) -hl.window_rule({ match = { tag = "firefox-based-browser" }, opacity = "1.0 0.97" }) - --- Hide the screen-sharing notification bar (the "Hide" button on it is broken on Wayland). -hl.window_rule({ match = { title = ".*is sharing.*" }, workspace = "special silent" }) +-- Hide screen sharing notification windows. +o.window({ title = ".*is sharing.*" }, { workspace = "special silent" }) diff --git a/default/hypr/apps/davinci-resolve.lua b/default/hypr/apps/davinci-resolve.lua index 637d51bd..ba81e12c 100644 --- a/default/hypr/apps/davinci-resolve.lua +++ b/default/hypr/apps/davinci-resolve.lua @@ -1,2 +1,2 @@ --- Focus floating DaVinci Resolve dialog windows. -hl.window_rule({ match = { class = ".*[Rr]esolve.*", float = true }, stay_focused = true }) +-- DaVinci Resolve dialog focus handling. +o.window(".*[Rr]esolve.*", { float = true, stay_focused = true }) diff --git a/default/hypr/apps/geforce.lua b/default/hypr/apps/geforce.lua index feb7e885..d89db006 100644 --- a/default/hypr/apps/geforce.lua +++ b/default/hypr/apps/geforce.lua @@ -1,5 +1 @@ -hl.window_rule({ - name = "geforce", - match = { class = "GeForceNOW" }, - idle_inhibit = "fullscreen", -}) +o.window("GeForceNOW", { idle_inhibit = "fullscreen" }) diff --git a/default/hypr/apps/jetbrains.lua b/default/hypr/apps/jetbrains.lua index f48d150f..678e823e 100644 --- a/default/hypr/apps/jetbrains.lua +++ b/default/hypr/apps/jetbrains.lua @@ -1,6 +1,2 @@ -- Disable mouse focus (see https://github.com/basecamp/omarchy/pull/5183#issuecomment-4189299971). -hl.window_rule({ - name = "jetbrains-focus", - match = { class = "^(jetbrains-.*)$" }, - no_follow_mouse = true, -}) +o.window("^(jetbrains-.*)$", { no_follow_mouse = true }) diff --git a/default/hypr/apps/localsend.lua b/default/hypr/apps/localsend.lua index e670fd7f..6e7ec132 100644 --- a/default/hypr/apps/localsend.lua +++ b/default/hypr/apps/localsend.lua @@ -1,4 +1,3 @@ -- Float LocalSend and fzf file picker. -hl.window_rule({ match = { class = "(Share|localsend)" }, float = true }) -hl.window_rule({ match = { class = "(Share|localsend)" }, center = true }) -hl.window_rule({ match = { class = "localsend" }, size = { 1100, 700 } }) +o.window("(Share|localsend)", { float = true, center = true }) +o.window("localsend", { size = { 1100, 700 } }) diff --git a/default/hypr/apps/moonlight.lua b/default/hypr/apps/moonlight.lua index 2774d77c..d7f3489e 100644 --- a/default/hypr/apps/moonlight.lua +++ b/default/hypr/apps/moonlight.lua @@ -1,6 +1 @@ -hl.window_rule({ - name = "moonlight", - match = { class = "com.moonlight_stream.Moonlight" }, - fullscreen = true, - idle_inhibit = "fullscreen", -}) +o.window("com.moonlight_stream.Moonlight", { fullscreen = true, idle_inhibit = "fullscreen" }) diff --git a/default/hypr/apps/pip.lua b/default/hypr/apps/pip.lua index 6158fa9b..d7d4e12f 100644 --- a/default/hypr/apps/pip.lua +++ b/default/hypr/apps/pip.lua @@ -1,10 +1,12 @@ -- Picture-in-picture overlays. -hl.window_rule({ match = { title = "(Picture.?in.?[Pp]icture)" }, tag = "+pip" }) -hl.window_rule({ match = { tag = "pip" }, tag = "-default-opacity" }) -hl.window_rule({ match = { tag = "pip" }, float = true }) -hl.window_rule({ match = { tag = "pip" }, pin = true }) -hl.window_rule({ match = { tag = "pip" }, size = { 600, 338 } }) -hl.window_rule({ match = { tag = "pip" }, keep_aspect_ratio = true }) -hl.window_rule({ match = { tag = "pip" }, border_size = 0 }) -hl.window_rule({ match = { tag = "pip" }, opacity = "1 1" }) -hl.window_rule({ match = { tag = "pip" }, move = { "(monitor_w-window_w-40)", "(monitor_h*0.04)" } }) +o.window({ title = "(Picture.?in.?[Pp]icture)" }, { tag = "+pip" }) +o.window({ tag = "pip" }, { + tag = "-default-opacity", + float = true, + pin = true, + size = { 600, 338 }, + keep_aspect_ratio = true, + border_size = 0, + opacity = "1 1", + move = { "(monitor_w-window_w-40)", "(monitor_h*0.04)" }, +}) diff --git a/default/hypr/apps/qemu.lua b/default/hypr/apps/qemu.lua index e9334c1c..c919bcec 100644 --- a/default/hypr/apps/qemu.lua +++ b/default/hypr/apps/qemu.lua @@ -1,2 +1 @@ -hl.window_rule({ match = { class = "qemu" }, tag = "-default-opacity" }) -hl.window_rule({ match = { class = "qemu" }, opacity = "1 1" }) +o.window("qemu", { tag = "-default-opacity", opacity = "1 1" }) diff --git a/default/hypr/apps/retroarch.lua b/default/hypr/apps/retroarch.lua index 01b3f8b5..0bc8a218 100644 --- a/default/hypr/apps/retroarch.lua +++ b/default/hypr/apps/retroarch.lua @@ -1,4 +1,6 @@ -hl.window_rule({ match = { class = "com.libretro.RetroArch" }, fullscreen = true }) -hl.window_rule({ match = { class = "com.libretro.RetroArch" }, tag = "-default-opacity" }) -hl.window_rule({ match = { class = "com.libretro.RetroArch" }, opacity = "1 1" }) -hl.window_rule({ match = { class = "com.libretro.RetroArch" }, idle_inhibit = "fullscreen" }) +o.window("com.libretro.RetroArch", { + fullscreen = true, + tag = "-default-opacity", + opacity = "1 1", + idle_inhibit = "fullscreen", +}) diff --git a/default/hypr/apps/steam.lua b/default/hypr/apps/steam.lua index 05bb75d8..152aff64 100644 --- a/default/hypr/apps/steam.lua +++ b/default/hypr/apps/steam.lua @@ -1,8 +1,4 @@ --- Float Steam. -hl.window_rule({ match = { class = "steam" }, float = true }) -hl.window_rule({ match = { class = "steam", title = "Steam" }, center = true }) -hl.window_rule({ match = { class = "steam.*" }, tag = "-default-opacity" }) -hl.window_rule({ match = { class = "steam.*" }, opacity = "1 1" }) -hl.window_rule({ match = { class = "steam", title = "Steam" }, size = { 1100, 700 } }) -hl.window_rule({ match = { class = "steam", title = "Friends List" }, size = { 460, 800 } }) -hl.window_rule({ match = { class = "steam" }, idle_inhibit = "fullscreen" }) +o.window("steam", { float = true, idle_inhibit = "fullscreen" }) +o.window({ class = "steam", title = "Steam" }, { center = true, size = { 1100, 700 } }) +o.window("steam.*", { tag = "-default-opacity", opacity = "1 1" }) +o.window({ class = "steam", title = "Friends List" }, { size = { 460, 800 } }) diff --git a/default/hypr/apps/system.lua b/default/hypr/apps/system.lua index 9ce1fcd1..2dcb6198 100644 --- a/default/hypr/apps/system.lua +++ b/default/hypr/apps/system.lua @@ -1,26 +1,18 @@ --- Floating windows. -hl.window_rule({ match = { tag = "floating-window" }, float = true }) -hl.window_rule({ match = { tag = "floating-window" }, center = true }) -hl.window_rule({ match = { tag = "floating-window" }, size = { 875, 600 } }) +o.window({ tag = "floating-window" }, { float = true, center = true, size = { 875, 600 } }) -hl.window_rule({ match = { class = "(org.omarchy.bluetui|org.omarchy.impala|org.omarchy.wiremix|org.omarchy.btop|org.omarchy.terminal|org.omarchy.bash|org.codeberg.dnkl.foot|org.gnome.NautilusPreviewer|org.gnome.Evince|com.gabm.satty|Omarchy|About|TUI.float|imv|mpv)" }, tag = "+floating-window" }) -hl.window_rule({ match = { class = "(xdg-desktop-portal-gtk|sublime_text|DesktopEditors|org.gnome.Nautilus)", title = "^(Open.*Files?|Open [F|f]older.*|Save.*Files?|Save.*As|Save|All Files|.*wants to [open|save].*|[C|c]hoose.*)" }, tag = "+floating-window" }) -hl.window_rule({ match = { class = "org.gnome.Calculator" }, float = true }) +o.window("(org.omarchy.bluetui|org.omarchy.impala|org.omarchy.wiremix|org.omarchy.btop|org.omarchy.terminal|org.omarchy.bash|org.codeberg.dnkl.foot|org.gnome.NautilusPreviewer|org.gnome.Evince|com.gabm.satty|Omarchy|About|TUI.float|imv|mpv)", { tag = "+floating-window" }) +o.window({ class = "(xdg-desktop-portal-gtk|sublime_text|DesktopEditors|org.gnome.Nautilus)", title = "^(Open.*Files?|Open [F|f]older.*|Save.*Files?|Save.*As|Save|All Files|.*wants to [open|save].*|[C|c]hoose.*)" }, { tag = "+floating-window" }) +o.window("org.gnome.Calculator", { float = true }) --- Fullscreen screensaver. -hl.window_rule({ match = { class = "org.omarchy.screensaver" }, fullscreen = true }) -hl.window_rule({ match = { class = "org.omarchy.screensaver" }, float = true }) -hl.window_rule({ match = { class = "org.omarchy.screensaver" }, animation = "slide" }) +-- Screen saver should always cover the screen and not be tiled. +o.window("org.omarchy.screensaver", { fullscreen = true, float = true, animation = "slide" }) --- No transparency on media windows. -hl.window_rule({ match = { class = "^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$" }, tag = "-default-opacity" }) -hl.window_rule({ match = { class = "^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$" }, opacity = "1 1" }) +-- Media/image/video apps should be opaque. +o.window("^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$", { tag = "-default-opacity", opacity = "1 1" }) --- Popped window rounding. -hl.window_rule({ match = { tag = "pop" }, rounding = 8 }) +-- Common app-controlled tags. +o.window({ tag = "pop" }, { rounding = 8 }) +o.window({ tag = "noidle" }, { idle_inhibit = "always" }) --- Prevent idle while open. -hl.window_rule({ match = { tag = "noidle" }, idle_inhibit = "always" }) - --- Image selector. +-- Disable animations for image selector overlay. hl.layer_rule({ match = { namespace = "omarchy-image-selector" }, no_anim = true }) diff --git a/default/hypr/apps/telegram.lua b/default/hypr/apps/telegram.lua index e1509516..ff0f03a6 100644 --- a/default/hypr/apps/telegram.lua +++ b/default/hypr/apps/telegram.lua @@ -1,2 +1,2 @@ -- Prevent Telegram from stealing focus on new messages. -hl.window_rule({ match = { class = "org.telegram.desktop" }, focus_on_activate = false }) +o.window("org.telegram.desktop", { focus_on_activate = false }) diff --git a/default/hypr/apps/terminals.lua b/default/hypr/apps/terminals.lua index 0a1d403e..4d90a636 100644 --- a/default/hypr/apps/terminals.lua +++ b/default/hypr/apps/terminals.lua @@ -1,4 +1,3 @@ -- Define terminal tag to style them uniformly. -hl.window_rule({ match = { class = "(Alacritty|kitty|com.mitchellh.ghostty|foot)" }, tag = "+terminal" }) -hl.window_rule({ match = { tag = "terminal" }, tag = "-default-opacity" }) -hl.window_rule({ match = { tag = "terminal" }, opacity = "0.97 0.9" }) +o.window("(Alacritty|kitty|com.mitchellh.ghostty|foot)", { tag = "+terminal" }) +o.window({ tag = "terminal" }, { tag = "-default-opacity", opacity = "0.97 0.9" }) diff --git a/default/hypr/apps/typora.lua b/default/hypr/apps/typora.lua index a8af36a3..4249db4c 100644 --- a/default/hypr/apps/typora.lua +++ b/default/hypr/apps/typora.lua @@ -1,2 +1,2 @@ --- Float Typora print dialog. -hl.window_rule({ match = { class = "^Typora$", title = "^Print$" }, float = true, center = true }) +-- Typora print dialog. +o.window({ class = "^Typora$", title = "^Print$" }, { float = true, center = true }) diff --git a/default/hypr/apps/webcam-overlay.lua b/default/hypr/apps/webcam-overlay.lua index 42f7acde..3e92ae8e 100644 --- a/default/hypr/apps/webcam-overlay.lua +++ b/default/hypr/apps/webcam-overlay.lua @@ -1,6 +1,8 @@ --- Webcam overlay for screen recording. -hl.window_rule({ match = { title = "WebcamOverlay" }, float = true }) -hl.window_rule({ match = { title = "WebcamOverlay" }, pin = true }) -hl.window_rule({ match = { title = "WebcamOverlay" }, no_initial_focus = true }) -hl.window_rule({ match = { title = "WebcamOverlay" }, no_dim = true }) -hl.window_rule({ match = { title = "WebcamOverlay" }, move = { "(monitor_w-window_w-40)", "(monitor_h-window_h-40)" } }) +-- Webcam overlay window. +o.window({ title = "WebcamOverlay" }, { + float = true, + pin = true, + no_initial_focus = true, + no_dim = true, + move = { "(monitor_w-window_w-40)", "(monitor_h-window_h-40)" }, +}) diff --git a/default/hypr/helpers.lua b/default/hypr/helpers.lua index 977bc320..ba570ca2 100644 --- a/default/hypr/helpers.lua +++ b/default/hypr/helpers.lua @@ -65,3 +65,17 @@ end function o.notify(message) return "notify-send -u low " .. shell_quote(message) end + +function o.window(match, rules) + rules.match = rules.match or {} + + if type(match) == "string" then + rules.match.class = match + else + for key, value in pairs(match) do + rules.match[key] = value + end + end + + hl.window_rule(rules) +end diff --git a/default/hypr/windows.lua b/default/hypr/windows.lua index 342ddbcb..d2dd282c 100644 --- a/default/hypr/windows.lua +++ b/default/hypr/windows.lua @@ -1,9 +1,9 @@ -- See https://wiki.hypr.land/Configuring/Basics/Window-Rules/ -hl.window_rule({ match = { class = ".*" }, suppress_event = "maximize" }) +o.window(".*", { suppress_event = "maximize" }) -- Tag all windows for default opacity (apps can override with -default-opacity tag). -hl.window_rule({ match = { class = ".*" }, tag = "+default-opacity" }) +o.window(".*", { tag = "+default-opacity" }) -- Fix some dragging issues with XWayland. hl.window_rule({ @@ -22,4 +22,4 @@ hl.window_rule({ require("default.hypr.apps") -- Apply default opacity after apps have had a chance to opt out. -hl.window_rule({ match = { tag = "default-opacity" }, opacity = "0.97 0.9" }) +o.window({ tag = "default-opacity" }, { opacity = "0.97 0.9" }) diff --git a/themes/kanagawa/hyprland.lua b/themes/kanagawa/hyprland.lua index b03145b6..5b619e0c 100644 --- a/themes/kanagawa/hyprland.lua +++ b/themes/kanagawa/hyprland.lua @@ -15,4 +15,4 @@ hl.config({ }) -- Kanagawa backdrop is too strong for default opacity. -hl.window_rule({ match = { tag = "terminal" }, opacity = "0.98 0.95" }) +o.window({ tag = "terminal" }, { opacity = "0.98 0.95" })