Desktop & Dock becomes Shell — Bar, Dock, Control Center, Tiling, Workspaces — the home for everything Quickshell draws. The settings- management cluster moves to System as Sync & Backup, Appearance's Shell tab dissolves, and 24-hour time finally lives on Date & Time, which always owned it. The bar gets what it never had: a way to survive the wallpaper. A second neutral text family (follow theme, or forced light or dark), a one-layer shadow under every glyph, and a gradient scrim for wallpapers nothing else survives — all off by default, pixel-identical until asked. Widgets earn toggles (weather, media, clipboard, calendar countdown), the vitals cluster stops leaving a dead pill behind, and Control Center's sections learn to step aside. The dock graduates from MVP: a context menu with window rows, pin, unpin, quit and new-window; scroll an icon to cycle its windows; drag to reorder on the dock itself; hover previews with one-shot captures; and "Add App to Dock" in the launcher. Three real bugs died en route — menus that slid away with the autohide, a readonly-property crash on every menu open, and a drag that drifted half a slot per icon on side docks. The pinned-apps editor in Settings becomes a drag strip. 166 contracts; the full suite is green except two live display and switcher tests that cannot run behind a locked session — re-verified on unlock. Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
225 lines
8.4 KiB
Lua
225 lines
8.4 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,
|
|
-- 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
|