From 463476384027ba16a497df1798fca4191183ae70 Mon Sep 17 00:00:00 2001 From: purian23 Date: Tue, 19 May 2026 18:42:45 -0400 Subject: [PATCH] refactor(fullscreen): Refine fullscreen layering and frame overlay behavior - Replaced fullscreen hide/reveal toggles with Show Over Fullscreen layer toggles - Added Launcher opt to Show Over Fullscreen setting - Kept fullscreen stacking compositor-owned via top/overlay layer choices - Fixed Hyrland Special Workspaces - Updated DMS Advanced Configuration docs --- quickshell/Common/SettingsData.qml | 4 +- quickshell/Common/settings/SettingsSpec.js | 5 +- .../Modals/Common/DankModalConnected.qml | 8 +- .../DankLauncherV2/DankLauncherV2Modal.qml | 2 + .../DankLauncherV2ModalConnected.qml | 45 ++-- .../DankLauncherV2ModalSpotlight.qml | 29 +- .../DankLauncherV2ModalStandalone.qml | 37 +-- quickshell/Modules/DankBar/BarCanvas.qml | 53 +++- quickshell/Modules/DankBar/DankBarContent.qml | 19 +- quickshell/Modules/DankBar/DankBarWindow.qml | 52 ++-- .../Modules/DankBar/Widgets/NotepadButton.qml | 21 +- .../Modules/DankBar/Widgets/SystemTrayBar.qml | 4 +- quickshell/Modules/Dock/Dock.qml | 142 +++------- quickshell/Modules/Dock/DockApps.qml | 1 + quickshell/Modules/Dock/DockGeometry.qml | 22 +- .../Modules/Dock/DockLauncherButton.qml | 2 +- quickshell/Modules/Frame/FrameExclusions.qml | 3 +- quickshell/Modules/Frame/FrameWindow.qml | 7 +- .../Notifications/Popup/NotificationPopup.qml | 14 +- .../Popup/NotificationPopupManager.qml | 2 +- quickshell/Modules/Plugins/BasePill.qml | 13 + quickshell/Modules/Settings/DankBarTab.qml | 11 +- quickshell/Modules/Settings/DockTab.qml | 12 +- quickshell/Modules/Settings/LauncherTab.qml | 9 + quickshell/Services/CompositorService.qml | 248 +++++++++++++++++- quickshell/Services/PopoutService.qml | 27 +- quickshell/Widgets/DankPopout.qml | 35 ++- quickshell/Widgets/DankPopoutConnected.qml | 110 ++++---- quickshell/Widgets/DankPopoutStandalone.qml | 36 +-- quickshell/Widgets/DankSlideout.qml | 3 +- .../translations/settings_search_index.json | 143 +++++++--- 31 files changed, 748 insertions(+), 371 deletions(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index b964a2dc..f4e3b340 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -449,6 +449,7 @@ Singleton { property bool dankLauncherV2UnloadOnClose: false property bool dankLauncherV2IncludeFilesInAll: false property bool dankLauncherV2IncludeFoldersInAll: false + property bool launcherShowOverFullscreen: false property string launcherStyle: "full" property string _legacyWeatherLocation: "New York, NY" @@ -606,7 +607,7 @@ Singleton { property bool showDock: false property bool dockAutoHide: false property bool dockSmartAutoHide: false - property bool dockHideOnFullscreen: true + property bool dockShowOverFullscreen: false property bool dockGroupByApp: false property bool dockRestoreSpecialWorkspaceOnClick: false property bool dockOpenOnOverview: false @@ -787,6 +788,7 @@ Singleton { "popupGapsAuto": true, "popupGapsManual": 4, "maximizeDetection": true, + "showOverFullscreen": false, "scrollEnabled": true, "scrollXBehavior": "column", "scrollYBehavior": "workspace", diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index f9938106..ef1560d8 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -213,6 +213,7 @@ var SPEC = { dankLauncherV2UnloadOnClose: { def: false }, dankLauncherV2IncludeFilesInAll: { def: false }, dankLauncherV2IncludeFoldersInAll: { def: false }, + launcherShowOverFullscreen: { def: false }, launcherStyle: { def: "full" }, useAutoLocation: { def: false }, @@ -332,7 +333,7 @@ var SPEC = { showDock: { def: false }, dockAutoHide: { def: false }, dockSmartAutoHide: { def: false }, - dockHideOnFullscreen: { def: true }, + dockShowOverFullscreen: { def: false }, dockGroupByApp: { def: false }, dockRestoreSpecialWorkspaceOnClick: { def: false }, dockOpenOnOverview: { def: false }, @@ -496,7 +497,7 @@ var SPEC = { popupGapsAuto: true, popupGapsManual: 4, maximizeDetection: true, - fullscreenDetection: true, + showOverFullscreen: false, scrollEnabled: true, scrollXBehavior: "column", scrollYBehavior: "workspace", diff --git a/quickshell/Modals/Common/DankModalConnected.qml b/quickshell/Modals/Common/DankModalConnected.qml index 82d5cb31..79afe204 100644 --- a/quickshell/Modals/Common/DankModalConnected.qml +++ b/quickshell/Modals/Common/DankModalConnected.qml @@ -38,7 +38,7 @@ Item { readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : "" - readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== "" && !allowStacking + readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== "" && !allowStacking && CompositorService.usesConnectedFrameChromeForScreen(effectiveScreen) function _dockOccupiesSide(side) { if (!SettingsData.showDock) @@ -58,7 +58,7 @@ Item { readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome && _dockOccupiesSide(resolvedConnectedBarSide) - readonly property bool connectedMotionParity: Theme.isConnectedEffect + readonly property bool connectedMotionParity: frameOwnsConnectedChrome property int animationDuration: connectedMotionParity ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration property real animationScaleCollapsed: Theme.effectScaleCollapsed property real animationOffset: Theme.effectAnimOffset @@ -68,7 +68,7 @@ Item { property color borderColor: Theme.outlineMedium property real borderWidth: 0 property real cornerRadius: Theme.cornerRadius - readonly property bool connectedSurfaceOverride: Theme.isConnectedEffect + readonly property bool connectedSurfaceOverride: frameOwnsConnectedChrome readonly property color effectiveBackgroundColor: connectedSurfaceOverride ? Theme.connectedSurfaceColor : backgroundColor readonly property color effectiveBorderColor: connectedSurfaceOverride ? "transparent" : borderColor readonly property real effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth @@ -346,7 +346,7 @@ Item { readonly property real shadowFallbackOffset: 6 readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0 readonly property real shadowMotionPadding: { - if (Theme.isConnectedEffect) + if (frameOwnsConnectedChrome) return 0; if (animationType === "slide") return 30; diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml index 98f61fcd..257157ca 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml @@ -23,6 +23,7 @@ Item { readonly property bool frameOwnsConnectedChrome: impl.item ? (impl.item.frameOwnsConnectedChrome ?? false) : false readonly property string resolvedConnectedBarSide: impl.item ? (impl.item.resolvedConnectedBarSide ?? "") : "" readonly property bool launcherArcExtenderActive: impl.item ? (impl.item.launcherArcExtenderActive ?? false) : false + property bool triggerUsesOverlayLayer: false signal dialogClosed @@ -116,6 +117,7 @@ Item { if (!it) return; it.modalHandle = root; + it.triggerUsesOverlayLayer = Qt.binding(() => root.triggerUsesOverlayLayer); } Connections { diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml index 0e93e1e0..c376dea0 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml @@ -13,13 +13,14 @@ Item { readonly property var log: Log.scoped("DankLauncherV2ModalConnected") property var modalHandle: root + property bool triggerUsesOverlayLayer: false visible: false property bool spotlightOpen: false property bool keyboardActive: false property bool contentVisible: false - readonly property bool launcherMotionVisible: Theme.isConnectedEffect ? _motionActive : (Theme.isDirectionalEffect ? spotlightOpen : _motionActive) + readonly property bool launcherMotionVisible: frameOwnsConnectedChrome ? _motionActive : (Theme.isDirectionalEffect ? spotlightOpen : _motionActive) property var spotlightContent: launcherContentLoader.item property bool openedFromOverview: false property bool isClosing: false @@ -40,6 +41,21 @@ Item { readonly property real screenWidth: effectiveScreen?.width ?? 1920 readonly property real screenHeight: effectiveScreen?.height ?? 1080 readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1 + readonly property bool usesOverlayLayer: SettingsData.launcherShowOverFullscreen || triggerUsesOverlayLayer + readonly property var effectiveLauncherLayer: { + switch (Quickshell.env("DMS_MODAL_LAYER")) { + case "bottom": + log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "background": + log.error("'background' layer is not valid for modals. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "overlay": + return WlrLayershell.Overlay; + default: + return root.usesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top; + } + } readonly property int baseWidth: { switch (SettingsData.dankLauncherV2Size) { @@ -74,7 +90,7 @@ Item { readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : "" - readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== "" + readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== "" && CompositorService.usesConnectedFrameChromeForScreen(effectiveScreen) readonly property bool launcherArcExtenderActive: frameOwnsConnectedChrome && SettingsData.frameLauncherArcExtender && (resolvedConnectedBarSide === "top" || resolvedConnectedBarSide === "bottom") function _dockOccupiesSide(side) { @@ -140,10 +156,10 @@ Item { readonly property real modalX: frameOwnsConnectedChrome ? _connectedModalPos.x : ((screenWidth - modalWidth) / 2) readonly property real modalY: frameOwnsConnectedChrome ? _connectedModalPos.y : ((screenHeight - modalHeight) / 2) - readonly property bool connectedSurfaceOverride: Theme.isConnectedEffect - readonly property int launcherAnimationDuration: Theme.isConnectedEffect ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration - readonly property list launcherEnterCurve: Theme.isConnectedEffect ? Theme.variantPopoutEnterCurve : Theme.variantModalEnterCurve - readonly property list launcherExitCurve: Theme.isConnectedEffect ? Theme.variantPopoutExitCurve : Theme.variantModalExitCurve + readonly property bool connectedSurfaceOverride: frameOwnsConnectedChrome + readonly property int launcherAnimationDuration: frameOwnsConnectedChrome ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration + readonly property list launcherEnterCurve: frameOwnsConnectedChrome ? Theme.variantPopoutEnterCurve : Theme.variantModalEnterCurve + readonly property list launcherExitCurve: frameOwnsConnectedChrome ? Theme.variantPopoutExitCurve : Theme.variantModalExitCurve readonly property color backgroundColor: connectedSurfaceOverride ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) readonly property real cornerRadius: connectedSurfaceOverride ? Theme.connectedSurfaceRadius : Theme.cornerRadius readonly property color borderColor: { @@ -596,7 +612,7 @@ Item { readonly property real _rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0) WlrLayershell.namespace: "dms:spotlight:bg" - WlrLayershell.layer: WlrLayershell.Top + WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: WlrKeyboardFocus.None @@ -669,20 +685,7 @@ Item { } WlrLayershell.namespace: "dms:spotlight" - WlrLayershell.layer: { - switch (Quickshell.env("DMS_MODAL_LAYER")) { - case "bottom": - log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "background": - log.error("'background' layer is not valid for modals. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "overlay": - return WlrLayershell.Overlay; - default: - return WlrLayershell.Top; - } - } + WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml index 3a08ccf3..73f00b95 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml @@ -11,6 +11,7 @@ Item { readonly property var log: Log.scoped("DankLauncherV2ModalSpotlight") property var modalHandle: root + property bool triggerUsesOverlayLayer: false visible: false @@ -29,13 +30,28 @@ Item { readonly property real screenWidth: effectiveScreen?.width ?? 1920 readonly property real screenHeight: effectiveScreen?.height ?? 1080 readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1 + readonly property bool usesOverlayLayer: SettingsData.launcherShowOverFullscreen || triggerUsesOverlayLayer + readonly property var effectiveLauncherLayer: { + switch (Quickshell.env("DMS_MODAL_LAYER")) { + case "bottom": + log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "background": + log.error("'background' layer is not valid for modals. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "overlay": + return WlrLayershell.Overlay; + default: + return root.usesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top; + } + } readonly property int _openDuration: 80 readonly property int _closeDuration: 70 readonly property int _motionDuration: 90 // Connected frame mode clamps the centered surface inside frame insets. - readonly property bool frameConnected: SettingsData.connectedFrameModeActive && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences) + readonly property bool frameConnected: CompositorService.usesConnectedFrameChromeForScreen(effectiveScreen) function _frameEdgeInset(side) { if (!effectiveScreen || !frameConnected) @@ -263,7 +279,7 @@ Item { color: "transparent" WlrLayershell.namespace: "dms:spotlight:clickcatcher" - WlrLayershell.layer: WlrLayershell.Top + WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: WlrKeyboardFocus.None @@ -324,14 +340,7 @@ Item { } WlrLayershell.namespace: "dms:spotlight" - WlrLayershell.layer: { - switch (Quickshell.env("DMS_MODAL_LAYER")) { - case "overlay": - return WlrLayershell.Overlay; - default: - return WlrLayershell.Top; - } - } + WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml index a9c2192e..c0b51b0a 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml @@ -11,6 +11,7 @@ Item { readonly property var log: Log.scoped("DankLauncherV2ModalStandalone") property var modalHandle: root + property bool triggerUsesOverlayLayer: false visible: false @@ -31,7 +32,7 @@ Item { readonly property real screenHeight: effectiveScreen?.height ?? 1080 readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1 - readonly property bool frameOwnsConnectedChrome: SettingsData.connectedFrameModeActive && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences) + readonly property bool frameOwnsConnectedChrome: CompositorService.usesConnectedFrameChromeForScreen(effectiveScreen) readonly property string resolvedConnectedBarSide: frameOwnsConnectedChrome ? (SettingsData.frameLauncherEmergeSide || "bottom") : "" readonly property int baseWidth: { @@ -79,6 +80,21 @@ Item { readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground + readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherShowOverFullscreen || triggerUsesOverlayLayer + readonly property var effectiveLauncherLayer: { + switch (Quickshell.env("DMS_MODAL_LAYER")) { + case "bottom": + log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "background": + log.error("'background' layer is not valid for modals. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "overlay": + return WlrLayershell.Overlay; + default: + return root.usesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top; + } + } readonly property real cornerRadius: Theme.cornerRadius readonly property color borderColor: { if (!SettingsData.dankLauncherV2BorderEnabled) @@ -301,7 +317,7 @@ Item { updatesEnabled: root.useBackgroundDarken && (spotlightOpen || isClosing) WlrLayershell.namespace: "dms:spotlight:clickcatcher" - WlrLayershell.layer: WlrLayershell.Top + WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: WlrKeyboardFocus.None @@ -378,22 +394,7 @@ Item { } WlrLayershell.namespace: "dms:spotlight" - WlrLayershell.layer: { - if (root.useBackgroundDarken) - return WlrLayershell.Overlay; - switch (Quickshell.env("DMS_MODAL_LAYER")) { - case "bottom": - log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "background": - log.error("'background' layer is not valid for modals. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "overlay": - return WlrLayershell.Overlay; - default: - return WlrLayershell.Top; - } - } + WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None diff --git a/quickshell/Modules/DankBar/BarCanvas.qml b/quickshell/Modules/DankBar/BarCanvas.qml index 04d6feb7..f9afb938 100644 --- a/quickshell/Modules/DankBar/BarCanvas.qml +++ b/quickshell/Modules/DankBar/BarCanvas.qml @@ -10,13 +10,15 @@ Item { required property var axis required property var barConfig - visible: !SettingsData.frameEnabled + readonly property bool frameShapesBar: SettingsData.frameEnabled && barWindow.usesConnectedFrameChrome + + visible: !frameShapesBar anchors.fill: parent anchors.left: parent.left anchors.top: parent.top - readonly property bool gothEnabled: (barConfig?.gothCornersEnabled ?? false) && !barWindow.hasMaximizedToplevel + readonly property bool gothEnabled: (barConfig?.gothCornersEnabled ?? false) && !(barWindow.flattenForMaximizedWindow && barWindow.hasMaximizedToplevel) anchors.leftMargin: -(gothEnabled && axis.isVertical && axis.edge === "right" ? barWindow._wingR : 0) anchors.rightMargin: -(gothEnabled && axis.isVertical && axis.edge === "left" ? barWindow._wingR : 0) anchors.topMargin: -(gothEnabled && !axis.isVertical && axis.edge === "bottom" ? barWindow._wingR : 0) @@ -39,11 +41,11 @@ Item { } property real rt: { - if (SettingsData.frameEnabled) + if (frameShapesBar) return SettingsData.frameRounding; if (barConfig?.squareCorners ?? false) return 0; - if (barWindow.hasMaximizedToplevel) + if (barWindow.flattenForMaximizedWindow && barWindow.hasMaximizedToplevel) return 0; return Theme.cornerRadius; } @@ -113,9 +115,32 @@ Item { readonly property real shadowOffsetX: Theme.elevationOffsetXFor(hasPerBarOverride ? null : elevLevel, effectiveShadowDirection, shadowOffsetMagnitude) readonly property real shadowOffsetY: Theme.elevationOffsetYFor(hasPerBarOverride ? null : elevLevel, effectiveShadowDirection, shadowOffsetMagnitude) - readonly property string mainPath: generatePathForPosition(width, height) - readonly property string borderFullPath: generateBorderFullPath(width, height) - readonly property string borderEdgePath: generateBorderEdgePath(width, height) + readonly property string mainPath: { + frameShapesBar; + rt; + wing; + barWindow.flattenForMaximizedWindow; + barWindow.hasMaximizedToplevel; + width; + height; + return generatePathForPosition(width, height); + } + readonly property string borderFullPath: { + frameShapesBar; + rt; + wing; + width; + height; + return generateBorderFullPath(width, height); + } + readonly property string borderEdgePath: { + frameShapesBar; + rt; + wing; + width; + height; + return generateBorderEdgePath(width, height); + } property bool mainPathCorrectShape: false property bool borderFullPathCorrectShape: false property bool borderEdgePathCorrectShape: false @@ -136,6 +161,12 @@ Item { } } + onFrameShapesBarChanged: { + mainPathCorrectShape = false; + borderFullPathCorrectShape = false; + borderEdgePathCorrectShape = false; + } + MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton @@ -259,7 +290,7 @@ Item { h = h - wing; const r = wing; const cr = rt; - const crE = SettingsData.frameEnabled ? 0 : cr; + const crE = frameShapesBar ? 0 : cr; let d = `M ${crE} 0`; d += ` L ${w - crE} 0`; @@ -290,7 +321,7 @@ Item { h = h - wing; const r = wing; const cr = rt; - const crE = SettingsData.frameEnabled ? 0 : cr; + const crE = frameShapesBar ? 0 : cr; let d = `M ${crE} ${fullH}`; d += ` L ${w - crE} ${fullH}`; @@ -320,7 +351,7 @@ Item { w = w - wing; const r = wing; const cr = rt; - const crE = SettingsData.frameEnabled ? 0 : cr; + const crE = frameShapesBar ? 0 : cr; let d = `M 0 ${crE}`; d += ` L 0 ${h - crE}`; @@ -351,7 +382,7 @@ Item { w = w - wing; const r = wing; const cr = rt; - const crE = SettingsData.frameEnabled ? 0 : cr; + const crE = frameShapesBar ? 0 : cr; let d = `M ${fullW} ${crE}`; d += ` L ${fullW} ${h - crE}`; diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml index a9668489..93b03b40 100644 --- a/quickshell/Modules/DankBar/DankBarContent.qml +++ b/quickshell/Modules/DankBar/DankBarContent.qml @@ -24,8 +24,9 @@ Item { readonly property real innerPadding: barConfig?.innerPadding ?? 4 readonly property real outlineThickness: (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0 readonly property real _edgeBaseMargin: Math.max(Theme.spacingXS, innerPadding * 0.8) - readonly property real _frameEdgeFloorInset: SettingsData.frameEnabled ? Math.max(0, SettingsData.frameThickness - _edgeBaseMargin) : 0 readonly property bool _hasBarWindow: barWindow !== undefined && barWindow !== null + readonly property bool _usesConnectedFrameChrome: _hasBarWindow && (barWindow.usesConnectedFrameChrome ?? false) + readonly property real _frameEdgeFloorInset: (SettingsData.frameEnabled && _usesConnectedFrameChrome) ? Math.max(0, SettingsData.frameThickness - _edgeBaseMargin) : 0 readonly property bool _barIsVertical: _hasBarWindow ? barWindow.isVertical : false readonly property string _barScreenName: _hasBarWindow ? (barWindow.screenName || "") : "" readonly property bool hasAdjacentTopBarLive: _hasBarWindow && barWindow.hasAdjacentTopBar @@ -47,22 +48,22 @@ Item { _hadAdjacentRightBar = true readonly property real _frameLeftInset: { - if (!_hasBarWindow || !SettingsData.frameEnabled || _barIsVertical) + if (!_hasBarWindow || !SettingsData.frameEnabled || !_usesConnectedFrameChrome || _barIsVertical) return 0; return hasAdjacentLeftBarLive ? SettingsData.frameBarSize : (_hadAdjacentLeftBar ? _frameEdgeFloorInset : 0); } readonly property real _frameRightInset: { - if (!_hasBarWindow || !SettingsData.frameEnabled || _barIsVertical) + if (!_hasBarWindow || !SettingsData.frameEnabled || !_usesConnectedFrameChrome || _barIsVertical) return 0; return hasAdjacentRightBarLive ? SettingsData.frameBarSize : (_hadAdjacentRightBar ? _frameEdgeFloorInset : 0); } readonly property real _frameTopInset: { - if (!_hasBarWindow || !SettingsData.frameEnabled || !_barIsVertical) + if (!_hasBarWindow || !SettingsData.frameEnabled || !_usesConnectedFrameChrome || !_barIsVertical) return 0; return hasAdjacentTopBarLive ? SettingsData.frameThickness : (_hadAdjacentTopBar ? _frameEdgeFloorInset : 0); } readonly property real _frameBottomInset: { - if (!_hasBarWindow || !SettingsData.frameEnabled || !_barIsVertical) + if (!_hasBarWindow || !SettingsData.frameEnabled || !_usesConnectedFrameChrome || !_barIsVertical) return 0; return hasAdjacentBottomBarLive ? SettingsData.frameThickness : (_hadAdjacentBottomBar ? _frameEdgeFloorInset : 0); } @@ -95,7 +96,7 @@ Item { } Behavior on anchors.leftMargin { - enabled: _animateFrameInsets && SettingsData.frameEnabled + enabled: _animateFrameInsets && _usesConnectedFrameChrome NumberAnimation { duration: Theme.shortDuration easing.type: Easing.OutCubic @@ -103,7 +104,7 @@ Item { } Behavior on anchors.rightMargin { - enabled: _animateFrameInsets && SettingsData.frameEnabled + enabled: _animateFrameInsets && _usesConnectedFrameChrome NumberAnimation { duration: Theme.shortDuration easing.type: Easing.OutCubic @@ -111,7 +112,7 @@ Item { } Behavior on anchors.topMargin { - enabled: _animateFrameInsets && SettingsData.frameEnabled + enabled: _animateFrameInsets && _usesConnectedFrameChrome NumberAnimation { duration: Theme.shortDuration easing.type: Easing.OutCubic @@ -119,7 +120,7 @@ Item { } Behavior on anchors.bottomMargin { - enabled: _animateFrameInsets && SettingsData.frameEnabled + enabled: _animateFrameInsets && _usesConnectedFrameChrome NumberAnimation { duration: Theme.shortDuration easing.type: Easing.OutCubic diff --git a/quickshell/Modules/DankBar/DankBarWindow.qml b/quickshell/Modules/DankBar/DankBarWindow.qml index cf6089cb..43ae8185 100644 --- a/quickshell/Modules/DankBar/DankBarWindow.qml +++ b/quickshell/Modules/DankBar/DankBarWindow.qml @@ -108,6 +108,8 @@ PanelWindow { triggerDashTab(2); } + readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(barWindow.screen) || (barConfig?.showOverFullscreen ?? false) + readonly property var dBarLayer: { switch (Quickshell.env("DMS_DANKBAR_LAYER")) { case "bottom": @@ -119,10 +121,7 @@ PanelWindow { case "top": return WlrLayer.Top; default: - // Elevate to Overlay when Frame is enabled so the bar stays above - // the FrameWindow (WlrLayer.Top) when it is re-mapped on mode switch, - // but drop back to Top while a true fullscreen app owns this screen. - return SettingsData.frameEnabled && !barWindow.hasFullscreenToplevel ? WlrLayer.Overlay : WlrLayer.Top; + return barWindow.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top; } } @@ -152,6 +151,13 @@ PanelWindow { onTriggered: barBlur.rebuild() } + Connections { + target: barWindow + function onUsesConnectedFrameChromeChanged() { + _blurRebuildTimer.restart(); + } + } + Component { id: blurRegionComp Region {} @@ -179,7 +185,7 @@ PanelWindow { // In frame mode, FrameWindow owns the blur region for the entire screen edge // (including the bar area). The bar must not set its own competing blur region // so that frameBlurEnabled acts as the single control for all blur in frame mode. - if (SettingsData.frameEnabled) + if (SettingsData.frameEnabled && barWindow.usesConnectedFrameChrome) return; const widgets = barWindow._blurWidgetItems.filter(w => w && w.visible && w.width > 0 && w.height > 0); @@ -292,7 +298,7 @@ PanelWindow { readonly property color _surfaceContainer: Theme.surfaceContainer readonly property string _barId: barConfig?.id ?? "default" property real _backgroundAlpha: barConfig?.transparency ?? 1.0 - readonly property color _bgColor: SettingsData.frameEnabled ? Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity) : Theme.withAlpha(_surfaceContainer, _backgroundAlpha) + readonly property color _bgColor: (SettingsData.frameEnabled && usesConnectedFrameChrome) ? Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity) : Theme.withAlpha(_surfaceContainer, _backgroundAlpha) function _updateBackgroundAlpha() { const live = SettingsData.barConfigs.find(c => c.id === _barId); @@ -316,16 +322,13 @@ PanelWindow { property string screenName: modelData.name + readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screenName) + + // Flatten/spacing collapse for maximized windows is only for frame-integrated layout. + // When the bar draws its own pill, keep rounded corners and spacing like the dock. + readonly property bool flattenForMaximizedWindow: !SettingsData.frameEnabled || usesConnectedFrameChrome + property bool hasMaximizedToplevel: false - readonly property bool hasFullscreenToplevel: { - if (!(barConfig?.fullscreenDetection ?? true)) - return false; - CompositorService.sortedToplevels; - ToplevelManager.activeToplevel; - if (CompositorService.isNiri) - NiriService.allWorkspaces; - return CompositorService.hasFullscreenToplevelOnScreen(screenName); - } property bool shouldHideForWindows: false function _updateHasMaximizedToplevel() { @@ -427,7 +430,7 @@ PanelWindow { shouldHideForWindows = filtered.length > 0; } - property real effectiveSpacing: SettingsData.frameEnabled ? 0 : (hasMaximizedToplevel ? 0 : (barConfig?.spacing ?? 4)) + property real effectiveSpacing: (SettingsData.frameEnabled && usesConnectedFrameChrome) ? 0 : ((flattenForMaximizedWindow && hasMaximizedToplevel) ? 0 : (barConfig?.spacing ?? 4)) Behavior on effectiveSpacing { enabled: barWindow.visible @@ -438,7 +441,7 @@ PanelWindow { } readonly property int notificationCount: NotificationService.notifications.length - readonly property real effectiveBarThickness: SettingsData.frameEnabled ? SettingsData.frameBarSize : Theme.snap(Math.max(barWindow.widgetThickness + (barConfig?.innerPadding ?? 4) + 4, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))), _dpr) + readonly property real effectiveBarThickness: (SettingsData.frameEnabled && usesConnectedFrameChrome) ? SettingsData.frameBarSize : Theme.snap(Math.max(barWindow.widgetThickness + (barConfig?.innerPadding ?? 4) + 4, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))), _dpr) readonly property bool effectiveOpenOnOverview: SettingsData.frameEnabled ? SettingsData.frameShowOnOverview : (barConfig?.openOnOverview ?? false) readonly property real widgetThickness: Theme.snap(Math.max(20, 26 + (barConfig?.innerPadding ?? 4) * 0.6), _dpr) @@ -636,9 +639,9 @@ PanelWindow { anchors.left: !isVertical ? true : (barPos === SettingsData.Position.Left) anchors.right: !isVertical ? true : (barPos === SettingsData.Position.Right) - readonly property bool reserveExclusiveWhenAutoHidden: SettingsData.connectedFrameModeActive && !!barWindow.screen && SettingsData.isScreenInPreferences(barWindow.screen, SettingsData.frameScreenPreferences) + readonly property bool reserveExclusiveWhenAutoHidden: SettingsData.connectedFrameModeActive && usesConnectedFrameChrome && !!barWindow.screen && SettingsData.isScreenInPreferences(barWindow.screen, SettingsData.frameScreenPreferences) - exclusiveZone: (barWindow.hasFullscreenToplevel || !(barConfig?.visible ?? true) || (topBarCore.autoHide && !barWindow.reserveExclusiveWhenAutoHidden)) ? -1 : (barWindow.effectiveBarThickness + effectiveSpacing + (Theme.isConnectedEffect ? 0 : (barConfig?.bottomGap ?? 0))) + exclusiveZone: (!(barConfig?.visible ?? true) || (topBarCore.autoHide && !barWindow.reserveExclusiveWhenAutoHidden)) ? -1 : (barWindow.effectiveBarThickness + effectiveSpacing + (usesConnectedFrameChrome ? 0 : (barConfig?.bottomGap ?? 0))) Item { id: inputMask @@ -647,9 +650,9 @@ PanelWindow { readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview readonly property bool effectiveVisible: (barConfig?.visible ?? true) || inOverviewWithShow - readonly property bool showing: effectiveVisible && !barWindow.hasFullscreenToplevel && (topBarCore.reveal || inOverviewWithShow || !topBarCore.autoHide) + readonly property bool showing: effectiveVisible && (topBarCore.reveal || inOverviewWithShow || !topBarCore.autoHide) - readonly property int maskThickness: barWindow.hasFullscreenToplevel ? 0 : (showing ? barThickness : 1) + readonly property int maskThickness: showing ? barThickness : 1 x: { if (!axis.isVertical) { @@ -826,9 +829,6 @@ PanelWindow { } property bool reveal: { - if (barWindow.hasFullscreenToplevel) - return false; - const inOverviewWithShow = CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview; if (inOverviewWithShow) return true; @@ -897,9 +897,9 @@ PanelWindow { bottom: barWindow.isVertical ? parent.bottom : undefined } readonly property bool inOverview: CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview - hoverEnabled: (barConfig?.autoHide ?? false) && !inOverview && !barWindow.hasFullscreenToplevel && !topBarCore.popoutPinsReveal + hoverEnabled: (barConfig?.autoHide ?? false) && !inOverview && !topBarCore.popoutPinsReveal acceptedButtons: Qt.NoButton - enabled: (barConfig?.autoHide ?? false) && !inOverview && !barWindow.hasFullscreenToplevel + enabled: (barConfig?.autoHide ?? false) && !inOverview Item { id: topBarContainer diff --git a/quickshell/Modules/DankBar/Widgets/NotepadButton.qml b/quickshell/Modules/DankBar/Widgets/NotepadButton.qml index 77a5e7b7..1871d898 100644 --- a/quickshell/Modules/DankBar/Widgets/NotepadButton.qml +++ b/quickshell/Modules/DankBar/Widgets/NotepadButton.qml @@ -11,13 +11,14 @@ BasePill { id: root readonly property string focusedScreenName: (CompositorService.isHyprland && typeof Hyprland !== "undefined" && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor ? (Hyprland.focusedWorkspace.monitor.name || "") : CompositorService.isNiri && typeof NiriService !== "undefined" && NiriService.currentOutput ? NiriService.currentOutput : "") + readonly property string targetScreenName: parentScreen?.name || focusedScreenName function resolveNotepadInstance() { if (typeof notepadSlideoutVariants === "undefined" || !notepadSlideoutVariants || !notepadSlideoutVariants.instances) { return null; } - const targetScreen = focusedScreenName; + const targetScreen = targetScreenName; if (targetScreen) { for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) { var slideout = notepadSlideoutVariants.instances[i]; @@ -34,6 +35,12 @@ BasePill { readonly property bool isActive: notepadInstance?.isVisible ?? false property bool isAutoHideBar: false + function prepareNotepadInstance(instance) { + if (instance) + instance.triggerUsesOverlayLayer = root.barUsesOverlayLayer; + return instance; + } + readonly property real minTooltipY: { if (!parentScreen || !(axis?.isVertical ?? false)) { return 0; @@ -68,8 +75,9 @@ BasePill { function openTabByIndex(tabIndex) { if (tabIndex < 0) return; - if (root.notepadInstance && typeof root.notepadInstance.show === "function") { - root.notepadInstance.show(); + const instance = prepareNotepadInstance(root.notepadInstance); + if (instance && typeof instance.show === "function") { + instance.show(); } Qt.callLater(() => { NotepadStorageService.switchToTab(tabIndex); @@ -77,8 +85,9 @@ BasePill { } function openNewNote() { - if (root.notepadInstance && typeof root.notepadInstance.show === "function") { - root.notepadInstance.show(); + const instance = prepareNotepadInstance(root.notepadInstance); + if (instance && typeof instance.show === "function") { + instance.show(); } Qt.callLater(() => { NotepadStorageService.createNewTab(); @@ -138,7 +147,7 @@ BasePill { openContextMenu(); return; } - const inst = root.notepadInstance; + const inst = prepareNotepadInstance(root.notepadInstance); if (inst) { inst.toggle(); } diff --git a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml index 136381db..ffb8d55e 100644 --- a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml +++ b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml @@ -978,7 +978,7 @@ BasePill { visible: root.useOverflowPopup && root.menuOpen screen: root.parentScreen - WlrLayershell.layer: WlrLayershell.Top + WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: { if (!root.menuOpen) @@ -1446,7 +1446,7 @@ BasePill { WlrLayershell.namespace: "dms:tray-menu-window" visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false) screen: menuRoot.parentScreen - WlrLayershell.layer: WlrLayershell.Top + WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: { if (!menuRoot.showMenu) diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index e94f3f50..79d21090 100644 --- a/quickshell/Modules/Dock/Dock.qml +++ b/quickshell/Modules/Dock/Dock.qml @@ -20,16 +20,16 @@ Variants { WindowBlur { targetWindow: dock - blurEnabled: dock.effectiveBlurEnabled && !SettingsData.connectedFrameModeActive + blurEnabled: dock.effectiveBlurEnabled && !dock.usesConnectedFrameChrome blurX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x blurY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y blurWidth: dock.hasApps && dock.reveal ? dockBackground.width : 0 blurHeight: dock.hasApps && dock.reveal ? dockBackground.height : 0 - blurRadius: Theme.isConnectedEffect ? Theme.connectedCornerRadius : dock.surfaceRadius + blurRadius: dock.usesConnectedFrameChrome ? Theme.connectedCornerRadius : dock.surfaceRadius } WlrLayershell.namespace: "dms:dock" - WlrLayershell.layer: SettingsData.frameEnabled && !dock.hasFullscreenToplevel ? WlrLayer.Overlay : WlrLayer.Top + WlrLayershell.layer: dock.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right @@ -50,16 +50,16 @@ Variants { readonly property bool connectedBarActiveOnEdge: dockGeometry.connectedBarActiveOnEdge readonly property real connectedJoinInset: dockGeometry.connectedJoinInset readonly property real dockFrameInset: dockGeometry.frameInset - readonly property real surfaceRadius: Theme.connectedSurfaceRadius - readonly property color surfaceColor: Theme.isConnectedEffect ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency) - readonly property color surfaceBorderColor: Theme.isConnectedEffect ? "transparent" : BlurService.borderColor - readonly property real surfaceBorderWidth: Theme.isConnectedEffect ? 0 : BlurService.borderWidth - readonly property real surfaceTopLeftRadius: Theme.isConnectedEffect && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius - readonly property real surfaceTopRightRadius: Theme.isConnectedEffect && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius - readonly property real surfaceBottomLeftRadius: Theme.isConnectedEffect && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius - readonly property real surfaceBottomRightRadius: Theme.isConnectedEffect && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius - readonly property real horizontalConnectorExtent: Theme.isConnectedEffect && !isVertical ? Theme.connectedCornerRadius : 0 - readonly property real verticalConnectorExtent: Theme.isConnectedEffect && isVertical ? Theme.connectedCornerRadius : 0 + readonly property real surfaceRadius: usesConnectedFrameChrome ? Theme.connectedSurfaceRadius : Theme.cornerRadius + readonly property color surfaceColor: usesConnectedFrameChrome ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency) + readonly property color surfaceBorderColor: usesConnectedFrameChrome ? "transparent" : BlurService.borderColor + readonly property real surfaceBorderWidth: usesConnectedFrameChrome ? 0 : BlurService.borderWidth + readonly property real surfaceTopLeftRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius + readonly property real surfaceTopRightRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius + readonly property real surfaceBottomLeftRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius + readonly property real surfaceBottomRightRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius + readonly property real horizontalConnectorExtent: usesConnectedFrameChrome && !isVertical ? Theme.connectedCornerRadius : 0 + readonly property real verticalConnectorExtent: usesConnectedFrameChrome && isVertical ? Theme.connectedCornerRadius : 0 readonly property int hasApps: dockApps.implicitWidth > 0 || dockApps.implicitHeight > 0 @@ -149,7 +149,6 @@ Variants { edge: dock.connectedBarSide dockVisible: dock.visible autoHide: dock.autoHide - hasFullscreenToplevel: dock.hasFullscreenToplevel iconSize: dock.widgetHeight spacing: SettingsData.dockSpacing borderThickness: dock.borderThickness @@ -176,25 +175,13 @@ Variants { } readonly property string _dockScreenName: dock.modelData ? dock.modelData.name : (dock.screen ? dock.screen.name : "") - readonly property bool hasFullscreenToplevel: { - if (!SettingsData.dockHideOnFullscreen) - return false; - CompositorService.sortedToplevels; - ToplevelManager.activeToplevel; - if (CompositorService.isNiri) { - NiriService.currentOutput; - NiriService.windows; - NiriService.allWorkspaces; - } - if (CompositorService.isHyprland) - Hyprland.focusedWorkspace; - return CompositorService.hasFullscreenToplevelOnScreen(dock._dockScreenName); - } + readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(dock._dockScreenName) + readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(dock._dockScreenName) || SettingsData.dockShowOverFullscreen function _syncDockChromeState() { if (!dock._dockScreenName) return; - if (!SettingsData.connectedFrameModeActive) { + if (!dock.usesConnectedFrameChrome) { ConnectedModeState.clearDockState(dock._dockScreenName); return; } @@ -212,19 +199,19 @@ Variants { } function _syncDockSlide() { - if (!dock._dockScreenName || !SettingsData.connectedFrameModeActive) + if (!dock._dockScreenName || !dock.usesConnectedFrameChrome) return; ConnectedModeState.setDockSlide(dock._dockScreenName, dockSlide.x, dockSlide.y); } DeferredAction { id: dockSlideSync - enabled: SettingsData.connectedFrameModeActive + enabled: dock.usesConnectedFrameChrome onTriggered: dock._syncDockSlide() } function _queueSlideSync() { - if (!SettingsData.connectedFrameModeActive) + if (!dock.usesConnectedFrameChrome) return; dockSlideSync.schedule(); } @@ -304,65 +291,10 @@ Variants { return false; } - // Hyprland implementation + // Hyprland implementation (current workspace + visible special workspaces) Hyprland.focusedWorkspace; - const filtered = CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName); - - if (filtered.length === 0) - return false; - - for (let i = 0; i < filtered.length; i++) { - const toplevel = filtered[i]; - - let hyprToplevel = null; - if (Hyprland.toplevels) { - const hyprToplevels = Array.from(Hyprland.toplevels.values); - for (let j = 0; j < hyprToplevels.length; j++) { - if (hyprToplevels[j].wayland === toplevel) { - hyprToplevel = hyprToplevels[j]; - break; - } - } - } - - if (!hyprToplevel?.lastIpcObject) - continue; - - const ipc = hyprToplevel.lastIpcObject; - const at = ipc.at; - const size = ipc.size; - if (!at || !size) - continue; - - const monX = hyprToplevel.monitor?.x ?? 0; - const monY = hyprToplevel.monitor?.y ?? 0; - - const winX = at[0] - monX; - const winY = at[1] - monY; - const winW = size[0]; - const winH = size[1]; - - switch (SettingsData.dockPosition) { - case SettingsData.Position.Top: - if (winY < dockThickness) - return true; - break; - case SettingsData.Position.Bottom: - if (winY + winH > screenHeight - dockThickness) - return true; - break; - case SettingsData.Position.Left: - if (winX < dockThickness) - return true; - break; - case SettingsData.Position.Right: - if (winX + winW > screenWidth - dockThickness) - return true; - break; - } - } - - return false; + Hyprland.toplevels; + return CompositorService.hyprlandDockOverlapForSmartAutoHide(screenName, SettingsData.dockPosition, dockThickness, screenWidth, screenHeight); } Timer { @@ -383,9 +315,6 @@ Variants { if (_modalRetractActive) return false; - if (dock.hasFullscreenToplevel) - return false; - if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) { return true; } @@ -421,7 +350,7 @@ Variants { onVisibleChanged: dock._syncDockChromeState() onHasAppsChanged: dock._syncDockChromeState() onConnectedBarSideChanged: dock._syncDockChromeState() - onHasFullscreenToplevelChanged: dock._syncDockChromeState() + onUsesConnectedFrameChromeChanged: dock._syncDockChromeState() Connections { target: SettingsData @@ -680,7 +609,7 @@ Variants { return 0; if (dock.reveal) return 0; - if (Theme.isConnectedEffect) { + if (dock.usesConnectedFrameChrome) { const retractDist = dockBackground.width + SettingsData.dockSpacing + 10; return SettingsData.dockPosition === SettingsData.Position.Right ? retractDist : -retractDist; } @@ -696,7 +625,7 @@ Variants { return 0; if (dock.reveal) return 0; - if (Theme.isConnectedEffect) { + if (dock.usesConnectedFrameChrome) { const retractDist = dockBackground.height + SettingsData.dockSpacing + 10; return SettingsData.dockPosition === SettingsData.Position.Bottom ? retractDist : -retractDist; } @@ -711,9 +640,9 @@ Variants { Behavior on x { NumberAnimation { id: slideXAnimation - duration: Theme.isConnectedEffect ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration - easing.type: Theme.isConnectedEffect ? Easing.BezierSpline : Easing.OutCubic - easing.bezierCurve: Theme.isConnectedEffect ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : [] + duration: dock.usesConnectedFrameChrome ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration + easing.type: dock.usesConnectedFrameChrome ? Easing.BezierSpline : Easing.OutCubic + easing.bezierCurve: dock.usesConnectedFrameChrome ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : [] onRunningChanged: if (!running) dock._syncDockChromeState() } @@ -722,9 +651,9 @@ Variants { Behavior on y { NumberAnimation { id: slideYAnimation - duration: Theme.isConnectedEffect ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration - easing.type: Theme.isConnectedEffect ? Easing.BezierSpline : Easing.OutCubic - easing.bezierCurve: Theme.isConnectedEffect ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : [] + duration: dock.usesConnectedFrameChrome ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration + easing.type: dock.usesConnectedFrameChrome ? Easing.BezierSpline : Easing.OutCubic + easing.bezierCurve: dock.usesConnectedFrameChrome ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : [] onRunningChanged: if (!running) dock._syncDockChromeState() } @@ -756,12 +685,12 @@ Variants { height: implicitHeight // Avoid an offscreen texture seam where the connected dock meets the frame. - layer.enabled: !Theme.isConnectedEffect + layer.enabled: !usesConnectedFrameChrome clip: false Rectangle { anchors.fill: parent - visible: !SettingsData.connectedFrameModeActive && !(Theme.isConnectedEffect && dock.reveal) + visible: !usesConnectedFrameChrome && (!SettingsData.connectedFrameModeActive || dock.reveal) color: dock.surfaceColor topLeftRadius: dock.surfaceTopLeftRadius topRightRadius: dock.surfaceTopRightRadius @@ -771,7 +700,7 @@ Variants { Rectangle { anchors.fill: parent - visible: !SettingsData.connectedFrameModeActive && !(Theme.isConnectedEffect && dock.reveal) + visible: !usesConnectedFrameChrome && (!SettingsData.connectedFrameModeActive || dock.reveal) color: "transparent" topLeftRadius: dock.surfaceTopLeftRadius topRightRadius: dock.surfaceTopRightRadius @@ -807,7 +736,7 @@ Variants { y: dockBackground.y - borderThickness width: dockBackground.width + borderThickness * 2 height: dockBackground.height + borderThickness * 2 - visible: SettingsData.dockBorderEnabled && dock.hasApps && !Theme.isConnectedEffect + visible: SettingsData.dockBorderEnabled && dock.hasApps && !usesConnectedFrameChrome preferredRendererType: Shape.CurveRenderer readonly property real borderThickness: Math.max(1, dock.borderThickness) @@ -883,6 +812,7 @@ Variants { isVertical: dock.isVertical dockScreen: dock.screen iconSize: dock.widgetHeight + usesOverlayLayer: dock.usesOverlayLayer } } } diff --git a/quickshell/Modules/Dock/DockApps.qml b/quickshell/Modules/Dock/DockApps.qml index e11296c8..85367c02 100644 --- a/quickshell/Modules/Dock/DockApps.qml +++ b/quickshell/Modules/Dock/DockApps.qml @@ -15,6 +15,7 @@ Item { property bool isVertical: false property var dockScreen: null property real iconSize: 40 + property bool usesOverlayLayer: false property int draggedIndex: -1 property int dropTargetIndex: -1 property bool suppressShiftAnimation: false diff --git a/quickshell/Modules/Dock/DockGeometry.qml b/quickshell/Modules/Dock/DockGeometry.qml index 7c268843..611739b7 100644 --- a/quickshell/Modules/Dock/DockGeometry.qml +++ b/quickshell/Modules/Dock/DockGeometry.qml @@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound import QtQuick import qs.Common +import qs.Services QtObject { id: root @@ -10,7 +11,6 @@ QtObject { property string edge: "bottom" property bool dockVisible: false property bool autoHide: false - property bool hasFullscreenToplevel: false property real iconSize: 40 property real spacing: 4 property real borderThickness: 0 @@ -23,14 +23,14 @@ QtObject { return Math.round(value * dpr) / dpr; } - readonly property bool frameExclusionActive: SettingsData.frameEnabled && !!screen && SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences) - readonly property bool connectedMode: Theme.isConnectedEffect - readonly property bool connectedBarActiveOnEdge: connectedMode && !!screen && SettingsData.getActiveBarEdgesForScreen(screen).includes(edge) + readonly property bool frameExclusionActive: CompositorService.frameWindowVisibleForScreen(screen) + readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screen) + readonly property bool connectedBarActiveOnEdge: usesConnectedFrameChrome && !!screen && SettingsData.getActiveBarEdgesForScreen(screen).includes(edge) readonly property real connectedJoinInset: { - if (connectedMode) + if (usesConnectedFrameChrome) return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness; - if (SettingsData.frameEnabled) + if (frameExclusionActive) return SettingsData.frameEdgeInsetForSide(screen, edge); return 0; } @@ -38,15 +38,15 @@ QtObject { readonly property real frameInset: { if (!frameExclusionActive) return 0; - if (connectedMode) + if (usesConnectedFrameChrome) return connectedJoinInset; return SettingsData.frameThickness; } - readonly property real effectiveMargin: connectedMode ? 0 : margin - readonly property real visualOffset: connectedMode ? 0 : offset + readonly property real effectiveMargin: usesConnectedFrameChrome ? 0 : margin + readonly property real visualOffset: usesConnectedFrameChrome ? 0 : offset readonly property real reserveOffset: offset - readonly property real joinedEdgeMargin: connectedMode ? 0 : (barSpacing + effectiveMargin + 1 + borderThickness) + readonly property real joinedEdgeMargin: usesConnectedFrameChrome ? 0 : (barSpacing + effectiveMargin + 1 + borderThickness) readonly property real bodyEdgeMargin: frameInset + joinedEdgeMargin readonly property real bodyThickness: iconSize + spacing * 2 + borderThickness * 2 @@ -57,5 +57,5 @@ QtObject { // Frame/bar edge exclusions already reserve the edge itself, so the dock // reservation covers only the dock body and user offset beyond that edge. readonly property real reserveZone: px(bodyThickness + reserveOffset + effectiveMargin) - readonly property bool shouldReserveSpace: dockVisible && !hasFullscreenToplevel && !autoHide && barSpacing <= 0 + readonly property bool shouldReserveSpace: dockVisible && !autoHide && barSpacing <= 0 } diff --git a/quickshell/Modules/Dock/DockLauncherButton.qml b/quickshell/Modules/Dock/DockLauncherButton.qml index 84b52bab..157bcf94 100644 --- a/quickshell/Modules/Dock/DockLauncherButton.qml +++ b/quickshell/Modules/Dock/DockLauncherButton.qml @@ -148,7 +148,7 @@ Item { if (wasDragging || mouse.button !== Qt.LeftButton) return; - PopoutService.toggleDankLauncherV2(); + PopoutService.toggleDankLauncherV2(dockApps?.usesOverlayLayer ?? false); } onPositionChanged: mouse => { if (longPressing && !dragging) { diff --git a/quickshell/Modules/Frame/FrameExclusions.qml b/quickshell/Modules/Frame/FrameExclusions.qml index 0ca1449b..a69bebfd 100644 --- a/quickshell/Modules/Frame/FrameExclusions.qml +++ b/quickshell/Modules/Frame/FrameExclusions.qml @@ -4,6 +4,7 @@ import QtQuick import Quickshell import Quickshell.Wayland import qs.Common +import qs.Services Scope { id: root @@ -18,7 +19,7 @@ Scope { // One thin invisible PanelWindow per edge. // Skips any edge where a bar already provides its own exclusiveZone. - readonly property bool screenEnabled: SettingsData.frameEnabled && SettingsData.isScreenInPreferences(root.screen, SettingsData.frameScreenPreferences) + readonly property bool screenEnabled: CompositorService.frameWindowVisibleForScreen(root.screen) Loader { active: root.screenEnabled && !root.barEdges.includes("top") diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index e3794b8c..70541383 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -17,8 +17,9 @@ PanelWindow { required property var targetScreen screen: targetScreen - visible: _frameActive - updatesEnabled: _frameActive + readonly property bool _frameVisible: CompositorService.frameWindowVisibleForScreen(win.targetScreen) + visible: win._frameVisible + updatesEnabled: win._frameVisible WlrLayershell.namespace: "dms:frame" WlrLayershell.layer: WlrLayer.Top @@ -52,7 +53,7 @@ PanelWindow { readonly property var _notifState: ConnectedModeState.notificationStates[win._screenName] || ConnectedModeState.emptyNotificationState readonly property var _modalState: ConnectedModeState.modalStates[win._screenName] || ConnectedModeState.emptyModalState - readonly property bool _connectedActive: win._frameActive && SettingsData.connectedFrameModeActive + readonly property bool _connectedActive: CompositorService.usesConnectedFrameChromeForScreen(win.targetScreen) readonly property string _barSide: { const edges = win.barEdges; if (edges.includes("top")) diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index f1866b5a..3e83cd29 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -10,7 +10,7 @@ import qs.Widgets PanelWindow { id: win - readonly property bool connectedFrameMode: SettingsData.frameEnabled && Theme.isConnectedEffect && SettingsData.isScreenInPreferences(win.screen, SettingsData.frameScreenPreferences) + readonly property bool connectedFrameMode: CompositorService.usesConnectedFrameChromeForScreen(win.screen) readonly property string notifBarSide: { const pos = SettingsData.notificationPopupPosition; if (pos === -1) @@ -370,9 +370,9 @@ PanelWindow { return Math.max(0, Math.round(Theme.px(raw, dpr))); } - readonly property bool frameOnlyNoConnected: SettingsData.frameEnabled && !connectedFrameMode && !!screen && SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences) + readonly property bool frameVisibleWithoutConnectedChrome: CompositorService.frameWindowVisibleForScreen(screen) && !connectedFrameMode - // Frame ON + Connected OFF. frameEdgeInset is the full bar/frame inset + // Frame visible without connected chrome. frameEdgeInset is the full bar/frame inset. function _frameGapMargin(side) { return _frameEdgeInset(side) + Theme.popupDistance; } @@ -387,7 +387,7 @@ PanelWindow { const cornerClear = (isCenterPosition || SettingsData.frameCloseGaps) ? 0 : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr)); return _frameEdgeInset("top") + cornerClear + screenY; } - if (frameOnlyNoConnected) + if (frameVisibleWithoutConnectedChrome) return _frameGapMargin("top") + screenY; const barInfo = getBarInfo(); const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance; @@ -404,7 +404,7 @@ PanelWindow { const cornerClear = (isCenterPosition || SettingsData.frameCloseGaps) ? 0 : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr)); return _frameEdgeInset("bottom") + cornerClear + screenY; } - if (frameOnlyNoConnected) + if (frameVisibleWithoutConnectedChrome) return _frameGapMargin("bottom") + screenY; const barInfo = getBarInfo(); const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance; @@ -422,7 +422,7 @@ PanelWindow { if (connectedFrameMode) return _frameEdgeInset("left"); - if (frameOnlyNoConnected) + if (frameVisibleWithoutConnectedChrome) return _frameGapMargin("left"); const barInfo = getBarInfo(); return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance; @@ -439,7 +439,7 @@ PanelWindow { if (connectedFrameMode) return _frameEdgeInset("right"); - if (frameOnlyNoConnected) + if (frameVisibleWithoutConnectedChrome) return _frameGapMargin("right"); const barInfo = getBarInfo(); return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance; diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml b/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml index 5c4d967c..41094650 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml @@ -10,7 +10,7 @@ QtObject { property var modelData property int topMargin: 0 readonly property bool compactMode: SettingsData.notificationCompactMode - readonly property bool notificationConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && SettingsData.isScreenInPreferences(manager.modelData, SettingsData.frameScreenPreferences) + readonly property bool notificationConnectedMode: CompositorService.usesConnectedFrameChromeForScreen(manager.modelData) readonly property bool closeGapNotifications: notificationConnectedMode && SettingsData.frameCloseGaps readonly property string notifBarSide: { const pos = SettingsData.notificationPopupPosition; diff --git a/quickshell/Modules/Plugins/BasePill.qml b/quickshell/Modules/Plugins/BasePill.qml index 48d15ad2..841082a5 100644 --- a/quickshell/Modules/Plugins/BasePill.qml +++ b/quickshell/Modules/Plugins/BasePill.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell import qs.Common import qs.Services import qs.Widgets @@ -38,6 +39,18 @@ Item { readonly property real rightMargin: !isVerticalOrientation ? (isRightBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0 readonly property real topMargin: isVerticalOrientation ? (isTopBarEdge && isFirst ? barEdgeExtension : (isFirst ? gapExtension : gapExtension / 2)) : 0 readonly property real bottomMargin: isVerticalOrientation ? (isBottomBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0 + readonly property bool barUsesOverlayLayer: { + switch (Quickshell.env("DMS_DANKBAR_LAYER")) { + case "overlay": + return true; + case "bottom": + case "background": + case "top": + return false; + default: + return (barConfig?.showOverFullscreen ?? false) || CompositorService.framePeerSurfacesUseOverlayForScreen(parentScreen); + } + } signal clicked signal rightClicked(real rootX, real rootY) diff --git a/quickshell/Modules/Settings/DankBarTab.qml b/quickshell/Modules/Settings/DankBarTab.qml index 7c3e487a..21e92cfa 100644 --- a/quickshell/Modules/Settings/DankBarTab.qml +++ b/quickshell/Modules/Settings/DankBarTab.qml @@ -137,7 +137,7 @@ Item { popupGapsAuto: defaultBar.popupGapsAuto ?? true, popupGapsManual: defaultBar.popupGapsManual ?? 4, maximizeDetection: defaultBar.maximizeDetection ?? true, - fullscreenDetection: defaultBar.fullscreenDetection ?? true, + showOverFullscreen: defaultBar.showOverFullscreen ?? false, scrollEnabled: defaultBar.scrollEnabled ?? true, scrollXBehavior: defaultBar.scrollXBehavior ?? "column", scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace", @@ -729,11 +729,14 @@ Item { } SettingsToggleRow { - text: I18n.tr("Hide When Fullscreen", "bar visibility toggle: hide the bar when a window is fullscreen") - checked: selectedBarConfig?.fullscreenDetection ?? true + settingKey: "barShowOverFullscreen" + tags: ["bar", "fullscreen", "overlay", "layer"] + text: I18n.tr("Show Over Fullscreen", "bar layer toggle: show the bar over fullscreen windows") + description: I18n.tr("Use the overlay layer so this bar appears above fullscreen windows") + checked: selectedBarConfig?.showOverFullscreen ?? false onToggled: toggled => { SettingsData.updateBarConfig(selectedBarId, { - fullscreenDetection: toggled + showOverFullscreen: toggled }); notifyHorizontalBarChange(); } diff --git a/quickshell/Modules/Settings/DockTab.qml b/quickshell/Modules/Settings/DockTab.qml index 88515f86..82061df2 100644 --- a/quickshell/Modules/Settings/DockTab.qml +++ b/quickshell/Modules/Settings/DockTab.qml @@ -90,13 +90,13 @@ Item { } SettingsToggleRow { - settingKey: "dockHideOnFullscreen" - tags: ["dock", "fullscreen", "hide"] - text: I18n.tr("Hide When Fullscreen", "dock visibility toggle: hide the dock when a window is fullscreen") - description: I18n.tr("Hide the dock when a window is fullscreen", "dock visibility toggle description") - checked: SettingsData.dockHideOnFullscreen + settingKey: "dockShowOverFullscreen" + tags: ["dock", "fullscreen", "overlay", "layer"] + text: I18n.tr("Show Over Fullscreen", "dock layer toggle: show the dock over fullscreen windows") + description: I18n.tr("Use the overlay layer so the dock appears above fullscreen windows") + checked: SettingsData.dockShowOverFullscreen visible: SettingsData.showDock - onToggled: checked => SettingsData.set("dockHideOnFullscreen", checked) + onToggled: checked => SettingsData.set("dockShowOverFullscreen", checked) } } diff --git a/quickshell/Modules/Settings/LauncherTab.qml b/quickshell/Modules/Settings/LauncherTab.qml index 98790eab..d850208c 100644 --- a/quickshell/Modules/Settings/LauncherTab.qml +++ b/quickshell/Modules/Settings/LauncherTab.qml @@ -67,6 +67,15 @@ Item { SettingsData.set("launcherStyle", index === 1 ? "spotlight" : "full"); } } + + SettingsToggleRow { + settingKey: "launcherShowOverFullscreen" + tags: ["launcher", "fullscreen", "overlay", "layer"] + text: I18n.tr("Show Over Fullscreen", "launcher layer toggle: show the launcher over fullscreen windows") + description: I18n.tr("Use the overlay layer when opening the launcher") + checked: SettingsData.launcherShowOverFullscreen + onToggled: checked => SettingsData.set("launcherShowOverFullscreen", checked) + } } SettingsCard { diff --git a/quickshell/Services/CompositorService.qml b/quickshell/Services/CompositorService.qml index 272ab3f2..aff7db9f 100644 --- a/quickshell/Services/CompositorService.qml +++ b/quickshell/Services/CompositorService.qml @@ -36,6 +36,7 @@ Singleton { signal randrDataReady property var sortedToplevels: [] + property var hyprlandVisibleSpecialWorkspaces: ({}) property bool _sortScheduled: false signal toplevelsChanged @@ -153,10 +154,14 @@ Singleton { enabled: isHyprland function onRawEvent(event) { - if (event.name === "openwindow" || event.name === "closewindow" || event.name === "movewindow" || event.name === "movewindowv2" || event.name === "workspace" || event.name === "workspacev2" || event.name === "focusedmon" || event.name === "focusedmonv2" || event.name === "activewindow" || event.name === "activewindowv2" || event.name === "changefloatingmode" || event.name === "fullscreen" || event.name === "moveintogroup" || event.name === "moveoutofgroup") { + if (event.name === "openwindow" || event.name === "closewindow" || event.name === "movewindow" || event.name === "movewindowv2" || event.name === "workspace" || event.name === "workspacev2" || event.name === "focusedmon" || event.name === "focusedmonv2" || event.name === "activewindow" || event.name === "activewindowv2" || event.name === "changefloatingmode" || event.name === "fullscreen" || event.name === "moveintogroup" || event.name === "moveoutofgroup" || event.name === "activespecial") { try { Hyprland.refreshToplevels(); + if (event.name === "workspace" || event.name === "workspacev2" || event.name === "focusedmon" || event.name === "focusedmonv2" || event.name === "activespecial") + Hyprland.refreshMonitors(); } catch (e) {} + if (event.name === "activespecial") + root.updateHyprlandVisibleSpecialWorkspaces(event); root.scheduleSort(); } } @@ -171,6 +176,7 @@ Singleton { Component.onCompleted: { fetchRandrData(); detectCompositor(); + updateHyprlandVisibleSpecialWorkspaces(null); scheduleSort(); Qt.callLater(() => { NiriService.generateNiriLayoutConfig(); @@ -215,6 +221,81 @@ Singleton { } } + function _normalizeSpecialWorkspaceName(name) { + const raw = String(name ?? "").trim(); + if (raw.length === 0) + return ""; + if (raw === "special") + return "special:special"; + return raw.startsWith("special:") ? raw : `special:${raw}`; + } + + function _hyprlandRawEventParts(event, argumentCount) { + if (!event) + return []; + try { + const parsed = event.parse(argumentCount); + if (parsed && parsed.length !== undefined) + return parsed; + } catch (e) {} + const data = String(event.data ?? ""); + return data.length > 0 ? data.split(",") : []; + } + + function _specialWorkspaceNameFromMonitor(monitor) { + if (!monitor) + return ""; + const candidates = [ + monitor.activeSpecialWorkspace?.name, + monitor.specialWorkspace?.name, + monitor.lastIpcObject?.specialWorkspace?.name, + monitor.lastIpcObject?.specialWorkspace, + monitor.lastIpcObject?.activeSpecialWorkspace?.name + ]; + for (let i = 0; i < candidates.length; i++) { + const normalized = _normalizeSpecialWorkspaceName(candidates[i]); + if (normalized) + return normalized; + } + return ""; + } + + function updateHyprlandVisibleSpecialWorkspaces(event) { + if (!isHyprland) { + hyprlandVisibleSpecialWorkspaces = ({}); + return; + } + + const next = {}; + try { + const monitors = Hyprland.monitors?.values || []; + for (const monitor of monitors) { + const monitorName = monitor?.name ?? monitor?.lastIpcObject?.name ?? ""; + if (!monitorName) + continue; + const specialName = _specialWorkspaceNameFromMonitor(monitor); + if (specialName) + next[monitorName] = specialName; + } + } catch (e) { + log.warn("updateHyprlandVisibleSpecialWorkspaces monitor snapshot failed:", e); + } + + if (event?.name === "activespecial") { + const parts = _hyprlandRawEventParts(event, 2); + const specialName = _normalizeSpecialWorkspaceName(parts[0]); + const monitorName = String(parts[1] ?? Hyprland.focusedMonitor?.name ?? Hyprland.focusedWorkspace?.monitor?.name ?? ""); + if (monitorName) { + if (specialName) + next[monitorName] = specialName; + else + delete next[monitorName]; + } + } + + hyprlandVisibleSpecialWorkspaces = next; + } + function sortHyprlandToplevelsSafe() { if (!Hyprland.toplevels || !Hyprland.toplevels.values) return []; @@ -451,6 +532,171 @@ Singleton { return false; } + function _hyprlandToplevelMapped(hyprToplevel) { + if (!hyprToplevel) + return false; + if (hyprToplevel.mapped === false) + return false; + const ipcMapped = hyprToplevel.lastIpcObject?.mapped; + if (ipcMapped === false) + return false; + if (hyprToplevel.hidden === true) + return false; + const ipcHidden = hyprToplevel.lastIpcObject?.hidden; + if (ipcHidden === true) + return false; + return true; + } + + function hyprlandVisibleSpecialWorkspaceOnScreen(screenOrName) { + const screenName = _screenName(screenOrName); + if (!isHyprland || !screenName) + return ""; + hyprlandVisibleSpecialWorkspaces; + const trackedName = hyprlandVisibleSpecialWorkspaces[screenName] ?? ""; + if (trackedName) + return trackedName; + try { + const monitor = Hyprland.monitors?.values?.find(m => m.name === screenName); + return _specialWorkspaceNameFromMonitor(monitor); + } catch (e) { + return ""; + } + } + + function hyprlandSpecialWorkspaceBlocksConnectedFrame(screenOrName) { + const screenName = _screenName(screenOrName); + if (!isHyprland || !screenName || !Hyprland.toplevels?.values) + return false; + const visibleSpecialWorkspace = hyprlandVisibleSpecialWorkspaceOnScreen(screenName); + if (!visibleSpecialWorkspace) + return false; + + try { + for (const t of Hyprland.toplevels.values) { + const monName = t.monitor?.name ?? t.lastIpcObject?.monitor ?? ""; + if (monName !== screenName) + continue; + const wsName = _normalizeSpecialWorkspaceName(t.workspace?.name ?? t.lastIpcObject?.workspace?.name ?? ""); + if (!wsName || wsName !== visibleSpecialWorkspace) + continue; + if (_hyprlandToplevelMapped(t)) + return true; + } + } catch (e) { + log.warn("hyprlandSpecialWorkspaceBlocksConnectedFrame failed:", e); + } + return false; + } + + function connectedFrameBlockedOnScreen(screenOrName) { + if (hasFullscreenToplevelOnScreen(screenOrName)) + return true; + return hyprlandSpecialWorkspaceBlocksConnectedFrame(screenOrName); + } + + function _screenForName(screenOrName) { + if (screenOrName && typeof screenOrName !== "string") + return screenOrName; + const screenName = _screenName(screenOrName); + if (!screenName) + return null; + const screens = Quickshell.screens || []; + for (let i = 0; i < screens.length; i++) { + if (screens[i]?.name === screenName) + return screens[i]; + } + return null; + } + + function frameConfiguredForScreen(screenOrName) { + if (!SettingsData.frameEnabled) + return false; + const screen = _screenForName(screenOrName); + if (!screen || !SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences)) + return false; + return true; + } + + function frameWindowVisibleForScreen(screenOrName) { + if (!frameConfiguredForScreen(screenOrName)) + return false; + return !connectedFrameBlockedOnScreen(screenOrName); + } + + function usesConnectedFrameChromeForScreen(screenOrName) { + return SettingsData.connectedFrameModeActive && frameWindowVisibleForScreen(screenOrName); + } + + function framePeerSurfacesUseOverlayForScreen(screenOrName) { + return frameWindowVisibleForScreen(screenOrName); + } + + function hyprlandToplevelOverlapsDockEdge(hyprToplevel, screenName, dockPosition, dockThickness, screenWidth, screenHeight) { + if (!hyprToplevel?.lastIpcObject || !screenName) + return false; + const monName = hyprToplevel.monitor?.name ?? hyprToplevel.lastIpcObject?.monitor ?? ""; + if (monName && monName !== screenName) + return false; + const ipc = hyprToplevel.lastIpcObject; + const at = ipc.at; + const size = ipc.size; + if (!at || !size) + return false; + const monX = hyprToplevel.monitor?.x ?? 0; + const monY = hyprToplevel.monitor?.y ?? 0; + const winX = at[0] - monX; + const winY = at[1] - monY; + const winW = size[0]; + const winH = size[1]; + switch (dockPosition) { + case SettingsData.Position.Top: + return winY < dockThickness; + case SettingsData.Position.Bottom: + return winY + winH > screenHeight - dockThickness; + case SettingsData.Position.Left: + return winX < dockThickness; + case SettingsData.Position.Right: + return winX + winW > screenWidth - dockThickness; + default: + return false; + } + } + + function hyprlandDockOverlapForSmartAutoHide(screenName, dockPosition, dockThickness, screenWidth, screenHeight) { + if (!isHyprland || !screenName || !Hyprland.toplevels?.values) + return false; + + const filtered = filterCurrentWorkspace(sortedToplevels, screenName); + for (let i = 0; i < filtered.length; i++) { + const toplevel = filtered[i]; + let hyprToplevel = null; + for (const t of Hyprland.toplevels.values) { + if (t.wayland === toplevel) { + hyprToplevel = t; + break; + } + } + if (hyprlandToplevelOverlapsDockEdge(hyprToplevel, screenName, dockPosition, dockThickness, screenWidth, screenHeight)) + return true; + } + + const visibleSpecialWorkspace = hyprlandVisibleSpecialWorkspaceOnScreen(screenName); + if (!visibleSpecialWorkspace) + return false; + + for (const hyprToplevel of Hyprland.toplevels.values) { + const wsName = _normalizeSpecialWorkspaceName(hyprToplevel.workspace?.name ?? hyprToplevel.lastIpcObject?.workspace?.name ?? ""); + if (wsName !== visibleSpecialWorkspace) + continue; + if (!_hyprlandToplevelMapped(hyprToplevel)) + continue; + if (hyprlandToplevelOverlapsDockEdge(hyprToplevel, screenName, dockPosition, dockThickness, screenWidth, screenHeight)) + return true; + } + return false; + } + function filterHyprlandCurrentDisplaySafe(toplevels, screenName) { if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels) return toplevels; diff --git a/quickshell/Services/PopoutService.qml b/quickshell/Services/PopoutService.qml index eb51cc9f..73fd6dd8 100644 --- a/quickshell/Services/PopoutService.qml +++ b/quickshell/Services/PopoutService.qml @@ -500,8 +500,16 @@ Singleton { property bool _dankLauncherV2WantsToggle: false property string _dankLauncherV2PendingQuery: "" property string _dankLauncherV2PendingMode: "" + property bool _dankLauncherV2TriggerUsesOverlayLayer: false - function openDankLauncherV2() { + function _setDankLauncherV2TriggerUsesOverlayLayer(value) { + _dankLauncherV2TriggerUsesOverlayLayer = value === true; + if (dankLauncherV2Modal) + dankLauncherV2Modal.triggerUsesOverlayLayer = _dankLauncherV2TriggerUsesOverlayLayer; + } + + function openDankLauncherV2(triggerUsesOverlayLayer) { + _setDankLauncherV2TriggerUsesOverlayLayer(triggerUsesOverlayLayer); if (dankLauncherV2Modal) { dankLauncherV2Modal.show(); } else if (dankLauncherV2ModalLoader) { @@ -511,7 +519,8 @@ Singleton { } } - function openDankLauncherV2WithQuery(query: string) { + function openDankLauncherV2WithQuery(query: string, triggerUsesOverlayLayer) { + _setDankLauncherV2TriggerUsesOverlayLayer(triggerUsesOverlayLayer); if (dankLauncherV2Modal) { dankLauncherV2Modal.showWithQuery(query); } else if (dankLauncherV2ModalLoader) { @@ -522,7 +531,8 @@ Singleton { } } - function openDankLauncherV2WithMode(mode: string) { + function openDankLauncherV2WithMode(mode: string, triggerUsesOverlayLayer) { + _setDankLauncherV2TriggerUsesOverlayLayer(triggerUsesOverlayLayer); if (dankLauncherV2Modal) { dankLauncherV2Modal.showWithMode(mode); } else if (dankLauncherV2ModalLoader) { @@ -544,7 +554,8 @@ Singleton { } } - function toggleDankLauncherV2() { + function toggleDankLauncherV2(triggerUsesOverlayLayer) { + _setDankLauncherV2TriggerUsesOverlayLayer(triggerUsesOverlayLayer); if (dankLauncherV2Modal) { dankLauncherV2Modal.toggle(); } else if (dankLauncherV2ModalLoader) { @@ -554,7 +565,8 @@ Singleton { } } - function toggleDankLauncherV2WithMode(mode: string) { + function toggleDankLauncherV2WithMode(mode: string, triggerUsesOverlayLayer) { + _setDankLauncherV2TriggerUsesOverlayLayer(triggerUsesOverlayLayer); if (dankLauncherV2Modal) { dankLauncherV2Modal.toggleWithMode(mode); } else if (dankLauncherV2ModalLoader) { @@ -565,7 +577,8 @@ Singleton { } } - function toggleDankLauncherV2WithQuery(query: string) { + function toggleDankLauncherV2WithQuery(query: string, triggerUsesOverlayLayer) { + _setDankLauncherV2TriggerUsesOverlayLayer(triggerUsesOverlayLayer); if (dankLauncherV2Modal) { dankLauncherV2Modal.toggleWithQuery(query); } else if (dankLauncherV2ModalLoader) { @@ -577,6 +590,8 @@ Singleton { } function _onDankLauncherV2ModalLoaded() { + if (dankLauncherV2Modal) + dankLauncherV2Modal.triggerUsesOverlayLayer = _dankLauncherV2TriggerUsesOverlayLayer; if (_dankLauncherV2WantsOpen) { _dankLauncherV2WantsOpen = false; if (_dankLauncherV2PendingQuery) { diff --git a/quickshell/Widgets/DankPopout.qml b/quickshell/Widgets/DankPopout.qml index fb8a64d5..4f626dde 100644 --- a/quickshell/Widgets/DankPopout.qml +++ b/quickshell/Widgets/DankPopout.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell import qs.Common import qs.Services @@ -32,6 +33,7 @@ Item { property real storedBarThickness: Theme.barHeight - 4 property real storedBarSpacing: 4 property var storedBarConfig: null + property bool triggerUsesOverlayLayer: false property var adjacentBarInfo: ({ "topBar": 0, "bottomBar": 0, @@ -97,13 +99,29 @@ Item { function onConnectedFrameModeActiveChanged() { root._maybeResolveBackend(); } + function onFrameEnabledChanged() { + root._maybeResolveBackend(); + } function onFrameScreenPreferencesChanged() { root._maybeResolveBackend(); } + function onShowDockChanged() { + root._maybeResolveBackend(); + } + function onBarConfigsChanged() { + root._maybeResolveBackend(); + } + } + + Connections { + target: CompositorService + function onToplevelsChanged() { + root._maybeResolveBackend(); + } } function _usesConnectedBackendForScreen(targetScreen) { - return SettingsData.connectedFrameModeActive && !!targetScreen && SettingsData.isScreenInPreferences(targetScreen, SettingsData.frameScreenPreferences); + return CompositorService.usesConnectedFrameChromeForScreen(targetScreen); } function _backendForScreen(targetScreen) { @@ -151,6 +169,19 @@ Item { effectiveBarBottomGap = bottomGap !== undefined ? bottomGap : 0; } + function _triggerBarUsesOverlayLayer(targetScreen, barConfig) { + switch (Quickshell.env("DMS_DANKBAR_LAYER")) { + case "overlay": + return true; + case "bottom": + case "background": + case "top": + return false; + default: + return (barConfig?.showOverFullscreen ?? false) || CompositorService.framePeerSurfacesUseOverlayForScreen(targetScreen); + } + } + function setTriggerPosition(x, y, width, section, targetScreen, barPosition, barThickness, barSpacing, barConfig) { triggerX = x; triggerY = y; @@ -161,6 +192,7 @@ Item { storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4); storedBarSpacing = barSpacing !== undefined ? barSpacing : 4; storedBarConfig = barConfig; + triggerUsesOverlayLayer = _triggerBarUsesOverlayLayer(targetScreen, barConfig); const pos = barPosition !== undefined ? barPosition : 0; const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : 0) : 0; @@ -221,6 +253,7 @@ Item { it.storedBarThickness = Qt.binding(() => root.storedBarThickness); it.storedBarSpacing = Qt.binding(() => root.storedBarSpacing); it.storedBarConfig = Qt.binding(() => root.storedBarConfig); + it.triggerUsesOverlayLayer = Qt.binding(() => root.triggerUsesOverlayLayer); it.adjacentBarInfo = Qt.binding(() => root.adjacentBarInfo); it.screen = Qt.binding(() => root.screen); it.effectiveBarPosition = Qt.binding(() => root.effectiveBarPosition); diff --git a/quickshell/Widgets/DankPopoutConnected.qml b/quickshell/Widgets/DankPopoutConnected.qml index 11da3c9c..4c6630e3 100644 --- a/quickshell/Widgets/DankPopoutConnected.qml +++ b/quickshell/Widgets/DankPopoutConnected.qml @@ -47,6 +47,7 @@ Item { property real storedBarThickness: Theme.barHeight - 4 property real storedBarSpacing: 4 property var storedBarConfig: null + property bool triggerUsesOverlayLayer: false property var adjacentBarInfo: ({ "topBar": 0, "bottomBar": 0, @@ -56,9 +57,23 @@ Item { property var screen: null // Connected resize uses one full-screen surface; body-sized regions are masks. readonly property bool useBackgroundWindow: false + readonly property var effectivePopoutLayer: { + switch (Quickshell.env("DMS_POPOUT_LAYER")) { + case "bottom": + log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "background": + log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "overlay": + return WlrLayershell.Overlay; + default: + return root.triggerUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top; + } + } readonly property real effectiveBarThickness: { - if (Theme.isConnectedEffect) + if (root.usesConnectedSurfaceChrome) return Math.max(0, storedBarThickness); const padding = storedBarConfig ? (storedBarConfig.innerPadding !== undefined ? storedBarConfig.innerPadding : 4) : 4; return Math.max(26 + padding * 0.6, Theme.barHeight - 4 - (8 - padding)) + storedBarSpacing; @@ -345,7 +360,10 @@ Item { } } - readonly property bool frameOwnsConnectedChrome: SettingsData.connectedFrameModeActive && !!root.screen && SettingsData.isScreenInPreferences(root.screen, SettingsData.frameScreenPreferences) + readonly property bool frameOwnsConnectedChrome: CompositorService.usesConnectedFrameChromeForScreen(root.screen) + readonly property bool usesConnectedSurfaceChrome: Theme.isConnectedEffect && !CompositorService.connectedFrameBlockedOnScreen(root.screen) + readonly property bool usesLocalConnectedSurfaceChrome: usesConnectedSurfaceChrome && !frameOwnsConnectedChrome + onFrameOwnsConnectedChromeChanged: _syncPopoutChromeState() property bool animationsEnabled: true @@ -455,28 +473,23 @@ Item { readonly property real dpr: screen ? screen.devicePixelRatio : 1 readonly property bool closeFrameGapsActive: SettingsData.frameCloseGaps && frameOwnsConnectedChrome readonly property real frameInset: { - if (!SettingsData.frameEnabled) + if (!root.frameOwnsConnectedChrome) return 0; const ft = SettingsData.frameThickness; const fr = SettingsData.frameRounding; const ccr = Theme.connectedCornerRadius; - if (Theme.isConnectedEffect) - return Math.max(ft * 4, ft + ccr * 2); - const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true; - const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 6; - const gap = useAutoGaps ? Math.max(6, storedBarSpacing) : manualGapValue; - return Math.max(ft + gap, fr); + return Math.max(ft * 4, ft + ccr * 2, fr); } function _popupGapValue() { const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true; const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4; const rawPopupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue; - return Theme.isConnectedEffect ? 0 : rawPopupGap; + return root.usesConnectedSurfaceChrome ? 0 : rawPopupGap; } function _frameEdgeInset(side) { - if (!SettingsData.frameEnabled || !root.screen) + if (!root.frameOwnsConnectedChrome || !root.screen) return 0; const edges = SettingsData.getActiveBarEdgesForScreen(root.screen); const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness; @@ -549,7 +562,7 @@ Item { readonly property real shadowFallbackOffset: 6 readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0 readonly property real shadowMotionPadding: { - if (Theme.isConnectedEffect) + if (root.usesConnectedSurfaceChrome) return Math.max(storedBarSpacing + Theme.connectedCornerRadius + 4, 40); if (Theme.isDirectionalEffect) return 16; @@ -582,7 +595,7 @@ Item { } } readonly property real connectedAnchorX: { - if (!Theme.isConnectedEffect) + if (!root.usesConnectedSurfaceChrome) return triggerX; switch (effectiveBarPosition) { case SettingsData.Position.Left: @@ -594,7 +607,7 @@ Item { } } readonly property real connectedAnchorY: { - if (!Theme.isConnectedEffect) + if (!root.usesConnectedSurfaceChrome) return triggerY; switch (effectiveBarPosition) { case SettingsData.Position.Top: @@ -609,7 +622,7 @@ Item { function adjacentBarClearance(exclusion) { if (exclusion <= 0) return 0; - if (!Theme.isConnectedEffect) + if (!root.usesConnectedSurfaceChrome) return exclusion; // In a shared frame corner, the adjacent connected bar already occupies // one rounded-corner radius before the popout's own connector begins. @@ -641,7 +654,7 @@ Item { const popupGap = _popupGapValue(); const edgeGapLeft = _edgeGapFor("left", popupGap); const edgeGapRight = _edgeGapFor("right", popupGap); - const anchorX = Theme.isConnectedEffect ? connectedAnchorX : triggerX; + const anchorX = root.usesConnectedSurfaceChrome ? connectedAnchorX : triggerX; switch (effectiveBarPosition) { case SettingsData.Position.Left: @@ -662,7 +675,7 @@ Item { const popupGap = _popupGapValue(); const edgeGapTop = _edgeGapFor("top", popupGap); const edgeGapBottom = _edgeGapFor("bottom", popupGap); - const anchorY = Theme.isConnectedEffect ? connectedAnchorY : triggerY; + const anchorY = root.usesConnectedSurfaceChrome ? connectedAnchorY : triggerY; switch (effectiveBarPosition) { case SettingsData.Position.Bottom: @@ -718,7 +731,7 @@ Item { blurEnabled: root.effectiveSurfaceBlurEnabled && !root.frameOwnsConnectedChrome readonly property real s: Math.min(1, contentContainer.scaleValue) - readonly property bool trackBlurFromBarEdge: Theme.isConnectedEffect || Theme.isDirectionalEffect + readonly property bool trackBlurFromBarEdge: root.usesConnectedSurfaceChrome || Theme.isDirectionalEffect // Directional popouts clip to the bar edge, so the blur needs to grow from // that same edge instead of translating through the bar before settling. @@ -729,24 +742,11 @@ Item { blurY: trackBlurFromBarEdge ? contentContainer.y + (contentContainer.barBottom ? _dyClamp : 0) : contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr) - contentContainer.verticalConnectorExtent * s blurWidth: shouldBeVisible ? (trackBlurFromBarEdge ? Math.max(0, contentContainer.width - Math.abs(_dxClamp)) : (contentContainer.width + contentContainer.horizontalConnectorExtent * 2) * s) : 0 blurHeight: shouldBeVisible ? (trackBlurFromBarEdge ? Math.max(0, contentContainer.height - Math.abs(_dyClamp)) : (contentContainer.height + contentContainer.verticalConnectorExtent * 2) * s) : 0 - blurRadius: Theme.isConnectedEffect ? Theme.connectedCornerRadius : Theme.connectedSurfaceRadius + blurRadius: root.usesConnectedSurfaceChrome ? Theme.connectedCornerRadius : Theme.cornerRadius } WlrLayershell.namespace: root.layerNamespace - WlrLayershell.layer: { - switch (Quickshell.env("DMS_POPOUT_LAYER")) { - case "bottom": - log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "background": - log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "overlay": - return WlrLayershell.Overlay; - default: - return WlrLayershell.Top; - } - } + WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: { if (customKeyboardFocus !== null) @@ -827,22 +827,22 @@ Item { readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right readonly property string connectedBarSide: barTop ? "top" : (barBottom ? "bottom" : (barLeft ? "left" : "right")) - readonly property real surfaceRadius: Theme.connectedSurfaceRadius - readonly property color surfaceColor: Theme.popupLayerColor(Theme.surfaceContainer) - readonly property color surfaceBorderColor: Theme.isConnectedEffect ? "transparent" : (BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium) - readonly property real surfaceBorderWidth: Theme.isConnectedEffect ? 0 : BlurService.borderWidth - readonly property real surfaceTopLeftRadius: Theme.isConnectedEffect && (barTop || barLeft) ? 0 : surfaceRadius - readonly property real surfaceTopRightRadius: Theme.isConnectedEffect && (barTop || barRight) ? 0 : surfaceRadius - readonly property real surfaceBottomLeftRadius: Theme.isConnectedEffect && (barBottom || barLeft) ? 0 : surfaceRadius - readonly property real surfaceBottomRightRadius: Theme.isConnectedEffect && (barBottom || barRight) ? 0 : surfaceRadius + readonly property real surfaceRadius: root.usesConnectedSurfaceChrome ? Theme.connectedSurfaceRadius : Theme.cornerRadius + readonly property color surfaceColor: root.usesConnectedSurfaceChrome ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + readonly property color surfaceBorderColor: root.usesConnectedSurfaceChrome ? "transparent" : (BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium) + readonly property real surfaceBorderWidth: root.usesConnectedSurfaceChrome ? 0 : BlurService.borderWidth + readonly property real surfaceTopLeftRadius: root.usesConnectedSurfaceChrome && (barTop || barLeft) ? 0 : surfaceRadius + readonly property real surfaceTopRightRadius: root.usesConnectedSurfaceChrome && (barTop || barRight) ? 0 : surfaceRadius + readonly property real surfaceBottomLeftRadius: root.usesConnectedSurfaceChrome && (barBottom || barLeft) ? 0 : surfaceRadius + readonly property real surfaceBottomRightRadius: root.usesConnectedSurfaceChrome && (barBottom || barRight) ? 0 : surfaceRadius readonly property bool directionalEffect: Theme.isDirectionalEffect readonly property bool depthEffect: Theme.isDepthEffect readonly property real directionalTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL) readonly property real directionalTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL) readonly property real depthTravel: Math.max(root.animationOffset * 0.7, 28) readonly property real sectionTilt: (triggerSection === "left" ? -1 : (triggerSection === "right" ? 1 : 0)) - readonly property real horizontalConnectorExtent: Theme.isConnectedEffect && (barTop || barBottom) ? Theme.connectedCornerRadius : 0 - readonly property real verticalConnectorExtent: Theme.isConnectedEffect && (barLeft || barRight) ? Theme.connectedCornerRadius : 0 + readonly property real horizontalConnectorExtent: root.usesConnectedSurfaceChrome && (barTop || barBottom) ? Theme.connectedCornerRadius : 0 + readonly property real verticalConnectorExtent: root.usesConnectedSurfaceChrome && (barLeft || barRight) ? Theme.connectedCornerRadius : 0 readonly property real offsetX: { if (directionalEffect) { @@ -923,10 +923,10 @@ Item { Item { id: directionalClipMask - readonly property bool shouldClip: Theme.isDirectionalEffect || Theme.isConnectedEffect + readonly property bool shouldClip: Theme.isDirectionalEffect || root.usesConnectedSurfaceChrome readonly property real clipOversize: 1000 readonly property real connectedClipAllowance: { - if (!Theme.isConnectedEffect) + if (!root.usesConnectedSurfaceChrome) return 0; if (root.frameOwnsConnectedChrome) return 0; @@ -972,11 +972,11 @@ Item { ElevationShadow { id: shadowSource - readonly property real connectorExtent: Theme.isConnectedEffect ? Theme.connectedCornerRadius : 0 - readonly property real extraLeft: Theme.isConnectedEffect && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 - readonly property real extraRight: Theme.isConnectedEffect && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 - readonly property real extraTop: Theme.isConnectedEffect && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0 - readonly property real extraBottom: Theme.isConnectedEffect && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0 + readonly property real connectorExtent: root.usesConnectedSurfaceChrome ? Theme.connectedCornerRadius : 0 + readonly property real extraLeft: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 + readonly property real extraRight: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 + readonly property real extraTop: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0 + readonly property real extraBottom: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0 readonly property real bodyX: extraLeft readonly property real bodyY: extraTop readonly property real bodyWidth: rollOutAdjuster.baseWidth @@ -999,12 +999,12 @@ Item { targetColor: contentContainer.surfaceColor borderColor: contentContainer.surfaceBorderColor borderWidth: contentContainer.surfaceBorderWidth - useCustomSource: Theme.isConnectedEffect + useCustomSource: root.usesConnectedSurfaceChrome shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !root.frameOwnsConnectedChrome Item { anchors.fill: parent - visible: Theme.isConnectedEffect && !root.frameOwnsConnectedChrome + visible: root.usesLocalConnectedSurfaceChrome clip: false Rectangle { @@ -1020,7 +1020,7 @@ Item { } ConnectedCorner { - visible: Theme.isConnectedEffect + visible: root.usesConnectedSurfaceChrome barSide: contentContainer.connectedBarSide placement: "left" spacing: 0 @@ -1032,7 +1032,7 @@ Item { } ConnectedCorner { - visible: Theme.isConnectedEffect + visible: root.usesConnectedSurfaceChrome barSide: contentContainer.connectedBarSide placement: "right" spacing: 0 @@ -1109,7 +1109,7 @@ Item { Item { anchors.fill: parent clip: false - visible: !Theme.isConnectedEffect + visible: !root.usesConnectedSurfaceChrome Rectangle { anchors.fill: parent diff --git a/quickshell/Widgets/DankPopoutStandalone.qml b/quickshell/Widgets/DankPopoutStandalone.qml index 45b756b8..3f7e32a4 100644 --- a/quickshell/Widgets/DankPopoutStandalone.qml +++ b/quickshell/Widgets/DankPopoutStandalone.qml @@ -52,6 +52,7 @@ Item { property real storedBarThickness: Theme.barHeight - 4 property real storedBarSpacing: 4 property var storedBarConfig: null + property bool triggerUsesOverlayLayer: false property var adjacentBarInfo: ({ "topBar": 0, "bottomBar": 0, @@ -59,11 +60,25 @@ Item { "rightBar": 0 }) property var screen: null - readonly property bool frameOnlyNoConnected: SettingsData.frameEnabled && !!screen && SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences) + readonly property bool frameGapStandaloneActive: CompositorService.frameConfiguredForScreen(screen) && !CompositorService.usesConnectedFrameChromeForScreen(screen) readonly property bool fluidStandaloneActive: Theme.isDirectionalEffect readonly property bool backgroundDismissWindowRequired: backgroundInteractive readonly property bool backgroundWindowRequired: backgroundDismissWindowRequired || root.overlayContent !== null readonly property bool _fullHeight: fullHeightSurface + readonly property var effectivePopoutLayer: { + switch (Quickshell.env("DMS_POPOUT_LAYER")) { + case "bottom": + root.log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "background": + root.log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer."); + return WlrLayershell.Top; + case "overlay": + return WlrLayershell.Overlay; + default: + return root.triggerUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top; + } + } function _frameEdgeInset(side) { if (!screen) @@ -76,7 +91,7 @@ Item { } function _edgeClearance(side, popupGap, adjacentInset) { - if (frameOnlyNoConnected) + if (frameGapStandaloneActive) return Math.max(adjacentInset, _frameGapMargin(side)); return adjacentInset > 0 ? adjacentInset : popupGap; } @@ -524,7 +539,7 @@ Item { updatesEnabled: root.overlayContent !== null || root._bgCommitWindow WlrLayershell.namespace: root.layerNamespace + ":background" - WlrLayershell.layer: WlrLayershell.Top + WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: WlrKeyboardFocus.None @@ -602,20 +617,7 @@ Item { } WlrLayershell.namespace: root.layerNamespace - WlrLayershell.layer: { - switch (Quickshell.env("DMS_POPOUT_LAYER")) { - case "bottom": - root.log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "background": - root.log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "overlay": - return WlrLayershell.Overlay; - default: - return WlrLayershell.Top; - } - } + WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: { if (customKeyboardFocus !== null) diff --git a/quickshell/Widgets/DankSlideout.qml b/quickshell/Widgets/DankSlideout.qml index 4dd22be4..6d940f52 100644 --- a/quickshell/Widgets/DankSlideout.qml +++ b/quickshell/Widgets/DankSlideout.qml @@ -15,6 +15,7 @@ PanelWindow { property bool isVisible: false property var targetScreen: null property var modelData: null + property bool triggerUsesOverlayLayer: false property real slideoutWidth: 480 property bool expandable: false property bool expandedWidth: false @@ -61,7 +62,7 @@ PanelWindow { readonly property bool slideoutBlurActive: root.visible && BlurService.enabled && Theme.connectedSurfaceBlurEnabled - WlrLayershell.layer: WlrLayershell.Top + WlrLayershell.layer: (triggerUsesOverlayLayer || CompositorService.framePeerSurfacesUseOverlayForScreen(modelData)) ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.exclusiveZone: 0 WlrLayershell.keyboardFocus: isVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 0695647e..dd0ccc83 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -854,6 +854,29 @@ "icon": "layers", "description": "Override the global shadow with per-bar settings" }, + { + "section": "barShowOverFullscreen", + "label": "Show Over Fullscreen", + "tabIndex": 3, + "category": "Dank Bar", + "keywords": [ + "above", + "appears", + "bar", + "dank", + "fullscreen", + "layer", + "over", + "overlay", + "panel", + "show", + "statusbar", + "taskbar", + "topbar", + "windows" + ], + "description": "Use the overlay layer so this bar appears above fullscreen windows" + }, { "section": "barSpacing", "label": "Spacing", @@ -927,20 +950,28 @@ "tabIndex": 3, "category": "Dank Bar", "keywords": [ + "above", + "appears", "auto-hide", "autohide", "bar", "dank", + "fullscreen", "hidden", "hide", + "layer", + "overlay", "panel", "show", "statusbar", + "taskbar", "topbar", "visibility", - "visible" + "visible", + "windows" ], - "icon": "visibility_off" + "icon": "visibility_off", + "description": "Use the overlay layer so this bar appears above fullscreen windows" }, { "section": "workspaceDragReorder", @@ -1487,22 +1518,6 @@ ], "description": "Group multiple windows of the same app together with a window count indicator" }, - { - "section": "dockHideOnFullscreen", - "label": "Hide When Fullscreen", - "tabIndex": 5, - "category": "Dock", - "keywords": [ - "dock", - "fullscreen", - "hide", - "launcher bar", - "panel", - "taskbar", - "window" - ], - "description": "Hide the dock when a window is fullscreen" - }, { "section": "dockIconSize", "label": "Icon Size", @@ -1742,6 +1757,27 @@ "taskbar" ] }, + { + "section": "dockShowOverFullscreen", + "label": "Show Over Fullscreen", + "tabIndex": 5, + "category": "Dock", + "keywords": [ + "above", + "appears", + "dock", + "fullscreen", + "launcher bar", + "layer", + "over", + "overlay", + "panel", + "show", + "taskbar", + "windows" + ], + "description": "Use the overlay layer so the dock appears above fullscreen windows" + }, { "section": "dockShowOverflowBadge", "label": "Show Overflow Badge Count", @@ -2211,14 +2247,18 @@ "drawer", "full", "launcher", + "layer", "menu", "minimal", + "opening", + "overlay", "spotlight", "start", "start menu", "style" ], - "icon": "search" + "icon": "search", + "description": "Use the overlay layer when opening the launcher" }, { "section": "spotlightCloseNiriOverview", @@ -2361,6 +2401,29 @@ ], "description": "Show mode tabs and keyboard hints at the bottom." }, + { + "section": "launcherShowOverFullscreen", + "label": "Show Over Fullscreen", + "tabIndex": 9, + "category": "Launcher", + "keywords": [ + "app drawer", + "app menu", + "applications", + "drawer", + "fullscreen", + "launcher", + "layer", + "menu", + "opening", + "over", + "overlay", + "show", + "start", + "start menu" + ], + "description": "Use the overlay layer when opening the launcher" + }, { "section": "launcherLogoSizeOffset", "label": "Size Offset", @@ -4738,6 +4801,27 @@ ], "description": "Automatically lock the screen when DMS starts" }, + { + "section": "lockBeforeSuspend", + "label": "Lock before suspend", + "tabIndex": 11, + "category": "Lock Screen", + "keywords": [ + "automatic", + "automatically", + "before", + "lock", + "login", + "password", + "prepares", + "screen", + "security", + "sleep", + "suspend", + "system" + ], + "description": "Automatically lock the screen when the system prepares to suspend" + }, { "section": "lockScreenNotificationMode", "label": "Notification Display", @@ -6844,27 +6928,6 @@ "icon": "schedule", "description": "Gradually fade the screen before locking with a configurable grace period" }, - { - "section": "lockBeforeSuspend", - "label": "Lock before suspend", - "tabIndex": 21, - "category": "Power & Sleep", - "keywords": [ - "automatically", - "before", - "energy", - "lock", - "power", - "prepares", - "screen", - "security", - "shutdown", - "sleep", - "suspend", - "system" - ], - "description": "Automatically lock the screen when the system prepares to suspend" - }, { "section": "fadeToLockGracePeriod", "label": "Lock fade grace period",