208 lines
7.5 KiB
Lua
208 lines
7.5 KiB
Lua
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- 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 centre, 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,
|
|
})
|
|
|
|
-- 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,
|
|
ignore_alpha = 0.3,
|
|
})
|
|
|
|
-- Popovers: calendar, quick settings, notification centre, 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
|