Keep overlay surfaces mapped so they open sharp (#13419)

The menu, emoji picker, clipboard, OSD, reminder flow and Wi-Fi QR code
mapped a fresh surface on every open. Qt drew its first frames before
Hyprland sent the surface's fractional scale: the pixel ratio stepped
2, 1, then 1.6, so on a 1.6 display each overlay showed blurry for
~350ms before going sharp.

A new OverlayWindow keeps the surface. Hidden, it parks as a 1x1,
input-less layer below windows, off the overlay layer so it never
blocks direct scanout. Showing only resizes and raises it, so the scale
is already settled. Content stays hidden until the surface has grown,
so no 1x1 frame is stretched across the screen, and the window follows
the focused monitor each time it is shown. The image picker's own
fullscreen parking moves onto it too.

Each parked overlay keeps a Qt window alive: at rest the shell holds
~40 MiB more RSS and ~20 MiB more GPU memory.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier HanssonandClaude Opus 5.5 authored and GitHub committed 2026-09-27 18:03:01 +02:00
1 parent 6762769bbf
commit 48de7823da
14 files changed
+164 -96

No files matched your search

+2 -7
View File
@@ -311,15 +311,10 @@ Item {
}
}
PanelWindow {
OverlayWindow {
id: panel
visible: root.opened
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
shown: root.opened
WlrLayershell.namespace: "omarchy-clipboard"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.fill: parent
+2 -7
View File
@@ -157,15 +157,10 @@ Item {
path: root.omarchyPath + "/shell/plugins/emojis/emojis.json"
onLoaded: root.loadEmojis(text())
}
PanelWindow {
OverlayWindow {
id: panel
visible: root.opened
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
shown: root.opened
WlrLayershell.namespace: "omarchy-emojis"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.fill: parent
+4 -30
View File
@@ -1,11 +1,11 @@
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
import qs.Commons
import qs.Ui
import "ImagePickerModel.js" as ImagePickerModel
Item {
@@ -36,9 +36,6 @@ Item {
property string themeRows: ""
property bool themeMode: false
property bool themeOpenPending: false
// The surface outlives each open, so it no longer lands on the focused
// output by itself. Unset until the first open lets the compositor choose.
property var targetScreen: null
// Bound to the central [image-picker] section in shell.toml via Color.qml.
// `dimColor` tints unselected slices and text outlines on top of the scrim;
// it intentionally tracks the foundational background, not a surface role.
@@ -234,7 +231,6 @@ Item {
requestSerial += 1
themeMode = false
themeOpenPending = false
targetScreen = focusedScreen() || targetScreen
imageDirs = nextImageDirs
imageRows = nextImageRows
@@ -278,14 +274,6 @@ Item {
property var imageArray: []
function focusedScreen() {
var monitor = Hyprland.focusedMonitor
var name = monitor ? String(monitor.name || "") : ""
for (var i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name === name) return Quickshell.screens[i]
}
return null
}
function currentThemePreview() {
var name = String(themeNameFile.text() || "").trim()
@@ -456,25 +444,11 @@ Item {
onExited: root.releaseNextDoneFile()
}
PanelWindow {
OverlayWindow {
id: panel
// Stay mapped between opens. A fresh surface renders its first frames
// before the compositor sends its fractional scale, so the picker flashed
// blurry, and it had to re-upload every thumbnail texture. Closed, it
// waits transparent and input-less on the bottom layer: anything left on
// the overlay layer would block direct scanout for fullscreen apps.
visible: true
screen: root.targetScreen
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
mask: root.opened ? null : closedMask
shown: root.opened
shownKeyboardFocus: root.imagesLoaded ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
WlrLayershell.namespace: "omarchy-image-selector"
WlrLayershell.layer: root.opened ? WlrLayer.Overlay : WlrLayer.Bottom
WlrLayershell.keyboardFocus: root.opened && root.imagesLoaded ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
Region { id: closedMask }
Rectangle {
anchors.fill: parent
+5 -9
View File
@@ -1065,15 +1065,10 @@ Item {
if (root.guardsPending) Qt.callLater(function() { root.evaluateGuards() })
}
}
PanelWindow {
OverlayWindow {
id: panel
visible: root.opened && root.rowsLoaded
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
shown: root.opened && root.rowsLoaded
WlrLayershell.namespace: "omarchy-menu"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
// The card opens centered exactly as always. The first search keystroke
// or submenu move freezes the top line where it currently sits — from
@@ -1086,12 +1081,13 @@ Item {
readonly property int centeredTop: Math.max(Style.gapsOut, Math.round((height - root.cardHeight) / 2))
readonly property int effectiveCardTop: cardTop >= 0 ? cardTop : centeredTop
function freezeCardTop() {
if (visible && cardTop < 0) {
if (shown && cardTop < 0) {
cardTop = effectiveCardTop
maxRowsHeight = root.visibleRowsHeight
}
}
onVisibleChanged: if (!visible) { cardTop = -1; maxRowsHeight = -1 }
// The surface stays mapped between opens, so closing is shown going false.
onShownChanged: if (!shown) { cardTop = -1; maxRowsHeight = -1 }
Rectangle {
anchors.fill: parent
+3 -7
View File
@@ -123,15 +123,11 @@ Item {
function ping(): string { return "ok" }
}
PanelWindow {
OverlayWindow {
id: panel
visible: root.opened
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
shown: root.opened
shownKeyboardFocus: WlrKeyboardFocus.None
WlrLayershell.namespace: "omarchy-osd"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
// Visual-only surface: keep the layer-shell input region empty so the OSD
// never blocks clicks to the desktop below it.
mask: Region {}
+2 -7
View File
@@ -210,14 +210,9 @@ Item {
}
}
PanelWindow {
visible: root.opened
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
exclusionMode: ExclusionMode.Ignore
OverlayWindow {
shown: root.opened
WlrLayershell.namespace: "omarchy-network-qr"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
// Deep scrim: the floating code needs the backdrop to carry the contrast
// on any wallpaper.
+2 -7
View File
@@ -92,15 +92,10 @@ Item {
}
}
PanelWindow {
OverlayWindow {
id: panel
visible: root.opened
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
shown: root.opened
WlrLayershell.namespace: "omarchy-reminders"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.fill: parent