mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
330 lines
11 KiB
QML
330 lines
11 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import qs.Common
|
|
import qs.Widgets
|
|
import qs.Services
|
|
|
|
Variants {
|
|
readonly property var log: Log.scoped("BlurredWallpaperBackground")
|
|
model: 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"
|
|
|
|
visible: root.contentReady && !root.surfaceBounce
|
|
|
|
updatesEnabled: root.renderActive || root._settleFrames > 0
|
|
|
|
mask: Region {
|
|
item: Item {}
|
|
}
|
|
|
|
Item {
|
|
id: root
|
|
anchors.fill: parent
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: SettingsData.effectiveWallpaperBackgroundColor
|
|
}
|
|
|
|
function encodeFileUrl(path) {
|
|
if (!path)
|
|
return "";
|
|
return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
|
|
}
|
|
|
|
property string source: SessionData.getMonitorWallpaper(modelData.name) || ""
|
|
readonly property bool isColorSource: source.startsWith("#")
|
|
readonly property string displaySource: {
|
|
if (!source || isColorSource)
|
|
return "";
|
|
return source.startsWith("file://") ? source : encodeFileUrl(source);
|
|
}
|
|
property bool contentReady: false
|
|
property bool surfaceBounce: false
|
|
|
|
// Live stack is captured into frozenLayer once stable, then unloaded; if the capture stalls the stack just stays live.
|
|
property bool liveActive: false
|
|
property bool frozenValid: false
|
|
property string _frozenSource: ""
|
|
property bool loadFailed: false
|
|
property int _freezeWaitFrames: 0
|
|
|
|
readonly property var backingWindow: Window.window
|
|
readonly property bool renderActive: !source || liveActive || _freezeWaitFrames > 0
|
|
property int _settleFrames: 3
|
|
|
|
readonly property int maxTextureSize: 8192
|
|
readonly property int textureWidth: Math.min(modelData.width, maxTextureSize)
|
|
readonly property int textureHeight: Math.min(modelData.height, maxTextureSize)
|
|
|
|
Component.onCompleted: {
|
|
if (!displaySource) {
|
|
contentReady = true;
|
|
return;
|
|
}
|
|
liveActive = true;
|
|
}
|
|
|
|
onDisplaySourceChanged: {
|
|
invalidate();
|
|
loadFailed = false;
|
|
_freezeWaitFrames = 0;
|
|
if (!displaySource) {
|
|
liveActive = false;
|
|
frozenValid = false;
|
|
_frozenSource = "";
|
|
contentReady = true;
|
|
return;
|
|
}
|
|
liveActive = true;
|
|
}
|
|
|
|
function regenerate() {
|
|
invalidate();
|
|
if (!displaySource)
|
|
return;
|
|
if (liveActive) {
|
|
scheduleFreeze();
|
|
return;
|
|
}
|
|
liveActive = true;
|
|
}
|
|
|
|
function handleDisplayable() {
|
|
contentReady = true;
|
|
if (liveLoader.item?.currentFailed) {
|
|
if (!frozenValid)
|
|
loadFailed = true;
|
|
liveActive = false;
|
|
}
|
|
invalidate();
|
|
}
|
|
|
|
function scheduleFreeze() {
|
|
if (!liveLoader.item?.stable)
|
|
return;
|
|
frozenLayer.scheduleUpdate();
|
|
_freezeWaitFrames = 3;
|
|
_settleFrames = 3;
|
|
// No wedge watchdog: an occluded surface may never produce frames, the freeze just waits
|
|
backingWindow?.update();
|
|
}
|
|
|
|
function completeFreeze() {
|
|
const live = liveLoader.item;
|
|
if (!live || !live.stable)
|
|
return;
|
|
frozenValid = true;
|
|
_frozenSource = displaySource;
|
|
liveActive = false;
|
|
log.info("froze blur layer for", modelData.name);
|
|
invalidate();
|
|
}
|
|
|
|
onTextureWidthChanged: regenerate()
|
|
onTextureHeightChanged: regenerate()
|
|
|
|
function invalidate() {
|
|
_settleFrames = 3;
|
|
backingWindow?.update();
|
|
if (!_wedgeBounced)
|
|
wedgeWatchdog.restart();
|
|
}
|
|
|
|
onRenderActiveChanged: invalidate()
|
|
onBackingWindowChanged: invalidate()
|
|
|
|
// Same wedge recovery as WallpaperBackground
|
|
property bool _wedgeBounced: false
|
|
|
|
Timer {
|
|
id: wedgeWatchdog
|
|
interval: 3000
|
|
repeat: false
|
|
onTriggered: {
|
|
if (!root.backingWindow || !blurWallpaperWindow.visible || IdleService.isShellLocked)
|
|
return;
|
|
log.warn("no frame swapped on", modelData.name, "since last invalidate, re-attaching surface");
|
|
root._wedgeBounced = true;
|
|
surfaceReattach.restart();
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: surfaceReattach
|
|
interval: 0
|
|
repeat: false
|
|
onTriggered: {
|
|
root.surfaceBounce = true;
|
|
Qt.callLater(() => {
|
|
root.surfaceBounce = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.backingWindow
|
|
function onFrameSwapped() {
|
|
if (root._settleFrames > 0)
|
|
root._settleFrames--;
|
|
root._wedgeBounced = false;
|
|
wedgeWatchdog.stop();
|
|
if (root._freezeWaitFrames > 0 && --root._freezeWaitFrames === 0)
|
|
root.completeFreeze();
|
|
}
|
|
function onVisibleChanged() {
|
|
root.invalidate();
|
|
}
|
|
function onWidthChanged() {
|
|
root.regenerate();
|
|
}
|
|
function onHeightChanged() {
|
|
root.regenerate();
|
|
}
|
|
function onResourcesLost() {
|
|
root.frozenValid = false;
|
|
root.regenerate();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: Quickshell
|
|
function onScreensChanged() {
|
|
root.regenerate();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: SettingsData
|
|
function onWallpaperFillModeChanged() {
|
|
root.regenerate();
|
|
}
|
|
function onEffectiveWallpaperBackgroundColorChanged() {
|
|
root.invalidate();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: SessionData
|
|
function onIsLightModeChanged() {
|
|
if (SessionData.perModeWallpaper) {
|
|
var newSource = SessionData.getMonitorWallpaper(modelData.name) || "";
|
|
if (newSource !== root.source) {
|
|
root.source = newSource;
|
|
}
|
|
}
|
|
}
|
|
function onMonitorWallpaperFillModesChanged() {
|
|
root.regenerate();
|
|
}
|
|
function onPerMonitorWallpaperChanged() {
|
|
root.regenerate();
|
|
}
|
|
}
|
|
|
|
// Theme changes repaint DankBackdrop but nothing else wakes the render loop
|
|
Connections {
|
|
target: Theme
|
|
enabled: root.isColorSource || root.loadFailed
|
|
function onPrimaryChanged() {
|
|
root.invalidate();
|
|
}
|
|
function onBackgroundChanged() {
|
|
root.invalidate();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: IdleService
|
|
function onIsShellLockedChanged() {
|
|
if (IdleService.isShellLocked)
|
|
return;
|
|
root.invalidate();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: liveLoader.item
|
|
function onBecameDisplayable() {
|
|
root.handleDisplayable();
|
|
}
|
|
function onStableChanged() {
|
|
if (liveLoader.item.stable)
|
|
root.scheduleFreeze();
|
|
}
|
|
function onTransitioningChanged() {
|
|
root.invalidate();
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
active: !root.source || root.isColorSource || root.loadFailed
|
|
asynchronous: true
|
|
|
|
sourceComponent: DankBackdrop {
|
|
screenName: modelData.name
|
|
}
|
|
}
|
|
|
|
ShaderEffectSource {
|
|
id: frozenLayer
|
|
anchors.fill: parent
|
|
sourceItem: liveContainer
|
|
live: false
|
|
mipmap: false
|
|
recursive: false
|
|
smooth: true
|
|
visible: root.frozenValid || root.liveActive
|
|
textureSize: Qt.size(root.textureWidth, root.textureHeight)
|
|
}
|
|
|
|
Item {
|
|
id: liveContainer
|
|
anchors.fill: parent
|
|
visible: root.liveActive
|
|
|
|
Loader {
|
|
id: liveLoader
|
|
anchors.fill: parent
|
|
active: root.liveActive
|
|
asynchronous: false
|
|
|
|
// Cached images reach Ready synchronously during creation, before Connections retargets
|
|
onLoaded: {
|
|
if (item.displayableNow)
|
|
root.handleDisplayable();
|
|
if (item.stable)
|
|
root.scheduleFreeze();
|
|
}
|
|
|
|
sourceComponent: BlurredWallpaperLive {
|
|
wallpaperSource: root.displaySource
|
|
initialSource: root._frozenSource
|
|
screenName: modelData.name
|
|
blurTextureSize: Qt.size(root.textureWidth, root.textureHeight)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|