diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index b0b4e0a15..7156ddb49 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -2735,6 +2735,35 @@ Singleton { return edges; } + function getOverlayBarEdgesForScreen(screen) { + if (!screen) + return []; + var edges = []; + for (var i = 0; i < barConfigs.length; i++) { + var bc = barConfigs[i]; + if (!bc.enabled || !(bc.useOverlayLayer ?? false)) + continue; + var prefs = bc.screenPreferences || ["all"]; + if (!prefs.includes("all") && !isScreenInPreferences(screen, prefs)) + continue; + switch (bc.position ?? 0) { + case SettingsData.Position.Top: + edges.push("top"); + break; + case SettingsData.Position.Bottom: + edges.push("bottom"); + break; + case SettingsData.Position.Left: + edges.push("left"); + break; + case SettingsData.Position.Right: + edges.push("right"); + break; + } + } + return edges; + } + function frameEdgeInsetForSide(screen, side) { if (!frameEnabled || !screen) return 0; diff --git a/quickshell/Modules/DankBar/DankBar.qml b/quickshell/Modules/DankBar/DankBar.qml index d563d313c..a504df08b 100644 --- a/quickshell/Modules/DankBar/DankBar.qml +++ b/quickshell/Modules/DankBar/DankBar.qml @@ -173,8 +173,9 @@ Item { if (base.length === 0 && root.barConfig?.showOnLastDisplay && Quickshell.screens.length === 1) base = Quickshell.screens; } - // Connected frame mode renders the bar inside the frame surface; skip the standalone window there. - return base.filter(screen => !CompositorService.frameHostsSurfacesForScreen(screen)); + // Connected frame mode renders the bar inside the frame surface; skip the standalone window there + // unless this bar wants the overlay layer, which the frame surface cannot provide. + return base.filter(screen => !CompositorService.frameHostsBarForConfig(screen, root.barConfig)); } delegate: DankBarWindow { diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml index 1615883ce..c96423590 100644 --- a/quickshell/Modules/DankBar/DankBarContent.qml +++ b/quickshell/Modules/DankBar/DankBarContent.qml @@ -38,8 +38,9 @@ Item { readonly property real _barInsetPaddingRaw: SettingsData.barInsetPaddingSyncAll ? SettingsData.barInsetPaddingShared : (barConfig?.barInsetPadding ?? -1) readonly property real _barInsetPaddingAuto: _barIsVertical ? Theme.spacingXS : _edgeBaseMargin readonly property real _barInsetPadding: _barInsetPaddingRaw < 0 ? _barInsetPaddingAuto : _barInsetPaddingRaw - // Connected-frame Bar Inset Padding: absolute free-end inset (auto < 0 = frameThickness, 0 = edge-to-edge). - // FrameExclusions already moves a free bar end inward by frameThickness so use frame inset to mangage the gap + // Connected-frame Bar Inset Padding: absolute free-end gap the hosted bar spans full-width into + // (auto < 0 = frameThickness so widgets align with the interior cutout, 0 = edge-to-edge). The extra + // beyond frameThickness is what an adjacent bar end adds on top of its corner alignment. readonly property real _frameInsetResolved: SettingsData.frameBarInsetPadding < 0 ? SettingsData.frameThickness : SettingsData.frameBarInsetPadding readonly property real _frameInsetExtra: Math.max(0, _frameInsetResolved - SettingsData.frameThickness) @@ -53,28 +54,28 @@ Item { if (_barIsVertical) return _edgeBaseMargin; if (_usesFrameBarChrome) - return hasAdjacentLeftBarLive ? _cornerAlignInset : _frameInsetExtra; + return hasAdjacentLeftBarLive ? _cornerAlignInset : _frameInsetResolved; return Math.max(0, _barInsetPadding); } readonly property real _rightMargin: { if (_barIsVertical) return _edgeBaseMargin; if (_usesFrameBarChrome) - return hasAdjacentRightBarLive ? _cornerAlignInset : _frameInsetExtra; + return hasAdjacentRightBarLive ? _cornerAlignInset : _frameInsetResolved; return Math.max(0, _barInsetPadding); } readonly property real _topMargin: { if (!_barIsVertical) return 0; if (_usesFrameBarChrome) - return hasAdjacentTopBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetExtra; + return hasAdjacentTopBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetResolved; return Math.max(0, _barInsetPadding); } readonly property real _bottomMargin: { if (!_barIsVertical) return 0; if (_usesFrameBarChrome) - return hasAdjacentBottomBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetExtra; + return hasAdjacentBottomBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetResolved; return Math.max(0, _barInsetPadding); } diff --git a/quickshell/Modules/DankBar/DankBarHoverController.qml b/quickshell/Modules/DankBar/DankBarHoverController.qml index c213ca0ae..1ca49326e 100644 --- a/quickshell/Modules/DankBar/DankBarHoverController.qml +++ b/quickshell/Modules/DankBar/DankBarHoverController.qml @@ -930,7 +930,8 @@ Item { if (_barHovered) return; const excludedBar = _barExitPending ? barWindow : null; - if (cursorOverHoverChain(gx, gy, excludedBar)) + const staleOverExitedBar = excludedBar?.containsGlobalPoint?.(gx, gy, 0) ?? false; + if (!staleOverExitedBar && cursorOverHoverChain(gx, gy, excludedBar)) return; _barExitPending = false; closeHoverSurfaces(); diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index acc0bb07c..d8eea0167 100644 --- a/quickshell/Modules/Dock/Dock.qml +++ b/quickshell/Modules/Dock/Dock.qml @@ -9,7 +9,7 @@ import qs.Widgets Variants { id: dockVariants // Connected frame mode renders the dock inside the frame surface; skip the standalone window there. - model: SettingsData.getFilteredScreens("dock").filter(screen => !CompositorService.frameHostsSurfacesForScreen(screen)) + model: SettingsData.getFilteredScreens("dock").filter(screen => !CompositorService.frameHostsDockForScreen(screen)) property var contextMenu property var trashContextMenu diff --git a/quickshell/Modules/Frame/FrameBarHost.qml b/quickshell/Modules/Frame/FrameBarHost.qml index c0d1a46a4..398940eae 100644 --- a/quickshell/Modules/Frame/FrameBarHost.qml +++ b/quickshell/Modules/Frame/FrameBarHost.qml @@ -23,7 +23,7 @@ Item { const configs = SettingsData.barConfigs || []; for (let i = 0; i < configs.length; i++) { const bc = configs[i]; - if (!bc.enabled) + if (!bc.enabled || (bc.useOverlayLayer ?? false)) continue; const prefs = bc.screenPreferences || ["all"]; if (!prefs.includes("all") && !SettingsData.isScreenInPreferences(host.targetScreen, prefs)) diff --git a/quickshell/Modules/Frame/FrameExclusions.qml b/quickshell/Modules/Frame/FrameExclusions.qml index 7d8664119..1729a02c2 100644 --- a/quickshell/Modules/Frame/FrameExclusions.qml +++ b/quickshell/Modules/Frame/FrameExclusions.qml @@ -16,6 +16,12 @@ Scope { return SettingsData.getActiveBarEdgesForScreen(screen); } + // Overlay-layer bars stay standalone even in connected mode and reserve their own edge. + readonly property var overlayBarEdges: { + SettingsData.barConfigs; + return SettingsData.getOverlayBarEdgesForScreen(root.screen); + } + // One thin invisible PanelWindow per edge. A standalone bar reserves its own edge, so // that edge is skipped — unless the frame hosts the bar (connected mode), where no bar // window exists and the exclusion must reserve the full bar thickness itself. @@ -23,13 +29,17 @@ Scope { readonly property bool screenEnabled: CompositorService.frameWindowVisibleForScreen(root.screen) readonly property bool hostsBar: CompositorService.frameHostsSurfacesForScreen(root.screen) + function hostsBandForEdge(edge) { + return root.hostsBar && !root.overlayBarEdges.includes(edge); + } + function exclusionSizeForEdge(edge) { return root.barEdges.includes(edge) ? SettingsData.frameBarSize : SettingsData.frameThickness; } Loader { readonly property bool isBarEdge: root.barEdges.includes("top") - active: root.screenEnabled && (!isBarEdge || root.hostsBar) + active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("top")) sourceComponent: EdgeExclusion { targetScreen: root.screen exclusionSize: root.exclusionSizeForEdge("top") @@ -41,7 +51,7 @@ Scope { Loader { readonly property bool isBarEdge: root.barEdges.includes("bottom") - active: root.screenEnabled && (!isBarEdge || root.hostsBar) + active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("bottom")) sourceComponent: EdgeExclusion { targetScreen: root.screen exclusionSize: root.exclusionSizeForEdge("bottom") @@ -53,7 +63,7 @@ Scope { Loader { readonly property bool isBarEdge: root.barEdges.includes("left") - active: root.screenEnabled && (!isBarEdge || root.hostsBar) + active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("left")) sourceComponent: EdgeExclusion { targetScreen: root.screen exclusionSize: root.exclusionSizeForEdge("left") @@ -65,7 +75,7 @@ Scope { Loader { readonly property bool isBarEdge: root.barEdges.includes("right") - active: root.screenEnabled && (!isBarEdge || root.hostsBar) + active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("right")) sourceComponent: EdgeExclusion { targetScreen: root.screen exclusionSize: root.exclusionSizeForEdge("right") diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index bb6da59ad..1cfd58cf4 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -86,7 +86,7 @@ PanelWindow { readonly property bool _connectedActive: CompositorService.usesConnectedFrameChromeForScreen(win.targetScreen) readonly property bool _dockHostedHere: { - if (!win._connectedActive || !SettingsData.showDock) + if (!win._connectedActive || !SettingsData.showDock || SettingsData.dockUseOverlayLayer) return false; const screens = SettingsData.getFilteredScreens("dock"); for (let i = 0; i < screens.length; i++) { diff --git a/quickshell/Services/CompositorService.qml b/quickshell/Services/CompositorService.qml index 76818c845..ccfecbf95 100644 --- a/quickshell/Services/CompositorService.qml +++ b/quickshell/Services/CompositorService.qml @@ -694,6 +694,16 @@ Singleton { return FrameTransitionState.effectiveConnectedFrameModeActive && frameConfiguredForScreen(screenOrName); } + // A surface set to the overlay layer cannot live inside the frame's single top-layer window, + // so it stays a standalone window (which resolves itself onto the overlay layer) even in connected mode. + function frameHostsBarForConfig(screenOrName, barConfig) { + return frameHostsSurfacesForScreen(screenOrName) && !(barConfig?.useOverlayLayer ?? false); + } + + function frameHostsDockForScreen(screenOrName) { + return frameHostsSurfacesForScreen(screenOrName) && !SettingsData.dockUseOverlayLayer; + } + function framePeerSurfacesUseOverlayForScreen(screenOrName) { return frameWindowVisibleForScreen(screenOrName); }