From 7964b668271791a209b4d7bba3e83624b837d323 Mon Sep 17 00:00:00 2001 From: purian23 Date: Wed, 1 Jul 2026 16:15:02 -0400 Subject: [PATCH] feat(Xray): Automatic Xray blur effects for Niri & Hyprland layouts - Utilizes include layout within niri configs & hyprland's layout layer rules for lua configs --- quickshell/Common/SettingsData.qml | 4 + quickshell/Common/settings/SettingsSpec.js | 4 + .../Modules/Settings/CompositorLayoutTab.qml | 40 +++++++ .../Modules/Settings/ThemeColorsTab.qml | 34 ++++++ quickshell/Services/HyprlandService.qml | 25 +++++ quickshell/Services/NiriService.qml | 33 +++++- .../translations/settings_search_index.json | 100 ++++++++++++++++++ 7 files changed, 239 insertions(+), 1 deletion(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 7f056f0eb..15042c947 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -176,10 +176,14 @@ Singleton { property int niriLayoutGapsOverride: -1 property int niriLayoutRadiusOverride: -1 property int niriLayoutBorderSize: -1 + property bool niriLayoutXrayEnabled: false + property bool niriLayoutBarXrayEnabled: true property int hyprlandLayoutGapsOverride: -1 property int hyprlandLayoutRadiusOverride: -1 property int hyprlandLayoutBorderSize: -1 property bool hyprlandResizeOnBorder: false + property bool hyprlandLayoutXrayEnabled: false + property bool hyprlandLayoutBarXrayEnabled: true property int mangoLayoutGapsOverride: -1 property int mangoLayoutRadiusOverride: -1 property int mangoLayoutBorderSize: -1 diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index fe09468fb..af5568602 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -28,10 +28,14 @@ var SPEC = { niriLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" }, niriLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" }, niriLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" }, + niriLayoutXrayEnabled: { def: false, onChange: "updateCompositorLayout" }, + niriLayoutBarXrayEnabled: { def: true, onChange: "updateCompositorLayout" }, hyprlandLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" }, hyprlandLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" }, hyprlandLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" }, hyprlandResizeOnBorder: { def: false, onChange: "updateCompositorLayout" }, + hyprlandLayoutXrayEnabled: { def: false, onChange: "updateCompositorLayout" }, + hyprlandLayoutBarXrayEnabled: { def: true, onChange: "updateCompositorLayout" }, mangoLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" }, mangoLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" }, mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" }, diff --git a/quickshell/Modules/Settings/CompositorLayoutTab.qml b/quickshell/Modules/Settings/CompositorLayoutTab.qml index e32919978..0dda6372d 100644 --- a/quickshell/Modules/Settings/CompositorLayoutTab.qml +++ b/quickshell/Modules/Settings/CompositorLayoutTab.qml @@ -310,6 +310,26 @@ Item { defaultValue: 2 onSliderValueChanged: newValue => SettingsData.set("niriLayoutBorderSize", newValue) } + + SettingsToggleRow { + tags: ["niri", "xray", "blur", "background-effect", "performance"] + settingKey: "niriLayoutXrayEnabled" + text: I18n.tr("Xray Blur Effect") + description: SettingsData.niriLayoutXrayEnabled ? I18n.tr("Xray on makes the windows background visible through to the wallpaper") : I18n.tr("Xray off shows the real background beneath the blurred surfaces") + checked: SettingsData.niriLayoutXrayEnabled + onToggled: checked => SettingsData.set("niriLayoutXrayEnabled", checked) + } + + SettingsToggleRow { + // Hidden in Frame Connected mode, where it has no target + visible: !(SettingsData.frameEnabled && SettingsData.frameMode === "connected") + tags: ["niri", "xray", "bar", "frame", "performance"] + settingKey: "niriLayoutBarXrayEnabled" + text: SettingsData.frameEnabled ? I18n.tr("Frame Xray") : I18n.tr("Dank Bar Xray") + description: SettingsData.niriLayoutBarXrayEnabled ? (SettingsData.frameEnabled ? I18n.tr("Xray on makes the Frame's border visible through to the wallpaper; more efficient") : I18n.tr("Xray on makes the Dank Bar visible through to the wallpaper; more efficient")) : (SettingsData.frameEnabled ? I18n.tr("Xray off shows the real background beneath the Frame's border") : I18n.tr("Xray off shows the real background beneath the Dank Bar")) + checked: SettingsData.niriLayoutBarXrayEnabled + onToggled: checked => SettingsData.set("niriLayoutBarXrayEnabled", checked) + } } SettingsCard { @@ -416,6 +436,26 @@ Item { checked: SettingsData.hyprlandResizeOnBorder onToggled: checked => SettingsData.set("hyprlandResizeOnBorder", checked) } + + SettingsToggleRow { + tags: ["hyprland", "xray", "blur", "background-effect", "performance"] + settingKey: "hyprlandLayoutXrayEnabled" + text: I18n.tr("Xray Blur Effect") + description: SettingsData.hyprlandLayoutXrayEnabled ? I18n.tr("Xray on makes the windows background visible through to the wallpaper") : I18n.tr("Xray off shows the real background beneath the blurred surfaces") + checked: SettingsData.hyprlandLayoutXrayEnabled + onToggled: checked => SettingsData.set("hyprlandLayoutXrayEnabled", checked) + } + + SettingsToggleRow { + // Hidden in Frame Connected mode, where it has no target + visible: !(SettingsData.frameEnabled && SettingsData.frameMode === "connected") + tags: ["hyprland", "xray", "bar", "frame", "performance"] + settingKey: "hyprlandLayoutBarXrayEnabled" + text: SettingsData.frameEnabled ? I18n.tr("Frame Xray") : I18n.tr("Dank Bar Xray") + description: SettingsData.hyprlandLayoutBarXrayEnabled ? (SettingsData.frameEnabled ? I18n.tr("Xray on makes the Frame's border visible through to the wallpaper; more efficient") : I18n.tr("Xray on makes the Dank Bar visible through to the wallpaper; more efficient")) : (SettingsData.frameEnabled ? I18n.tr("Xray off shows the real background beneath the Frame's border") : I18n.tr("Xray off shows the real background beneath the Dank Bar")) + checked: SettingsData.hyprlandLayoutBarXrayEnabled + onToggled: checked => SettingsData.set("hyprlandLayoutBarXrayEnabled", checked) + } } SettingsCard { diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml index 32042d79f..dc0c95c82 100644 --- a/quickshell/Modules/Settings/ThemeColorsTab.qml +++ b/quickshell/Modules/Settings/ThemeColorsTab.qml @@ -1812,6 +1812,40 @@ Item { defaultValue: 35 onSliderValueChanged: newValue => SettingsData.set("blurBorderOpacity", newValue / 100) } + + Item { + width: parent.width + height: xrayHintRow.implicitHeight + visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango + + Row { + id: xrayHintRow + width: parent.width + spacing: Theme.spacingS + + DankIcon { + name: "info" + size: Theme.iconSizeSmall + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + width: parent.width - Theme.iconSizeSmall - Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("Xray specific options for blurred surfaces live in Compositor → Layout") + font.pixelSize: Theme.fontSizeSmall + color: Theme.primary + wrapMode: Text.Wrap + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: PopoutService.openSettingsWithTab("compositor_layout") + } + } } SettingsCard { diff --git a/quickshell/Services/HyprlandService.qml b/quickshell/Services/HyprlandService.qml index 34a3d66e6..e0fb5cfa1 100644 --- a/quickshell/Services/HyprlandService.qml +++ b/quickshell/Services/HyprlandService.qml @@ -258,6 +258,14 @@ Singleton { const gaps = (typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutGapsOverride >= 0) ? SettingsData.hyprlandLayoutGapsOverride : defaultGaps; const borderSize = (typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutBorderSize >= 0) ? SettingsData.hyprlandLayoutBorderSize : defaultBorderSize; const resizeOnBorder = (typeof SettingsData !== "undefined" && SettingsData.hyprlandResizeOnBorder) ? true : false; + const xrayEnabled = typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutXrayEnabled; + const barFrameXrayEnabled = typeof SettingsData === "undefined" || SettingsData.hyprlandLayoutBarXrayEnabled; + const frameEnabled = typeof SettingsData !== "undefined" && SettingsData.frameEnabled; + const frameConnectedMode = frameEnabled && SettingsData.frameMode === "connected"; + + // Hyprland's `xray = false` is still in early development, so unlike niri we never emit it (unset already + // samples real content) - we only force xray=true + const barFrameTargetNamespace = !frameEnabled ? "dms:bar" : (frameConnectedMode ? null : "dms:frame"); let content = `-- Auto-generated by DMS — do not edit manually @@ -274,6 +282,23 @@ hl.config({ }) `; + if (xrayEnabled) { + content += ` +hl.layer_rule({ + match = { namespace = "^dms:.*$" }, + xray = true, +}) +`; + } + if (barFrameXrayEnabled && barFrameTargetNamespace) { + content += ` +hl.layer_rule({ + match = { namespace = "^${barFrameTargetNamespace}$" }, + xray = true, +}) +`; + } + Proc.runCommand("hypr-write-layout", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${layoutPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { if (exitCode !== 0) { log.warn("Failed to write layout config:", output); diff --git a/quickshell/Services/NiriService.qml b/quickshell/Services/NiriService.qml index 3696a39b8..7d1f41957 100644 --- a/quickshell/Services/NiriService.qml +++ b/quickshell/Services/NiriService.qml @@ -1087,6 +1087,37 @@ Singleton { const cornerRadius = (typeof SettingsData !== "undefined" && SettingsData.niriLayoutRadiusOverride >= 0) ? SettingsData.niriLayoutRadiusOverride : defaultRadius; const gaps = (typeof SettingsData !== "undefined" && SettingsData.niriLayoutGapsOverride >= 0) ? SettingsData.niriLayoutGapsOverride : defaultGaps; const borderSize = (typeof SettingsData !== "undefined" && SettingsData.niriLayoutBorderSize >= 0) ? SettingsData.niriLayoutBorderSize : defaultBorderSize; + const xrayEnabled = typeof SettingsData !== "undefined" && SettingsData.niriLayoutXrayEnabled; + const barFrameXrayEnabled = typeof SettingsData === "undefined" || SettingsData.niriLayoutBarXrayEnabled; + const frameEnabled = typeof SettingsData !== "undefined" && SettingsData.frameEnabled; + const frameConnectedMode = frameEnabled && SettingsData.frameMode === "connected"; + + // The one wallpaper-flush surface to force xray=true on: bar (Frame off) or Frame's Separate-mode border + const barFrameTargetNamespace = !frameEnabled ? "dms:bar" : (frameConnectedMode ? null : "dms:frame"); + + let xrayRules = ""; + if (!xrayEnabled) { + let excludes = ""; + if (frameEnabled) + excludes += ` + exclude namespace="^dms:bar$"`; + xrayRules += ` + layer-rule { + match namespace=".*"` + excludes + ` + background-effect { + xray false + } + }`; + } + if (barFrameXrayEnabled && barFrameTargetNamespace) { + xrayRules += ` + layer-rule { + match namespace="^${barFrameTargetNamespace}$" + background-effect { + xray true + } + }`; + } const dmsWarning = `// ! DO NOT EDIT ! // ! AUTO-GENERATED BY DMS ! @@ -1111,7 +1142,7 @@ Singleton { clip-to-geometry true tiled-state true draw-border-with-background false - }`; + }` + xrayRules; const alttabContent = dmsWarning + `recent-windows { highlight { diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index ad2cc8140..c20877882 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -8642,6 +8642,46 @@ ], "description": "Width of window border and focus ring" }, + { + "section": "hyprlandLayoutBarXrayEnabled", + "label": "Frame Xray", + "tabIndex": 37, + "category": "Personalization", + "keywords": [ + "appearance", + "bar", + "custom", + "customize", + "frame", + "hyprland", + "makes", + "performance", + "personal", + "personalization", + "xray" + ], + "description": "Xray on makes the Frame" + }, + { + "section": "niriLayoutBarXrayEnabled", + "label": "Frame Xray", + "tabIndex": 37, + "category": "Personalization", + "keywords": [ + "appearance", + "bar", + "custom", + "customize", + "frame", + "makes", + "niri", + "performance", + "personal", + "personalization", + "xray" + ], + "description": "Xray on makes the Frame" + }, { "section": "hyprlandLayout", "label": "Hyprland Layout Overrides", @@ -9119,6 +9159,66 @@ ], "description": "Space between windows" }, + { + "section": "hyprlandLayoutXrayEnabled", + "label": "Xray Blur Effect", + "tabIndex": 37, + "category": "Personalization", + "keywords": [ + "appearance", + "background", + "background-effect", + "bg", + "blur", + "custom", + "customize", + "desktop", + "effect", + "hyprland", + "image", + "makes", + "performance", + "personal", + "personalization", + "picture", + "through", + "visible", + "wallpaper", + "windows", + "xray" + ], + "description": "Xray on makes the windows background visible through to the wallpaper" + }, + { + "section": "niriLayoutXrayEnabled", + "label": "Xray Blur Effect", + "tabIndex": 37, + "category": "Personalization", + "keywords": [ + "appearance", + "background", + "background-effect", + "bg", + "blur", + "custom", + "customize", + "desktop", + "effect", + "image", + "makes", + "niri", + "performance", + "personal", + "personalization", + "picture", + "through", + "visible", + "wallpaper", + "windows", + "xray" + ], + "description": "Xray on makes the windows background visible through to the wallpaper" + }, { "section": "windowRules", "label": "Window Rules",