1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

niri: ability to blur wallpaper on overview + add a separate layer for

blurred wallpapers
This commit is contained in:
bbedward
2025-10-24 12:37:57 -04:00
parent 1abd3ef8b1
commit ca2acbc704
9 changed files with 323 additions and 132 deletions

View File

@@ -66,6 +66,8 @@ Singleton {
property int animationSpeed: SettingsData.AnimationSpeed.Short
property int customAnimationDuration: 500
property string wallpaperFillMode: "Fill"
property bool blurredWallpaperLayer: false
property bool blurWallpaperOnOverview: false
property bool showLauncherButton: true
property bool showWorkspaceSwitcher: true
@@ -497,6 +499,8 @@ Singleton {
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
showOnLastDisplay = settings.showOnLastDisplay !== undefined ? settings.showOnLastDisplay : ({})
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill"
blurredWallpaperLayer = settings.blurredWallpaperLayer !== undefined ? settings.blurredWallpaperLayer : false
blurWallpaperOnOverview = settings.blurWallpaperOnOverview !== undefined ? settings.blurWallpaperOnOverview : false
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short
customAnimationDuration = settings.customAnimationDuration !== undefined ? settings.customAnimationDuration : 500
acMonitorTimeout = settings.acMonitorTimeout !== undefined ? settings.acMonitorTimeout : 0
@@ -663,6 +667,8 @@ Singleton {
"widgetBackgroundColor": widgetBackgroundColor,
"surfaceBase": surfaceBase,
"wallpaperFillMode": wallpaperFillMode,
"blurredWallpaperLayer": blurredWallpaperLayer,
"blurWallpaperOnOverview": blurWallpaperOnOverview,
"notificationTimeoutLow": notificationTimeoutLow,
"notificationTimeoutNormal": notificationTimeoutNormal,
"notificationTimeoutCritical": notificationTimeoutCritical,
@@ -741,7 +747,7 @@ Singleton {
"dankBarBorderOpacity", "dankBarBorderThickness", "popupGapsAuto", "popupGapsManual",
"dankBarPosition", "lockScreenShowPowerActions", "enableFprint", "maxFprintTries",
"hideBrightnessSlider", "widgetBackgroundColor", "surfaceBase", "wallpaperFillMode",
"notificationTimeoutLow", "notificationTimeoutNormal", "notificationTimeoutCritical",
"blurredWallpaperLayer", "blurWallpaperOnOverview", "notificationTimeoutLow", "notificationTimeoutNormal", "notificationTimeoutCritical",
"notificationPopupPosition", "osdAlwaysShowValue", "powerActionConfirm",
"customPowerActionLock", "customPowerActionLogout", "customPowerActionSuspend",
"customPowerActionHibernate", "customPowerActionReboot", "customPowerActionPowerOff",
@@ -1088,6 +1094,16 @@ Singleton {
saveSettings()
}
function setBlurredWallpaperLayer(enabled) {
blurredWallpaperLayer = enabled
saveSettings()
}
function setBlurWallpaperOnOverview(enabled) {
blurWallpaperOnOverview = enabled
saveSettings()
}
function setShowLauncherButton(enabled) {
showLauncherButton = enabled
saveSettings()

View File

@@ -52,6 +52,14 @@ Item {
}
}
Loader {
id: blurredWallpaperBackgroundLoader
active: SettingsData.blurredWallpaperLayer
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
WallpaperBackground {}
Lock {

View File

@@ -0,0 +1,135 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules
Variants {
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens
}
return SettingsData.getFilteredScreens("wallpaper")
}
PanelWindow {
id: blurWallpaperWindow
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.namespace: "dms:blurwallpaper"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
anchors.top: true
anchors.bottom: true
anchors.left: true
anchors.right: true
color: "transparent"
Item {
id: root
anchors.fill: parent
property string source: SessionData.getMonitorWallpaper(modelData.name) || ""
property bool isColorSource: source.startsWith("#")
Connections {
target: SessionData
function onIsLightModeChanged() {
if (SessionData.perModeWallpaper) {
var newSource = SessionData.getMonitorWallpaper(modelData.name) || ""
if (newSource !== root.source) {
root.source = newSource
}
}
}
}
function getFillMode(modeName) {
switch(modeName) {
case "Stretch": return Image.Stretch
case "Fit":
case "PreserveAspectFit": return Image.PreserveAspectFit
case "Fill":
case "PreserveAspectCrop": return Image.PreserveAspectCrop
case "Tile": return Image.Tile
case "TileVertically": return Image.TileVertically
case "TileHorizontally": return Image.TileHorizontally
case "Pad": return Image.Pad
default: return Image.PreserveAspectCrop
}
}
WallpaperEngineProc {
id: weProc
monitor: modelData.name
}
Component.onCompleted: {
if (source) {
const formattedSource = source.startsWith("file://") ? source : "file://" + source
wallpaperImage.source = formattedSource
}
}
Component.onDestruction: {
weProc.stop()
}
onSourceChanged: {
const isWE = source.startsWith("we:")
const isColor = source.startsWith("#")
if (isWE) {
wallpaperImage.source = ""
weProc.start(source.substring(3))
} else {
weProc.stop()
if (!source) {
wallpaperImage.source = ""
} else if (isColor) {
wallpaperImage.source = ""
} else {
wallpaperImage.source = source.startsWith("file://") ? source : "file://" + source
}
}
}
Loader {
anchors.fill: parent
active: !root.source || root.isColorSource
asynchronous: true
sourceComponent: DankBackdrop {
screenName: modelData.name
}
}
Image {
id: wallpaperImage
anchors.fill: parent
visible: false
asynchronous: true
smooth: true
cache: true
fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
}
MultiEffect {
anchors.fill: parent
source: wallpaperImage
blurEnabled: true
blur: 0.8
blurMax: 48
}
}
}
}

View File

@@ -332,7 +332,10 @@ Column {
return "Select device"
if (AudioService.sink.audio.muted)
return "Muted"
return Math.round(AudioService.sink.audio.volume * 100) + "%"
const volume = AudioService.sink.audio.volume
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
}
case "audioInput":
{
@@ -340,7 +343,10 @@ Column {
return "Select device"
if (AudioService.source.audio.muted)
return "Muted"
return Math.round(AudioService.source.audio.volume * 100) + "%"
const volume = AudioService.source.audio.volume
if (typeof volume !== "number" || isNaN(volume))
return "0%"
return Math.round(volume * 100) + "%"
}
default:
return widgetDef?.description || ""

View File

@@ -1,63 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSource: AudioService.source
iconName: {
if (!defaultSource) return "mic_off"
let volume = defaultSource.audio.volume
let muted = defaultSource.audio.muted
if (muted || volume === 0.0) return "mic_off"
return "mic"
}
isActive: defaultSource && !defaultSource.audio.muted
primaryText: {
if (!defaultSource) {
return "No input device"
}
return defaultSource.description || "Audio Input"
}
secondaryText: {
if (!defaultSource) {
return "Select device"
}
if (defaultSource.audio.muted) {
return "Muted"
}
return Math.round(defaultSource.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSource && defaultSource.audio) {
defaultSource.audio.muted = !defaultSource.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSource || !defaultSource.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSource.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSource.audio.muted = false
defaultSource.audio.volume = newVolume / 100
wheelEvent.accepted = true
}
}

View File

@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var defaultSink: AudioService.sink
iconName: {
if (!defaultSink) return "volume_off"
let volume = defaultSink.audio.volume
let muted = defaultSink.audio.muted
if (muted || volume === 0.0) return "volume_off"
if (volume <= 0.33) return "volume_down"
if (volume <= 0.66) return "volume_up"
return "volume_up"
}
isActive: defaultSink && !defaultSink.audio.muted
primaryText: {
if (!defaultSink) {
return "No output device"
}
return defaultSink.description || "Audio Output"
}
secondaryText: {
if (!defaultSink) {
return "Select device"
}
if (defaultSink.audio.muted) {
return "Muted"
}
return Math.round(defaultSink.audio.volume * 100) + "%"
}
onToggled: {
if (defaultSink && defaultSink.audio) {
defaultSink.audio.muted = !defaultSink.audio.muted
}
}
onWheelEvent: function (wheelEvent) {
if (!defaultSink || !defaultSink.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = defaultSink.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
defaultSink.audio.muted = false
defaultSink.audio.volume = newVolume / 100
AudioService.volumeChanged()
wheelEvent.accepted = true
}
}

View File

@@ -460,6 +460,58 @@ Item {
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.2
visible: CompositorService.isNiri
}
Row {
width: parent.width
spacing: Theme.spacingM
visible: CompositorService.isNiri
DankIcon {
name: "blur_on"
size: Theme.iconSize
color: SettingsData.blurWallpaperOnOverview ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - blurOverviewToggle.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Blur on Overview")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Blur wallpaper when niri overview is open")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
DankToggle {
id: blurOverviewToggle
anchors.verticalCenter: parent.verticalCenter
checked: SettingsData.blurWallpaperOnOverview
onToggled: checked => {
SettingsData.setBlurWallpaperOnOverview(checked)
}
}
}
// Per-Mode Wallpaper Section - Full Width
Rectangle {
width: parent.width
@@ -959,6 +1011,67 @@ Item {
}
}
StyledRect {
width: parent.width
height: blurLayerColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
visible: CompositorService.isNiri
Column {
id: blurLayerColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "blur_on"
size: Theme.iconSize
color: SettingsData.blurredWallpaperLayer ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - blurLayerToggle.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Blur Layer")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
DankToggle {
id: blurLayerToggle
anchors.verticalCenter: parent.verticalCenter
checked: SettingsData.blurredWallpaperLayer
onToggled: checked => {
SettingsData.setBlurredWallpaperLayer(checked)
}
}
}
}
}
StyledRect {
width: parent.width
height: lightModeRow.implicitHeight + Theme.spacingL * 2

View File

@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
@@ -6,6 +7,7 @@ import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modules
import qs.Services
Variants {
model: {
@@ -467,6 +469,15 @@ Variants {
})
}
}
MultiEffect {
anchors.fill: parent
source: effectLoader.active ? effectLoader.item : (root.actualTransitionType === "none" ? currentWallpaper : null)
visible: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview
blurEnabled: true
blur: 0.8
blurMax: 48
}
}
}
}

View File

@@ -439,6 +439,37 @@ binds {
spawn "dms" "ipc" "call" "night" "toggle";
}
}
// You probably want this too
debug {
honor-xdg-activation-with-invalid-serial
}
```
#### niri - place wallpaper on overview (blurred or non-blurred)
Place the wallpaper on backdrop using a layer-rule for a contiguous surface.
```kdl
layer-rule {
match namespace="quickshell"
place-within-backdrop true
}
```
If using "Blur Layer" option, you may want the blurred layer to appear on overview only, that can be done with some layer rules:
```kdl
layer-rule {
match namespace="dms:blurwallpaper"
opacity 0.0
}
layer-rule {
match namespace="dms:blurwallpaper"
place-within-backdrop true
opacity 1.0
}
```
#### niri theming