* Keep overlays sharp without parked surfaces * Refresh overlay monitor scale after live changes * Reset menu layout when its monitor changes
90 lines
3.3 KiB
QML
90 lines
3.3 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Hyprland
|
|
import Quickshell.Wayland
|
|
|
|
// Map a fresh fullscreen surface on each open. Keep its first frames
|
|
// transparent until Qt receives the output's scale, so it opens sharp without
|
|
// retaining a parked surface that can be closed when a monitor disconnects.
|
|
PanelWindow {
|
|
id: window
|
|
|
|
// Whether the overlay is showing. Drive this instead of visible.
|
|
property bool shown: false
|
|
property int shownLayer: WlrLayer.Overlay
|
|
property int shownKeyboardFocus: WlrKeyboardFocus.Exclusive
|
|
|
|
property var targetScreen: null
|
|
readonly property var targetMonitor: {
|
|
var monitors = Hyprland.monitors.values
|
|
for (var i = 0; i < monitors.length; i++) {
|
|
if (targetScreen && monitors[i].name === targetScreen.name) return monitors[i]
|
|
}
|
|
return null
|
|
}
|
|
|
|
// Wait for fullscreen geometry as well as scale so no smaller opening frame
|
|
// is stretched across the output. Fractional scale uses units of 1/120;
|
|
// compare in those units to tolerate floating point rounding.
|
|
readonly property bool contentReady: shown && backingWindowVisible && !!targetScreen
|
|
&& width === targetScreen.width && height === targetScreen.height
|
|
&& !!targetMonitor && Math.round(devicePixelRatio * 120) === Math.round(targetMonitor.scale * 120)
|
|
property bool contentRevealed: false
|
|
|
|
onContentReadyChanged: {
|
|
contentRevealed = false
|
|
// Resize and scale notifications arrive while Qt is still updating the
|
|
// window. Reveal on the next turn, once its content layout has caught up.
|
|
if (contentReady) Qt.callLater(function() {
|
|
if (window.contentReady) window.contentRevealed = true
|
|
})
|
|
}
|
|
|
|
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 Quickshell.screens.length > 0 ? Quickshell.screens[0] : null
|
|
}
|
|
|
|
// Live monitor scaling does not emit a Hyprland monitor event, so its cached
|
|
// scale can lag behind Qt. Refresh on opening and on scale changes while open.
|
|
onShownChanged: {
|
|
targetScreen = shown ? focusedScreen() : null
|
|
if (shown) Hyprland.refreshMonitors()
|
|
}
|
|
onDevicePixelRatioChanged: if (shown) Hyprland.refreshMonitors()
|
|
|
|
Connections {
|
|
target: Quickshell
|
|
function onScreensChanged() {
|
|
if (window.shown && Quickshell.screens.indexOf(window.targetScreen) < 0) {
|
|
window.targetScreen = null
|
|
// Let the removed output finish closing its old window before mapping
|
|
// a replacement, rather than recreating it during Qt's teardown.
|
|
Qt.callLater(function() {
|
|
if (window.shown && !window.targetScreen) window.targetScreen = window.focusedScreen()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
visible: shown && !!targetScreen && !!targetMonitor
|
|
screen: targetScreen
|
|
anchors { top: true; left: true; bottom: true; right: true }
|
|
color: "transparent"
|
|
exclusionMode: ExclusionMode.Ignore
|
|
WlrLayershell.layer: shownLayer
|
|
WlrLayershell.keyboardFocus: shownKeyboardFocus
|
|
|
|
// Opacity preserves keyboard handling while the scale arrives, including
|
|
// search keystrokes and Escape pressed immediately after opening.
|
|
Binding {
|
|
target: window.contentItem
|
|
property: "opacity"
|
|
value: window.contentRevealed ? 1 : 0
|
|
}
|
|
}
|