-- ───────────────────────────────────────────────────────────────────────────── -- Window, workspace and layer rules -- -- Rules evaluate top to bottom, last match wins -- but every NAMED rule is -- evaluated before every anonymous one, so a named rule can never override an -- anonymous rule further down. Names are used sparingly here for that reason. -- -- Matching is Google RE2 (no backtracking). Prefix a pattern with "negative:" -- to invert it. -- ───────────────────────────────────────────────────────────────────────────── -- ── Upstream sanity rules ─────────────────────────────────────────────────── hl.window_rule({ name = "suppress-maximize-events", match = { class = ".*" }, suppress_event = "maximize", }) hl.window_rule({ name = "fix-xwayland-drags", match = { class = "^$", title = "^$", xwayland = true, float = true, fullscreen = false, pin = false, }, no_focus = true, }) -- ── Floating apps ─────────────────────────────────────────────────────────── -- Carried over from the Forge windows.json override list, minus entries for -- apps that are no longer installed. hl.window_rule({ match = { class = "^(jetbrains-toolbox|zoom|Cider|Hidamari|com\\.mattjakeman\\.ExtensionManager)$", }, float = true, }) hl.window_rule({ match = { class = "^([Bb]itwarden|com\\.bitwarden\\.desktop)$" }, float = true, }) hl.window_rule({ match = { class = "^(com\\.nextcloud\\.desktopclient\\.nextcloud|Nextcloud)$" }, float = true, }) hl.window_rule({ match = { class = "^(org\\.gnome\\.Calculator|gnome-calculator)$" }, float = true, size = { 400, 600 }, center = true, }) hl.window_rule({ match = { class = "^(mpv|io\\.mpv\\.Mpv)$" }, float = true, -- Video should never be blurred or dimmed behind another window. no_blur = true, no_dim = true, }) hl.window_rule({ match = { class = "^(com\\.spotify\\.Client|Spotify)$" }, float = true, }) -- Settings-style utility windows and pickers: float and center, like GNOME did. hl.window_rule({ match = { class = "^(pavucontrol|org\\.pulseaudio\\.pavucontrol|nm-connection-editor|blueman-manager|org\\.gnome\\.Settings)$" }, float = true, size = { "monitor_w * 0.45", "monitor_h * 0.6" }, center = true, }) -- Quick Look. The GNOME previewer is what the file manager opens on space, -- and it is an overlay rather than a window someone manages: tiled, it shoves -- the file manager aside and has to be dismissed before the list is usable -- again. Sized generously because a preview that needs zooming is not a -- preview; it still gets a margin so the file underneath stays visible. hl.window_rule({ match = { class = "^org\\.gnome\\.NautilusPreviewer$" }, float = true, size = { "monitor_w * 0.7", "monitor_h * 0.8" }, center = true, }) -- Portal dialogs (file chooser, screen share picker) should always float. hl.window_rule({ match = { class = "^(xdg-desktop-portal-gtk|org\\.freedesktop\\.impl\\.portal\\.desktop\\.gtk|hyprland-share-picker)$" }, float = true, center = true, }) hl.window_rule({ match = { title = "^(Open File|Save File|Save As|Open Folder|Select a File|Choose Files)$" }, float = true, center = true, }) -- Polkit prompt. hl.window_rule({ match = { class = "^(hyprpolkitagent|polkit-gnome-authentication-agent-1)$" }, float = true, center = true, }) -- ── Gaming ────────────────────────────────────────────────────────────────── -- content = "game" is the keystone: misc.vrr = 3, render.direct_scanout = 2 and -- cursor.no_break_fs_vrr = 2 all key off it. Blur, animation, shadow and dim are -- all disabled so the compositor gets out of the way entirely. hl.window_rule({ match = { class = "^(steam_app_\\d+|gamescope|lutris|net\\.lutris\\.Lutris|com\\.heroicgameslauncher\\.hgl|Minecraft.*|moonlight|com\\.moonlight_stream\\.Moonlight)$" }, content = "game", immediate = true, no_blur = true, no_anim = true, no_shadow = true, no_dim = true, }) -- Steam itself is a normal window, but its transient popups are a mess. hl.window_rule({ match = { class = "^steam$", title = "^(Friends List|Steam Settings|Special Offer.*)$" }, float = true, }) -- Fullscreen video in a browser also benefits from no blur / no dim. hl.window_rule({ match = { fullscreen = true }, no_blur = true, no_dim = true, }) -- ── Workspace rules ───────────────────────────────────────────────────────── -- Deliberately NO "smart gaps". -- -- The usual trick is to strip gaps, border and rounding when a workspace holds -- a single tiled window (`workspace = "w[tv1]"` + matching window rules). It -- was here and it was wrong for this setup: the common case is one window on a -- workspace, so the desktop spent most of its time square-cornered and -- edge-to-edge -- the opposite of the intended look. Gaps and rounding are -- constant now, however many windows are open. -- ── Layer rules (Quickshell surfaces) ─────────────────────────────────────── -- Namespaces are set in QML via WlrLayershell.namespace; keep these regexes and -- those strings in sync. `hyprctl layers` lists what is actually live. -- -- The bar intentionally has no rule: it is a transparent edge-to-edge input -- surface, not glass. Its individual controls paint their own hover feedback. -- Keeping a full-width blur rule here would spend compositor work on pixels -- the bar does not draw. hl.layer_rule({ name = "qs-dock", match = { namespace = "^qs-dock$" }, blur = true, -- The dock's context menu and its window previews are xdg-popups of this -- surface, not layers of their own, so they are only blurred if the rule -- says to blur the popups too. Without it they are flat panes over the -- wallpaper while everything else on the desktop is glass. blur_popups = true, ignore_alpha = 0.3, }) -- Popovers: calendar, quick settings, notification center, tray menus. hl.layer_rule({ name = "qs-popover", match = { namespace = "^qs-popover" }, blur = true, blur_popups = true, ignore_alpha = 0.2, }) -- Overview, capture and local screen-reading UI dim the desktop behind them. hl.layer_rule({ name = "qs-overlay", match = { namespace = "^qs-(overview|capture|screen-intelligence)$" }, blur = true, ignore_alpha = 0.4, dim_around = true, no_screen_share = true, }) -- Notification toasts. Blurred like every other shell surface -- without this -- the cards are a near-transparent fill sitting directly on the wallpaper and -- read as washed out rather than as glass. -- -- no_anim because the toasts animate themselves in QML, and no_screen_share so -- they don't leak into OBS or Sunshine streams. hl.layer_rule({ name = "qs-notifications", match = { namespace = "^qs-notifications$" }, blur = true, ignore_alpha = 0.15, no_anim = true, no_screen_share = true, }) -- Signal Glass provides its own event-driven motion, so the compositor only -- supplies the material blur and privacy behavior. hl.layer_rule({ name = "qs-signal-glass", match = { namespace = "^qs-signal-glass$" }, blur = true, ignore_alpha = 0.15, no_anim = true, no_screen_share = true, }) -- The launcher blurs and dims whatever is behind it. hl.layer_rule({ name = "launcher", match = { namespace = "^(vicinae|wofi)$" }, blur = true, ignore_alpha = 0.5, dim_around = true, }) return true