Seeding is deliberately conservative: a role whose preferred applications are all missing is left alone rather than forced. That is right, and it is also silent -- so a curated handler nobody installs presents as the machine quietly going back to deciding defaults by installation order, which is the problem seeding exists to fix. None of Loupe, Papers, Decibels, Nautilus, mpv or sushi was declared anywhere. Preview works, on org.gnome.NautilusPreviewer2 rather than the interface its bus name suggests, but it opened tiled -- shoving the file manager aside for something meant to be an overlay -- so it gets a float and center rule sized to leave the file underneath visible. xdg-utils and desktop-file-utils were undeclared too; seeding from link-dotfiles started calling them, and the dependency contract said so. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
220 lines
8.0 KiB
Lua
220 lines
8.0 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 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,
|
|
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
|