1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-06 21:48:30 -04:00

wallpaper: more resilience to updatesEnabled missing expose/update

events
This commit is contained in:
bbedward
2026-07-06 15:57:19 -04:00
parent 405ea708b3
commit 3e481a566b
5 changed files with 208 additions and 147 deletions
+5 -3
View File
@@ -68,6 +68,8 @@ func GetQtLoggingRules() string {
level = "info" level = "info"
} }
// scene carries QML engine warnings (e.g. QQuickImage "Cannot open" cache
// probes); suppressed except at debug level
var rules []string var rules []string
switch strings.ToLower(level) { switch strings.ToLower(level) {
case "fatal": case "fatal":
@@ -75,13 +77,13 @@ func GetQtLoggingRules() string {
case "error": case "error":
rules = []string{"*.debug=false", "*.info=false", "*.warning=false"} rules = []string{"*.debug=false", "*.info=false", "*.warning=false"}
case "warn", "warning": case "warn", "warning":
rules = []string{"*.debug=false", "*.info=false"} rules = []string{"*.debug=false", "*.info=false", "scene.warning=false"}
case "info": case "info":
rules = []string{"*.debug=false"} rules = []string{"*.debug=false", "scene.warning=false"}
case "debug": case "debug":
return "" return ""
default: default:
rules = []string{"*.debug=false"} rules = []string{"*.debug=false", "scene.warning=false"}
} }
return strings.Join(rules, ";") return strings.Join(rules, ";")
@@ -1,5 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Effects import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
@@ -98,25 +98,25 @@ StyledRect {
} }
property string _videoThumb: "" property string _videoThumb: ""
property bool _thumbGenAttempted: false
// Probe the thumbnail optimistically; Image.Error triggers generation
onVideoThumbnailPathChanged: { onVideoThumbnailPathChanged: {
_videoThumb = ""; _thumbGenAttempted = false;
if (!videoThumbnailPath) _videoThumb = videoThumbnailPath;
}
function generateVideoThumbnail() {
if (_thumbGenAttempted)
return; return;
_thumbGenAttempted = true;
_videoThumb = "";
const thumbPath = videoThumbnailPath; const thumbPath = videoThumbnailPath;
const thumbDir = _xdgCacheHome + "/thumbnails/" + _thumbnailSize; const thumbDir = _xdgCacheHome + "/thumbnails/" + _thumbnailSize;
const size = _thumbnailPx; const script = "mkdir -p \"$1\" && ffmpegthumbnailer -i \"$2\" -o \"$3\" -s " + _thumbnailPx + " -f";
const fp = delegateRoot.filePath; Proc.runCommand(null, ["sh", "-c", script, "thumb", thumbDir, delegateRoot.filePath, thumbPath], function (output, exitCode) {
Paths.mkdir(thumbDir); if (exitCode === 0)
Proc.runCommand(null, ["test", "-f", thumbPath], function (output, exitCode) {
if (exitCode === 0) {
_videoThumb = thumbPath; _videoThumb = thumbPath;
} else {
Proc.runCommand(null, ["ffmpegthumbnailer", "-i", fp, "-o", thumbPath, "-s", String(size), "-f"], function (output, exitCode) {
if (exitCode === 0)
_videoThumb = thumbPath;
});
}
}); });
} }
@@ -160,75 +160,52 @@ StyledRect {
height: weMode ? 165 : (iconSizes[iconSizeIndex] - 8) height: weMode ? 165 : (iconSizes[iconSizeIndex] - 8)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Image { ClippingRectangle {
id: gridPreviewImage
anchors.fill: parent anchors.fill: parent
anchors.margins: 2 anchors.margins: 2
property var weExtensions: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tga", ".jxl", ".avif", ".heif", ".exr"] radius: Theme.cornerRadius
property int weExtIndex: 0 color: "transparent"
property string imagePath: {
if (weMode && delegateRoot.fileIsDir)
return delegateRoot.filePath + "/preview" + weExtensions[weExtIndex];
if (_videoThumb)
return _videoThumb;
return "";
}
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
onStatusChanged: {
if (weMode && delegateRoot.fileIsDir && status === Image.Error) {
if (weExtIndex < weExtensions.length - 1) {
weExtIndex++;
} else {
imagePath = "";
}
}
}
fillMode: Image.PreserveAspectCrop
sourceSize.width: weMode ? 225 : iconSizes[iconSizeIndex]
sourceSize.height: weMode ? 225 : iconSizes[iconSizeIndex]
asynchronous: true
visible: false
}
CachingImage { Image {
anchors.fill: parent id: gridPreviewImage
anchors.margins: 2
imagePath: !delegateRoot.fileIsDir && isImage ? delegateRoot.filePath : ""
maxCacheSize: 256
visible: !delegateRoot.fileIsDir && isImage
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: gridImageMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
MultiEffect {
anchors.fill: parent
anchors.margins: 2
source: gridPreviewImage
maskEnabled: true
maskSource: gridImageMask
visible: gridPreviewImage.status === Image.Ready && ((!delegateRoot.fileIsDir && isVideo) || (weMode && delegateRoot.fileIsDir))
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: gridImageMask
anchors.fill: parent
anchors.margins: 2
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius property var weExtensions: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tga", ".jxl", ".avif", ".heif", ".exr"]
color: "black" property int weExtIndex: 0
antialiasing: true property string imagePath: {
if (weMode && delegateRoot.fileIsDir)
return delegateRoot.filePath + "/preview" + weExtensions[weExtIndex];
if (_videoThumb)
return _videoThumb;
return "";
}
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
onStatusChanged: {
if (status !== Image.Error)
return;
if (weMode && delegateRoot.fileIsDir) {
if (weExtIndex < weExtensions.length - 1) {
weExtIndex++;
} else {
imagePath = "";
}
return;
}
if (_videoThumb)
generateVideoThumbnail();
}
fillMode: Image.PreserveAspectCrop
sourceSize.width: weMode ? 225 : iconSizes[iconSizeIndex]
sourceSize.height: weMode ? 225 : iconSizes[iconSizeIndex]
asynchronous: true
visible: status === Image.Ready && ((!delegateRoot.fileIsDir && isVideo) || (weMode && delegateRoot.fileIsDir))
}
CachingImage {
anchors.fill: parent
imagePath: !delegateRoot.fileIsDir && isImage ? delegateRoot.filePath : ""
maxCacheSize: 256
animate: false
visible: !delegateRoot.fileIsDir && isImage
} }
} }
@@ -1,5 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Effects import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
@@ -95,23 +95,25 @@ StyledRect {
} }
property string _videoThumb: "" property string _videoThumb: ""
property bool _thumbGenAttempted: false
// Probe the thumbnail optimistically; Image.Error triggers generation
onVideoThumbnailPathChanged: { onVideoThumbnailPathChanged: {
_videoThumb = ""; _thumbGenAttempted = false;
if (!videoThumbnailPath) _videoThumb = videoThumbnailPath;
}
function generateVideoThumbnail() {
if (_thumbGenAttempted)
return; return;
_thumbGenAttempted = true;
_videoThumb = "";
const thumbPath = videoThumbnailPath; const thumbPath = videoThumbnailPath;
const fp = listDelegateRoot.filePath; const thumbDir = _xdgCacheHome + "/thumbnails/normal";
Paths.mkdir(_xdgCacheHome + "/thumbnails/normal"); const script = "mkdir -p \"$1\" && ffmpegthumbnailer -i \"$2\" -o \"$3\" -s 128 -f";
Proc.runCommand(null, ["test", "-f", thumbPath], function (output, exitCode) { Proc.runCommand(null, ["sh", "-c", script, "thumb", thumbDir, listDelegateRoot.filePath, thumbPath], function (output, exitCode) {
if (exitCode === 0) { if (exitCode === 0)
_videoThumb = thumbPath; _videoThumb = thumbPath;
} else {
Proc.runCommand(null, ["ffmpegthumbnailer", "-i", fp, "-o", thumbPath, "-s", "128", "-f"], function (output, exitCode) {
if (exitCode === 0)
_videoThumb = thumbPath;
});
}
}); });
} }
@@ -165,54 +167,33 @@ StyledRect {
height: 28 height: 28
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Image { ClippingRectangle {
id: listPreviewImage
anchors.fill: parent anchors.fill: parent
property string imagePath: _videoThumb radius: Theme.cornerRadius
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : "" color: "transparent"
fillMode: Image.PreserveAspectCrop
sourceSize.width: 32
sourceSize.height: 32
asynchronous: true
visible: false
}
CachingImage { Image {
anchors.fill: parent id: listPreviewImage
imagePath: !listDelegateRoot.fileIsDir && isImage ? listDelegateRoot.filePath : ""
maxCacheSize: 256
visible: !listDelegateRoot.fileIsDir && isImage
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: listImageMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
MultiEffect {
anchors.fill: parent
source: listPreviewImage
maskEnabled: true
maskSource: listImageMask
visible: listPreviewImage.status === Image.Ready && !listDelegateRoot.fileIsDir && isVideo
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: listImageMask
anchors.fill: parent
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius property string imagePath: _videoThumb
color: "black" source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
antialiasing: true fillMode: Image.PreserveAspectCrop
sourceSize.width: 32
sourceSize.height: 32
asynchronous: true
visible: status === Image.Ready && !listDelegateRoot.fileIsDir && isVideo
onStatusChanged: {
if (status === Image.Error && _videoThumb)
generateVideoThumbnail();
}
}
CachingImage {
anchors.fill: parent
imagePath: !listDelegateRoot.fileIsDir && isImage ? listDelegateRoot.filePath : ""
maxCacheSize: 256
animate: false
visible: !listDelegateRoot.fileIsDir && isImage
} }
} }
@@ -108,16 +108,48 @@ Variants {
function invalidate() { function invalidate() {
_settleFrames = 3; _settleFrames = 3;
backingWindow?.update(); backingWindow?.update();
if (!_wedgeBounced)
wedgeWatchdog.restart();
} }
onRenderActiveChanged: invalidate() onRenderActiveChanged: invalidate()
onBackingWindowChanged: 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: {
blurWallpaperWindow.visible = false;
Qt.callLater(() => {
blurWallpaperWindow.visible = true;
});
}
}
Connections { Connections {
target: root.backingWindow target: root.backingWindow
function onFrameSwapped() { function onFrameSwapped() {
if (root._settleFrames > 0) if (root._settleFrames > 0)
root._settleFrames--; root._settleFrames--;
root._wedgeBounced = false;
wedgeWatchdog.stop();
} }
function onVisibleChanged() { function onVisibleChanged() {
root.invalidate(); root.invalidate();
@@ -142,10 +174,29 @@ Variants {
function onWallpaperFillModeChanged() { function onWallpaperFillModeChanged() {
root.invalidate(); root.invalidate();
} }
function onWallpaperBackgroundColorModeChanged() { function onEffectiveWallpaperBackgroundColorChanged() {
root.invalidate(); root.invalidate();
} }
function onWallpaperBackgroundCustomColorChanged() { }
Connections {
target: SessionData
function onMonitorWallpaperFillModesChanged() {
root.invalidate();
}
function onPerMonitorWallpaperChanged() {
root.invalidate();
}
}
// Theme changes repaint DankBackdrop but nothing else wakes the render loop
Connections {
target: Theme
enabled: root.isColorSource || currentWallpaper.status === Image.Error
function onPrimaryChanged() {
root.invalidate();
}
function onBackgroundChanged() {
root.invalidate(); root.invalidate();
} }
} }
@@ -170,6 +221,7 @@ Variants {
} }
onSourceChanged: { onSourceChanged: {
invalidate();
if (!source || source.startsWith("#")) { if (!source || source.startsWith("#")) {
setWallpaperImmediate(""); setWallpaperImmediate("");
return; return;
@@ -190,6 +242,7 @@ Variants {
} }
function setWallpaperImmediate(newSource) { function setWallpaperImmediate(newSource) {
transitionDelayTimer.stop();
transitionAnimation.stop(); transitionAnimation.stop();
root.transitionProgress = 0.0; root.transitionProgress = 0.0;
root.effectActive = false; root.effectActive = false;
+53 -5
View File
@@ -141,16 +141,37 @@ Variants {
function invalidate() { function invalidate() {
_settleFrames = 3; _settleFrames = 3;
backingWindow?.update(); backingWindow?.update();
if (!_wedgeBounced)
wedgeWatchdog.restart();
} }
onRenderActiveChanged: invalidate() onRenderActiveChanged: invalidate()
onBackingWindowChanged: invalidate() onBackingWindowChanged: invalidate()
// No swap after a requested frame: dropped frame callback left the window
// unexposed (qtwayland mFrameCallbackTimedOut); only surface re-attach recovers.
property bool _wedgeBounced: false
Timer {
id: wedgeWatchdog
interval: 3000
repeat: false
onTriggered: {
if (!root.backingWindow || !wallpaperWindow.visible || IdleService.isShellLocked)
return;
log.warn("no frame swapped on", modelData.name, "since last invalidate, re-attaching surface");
root._wedgeBounced = true;
surfaceReattach.restart();
}
}
Connections { Connections {
target: root.backingWindow target: root.backingWindow
function onFrameSwapped() { function onFrameSwapped() {
if (root._settleFrames > 0) if (root._settleFrames > 0)
root._settleFrames--; root._settleFrames--;
root._wedgeBounced = false;
wedgeWatchdog.stop();
} }
function onVisibleChanged() { function onVisibleChanged() {
root.invalidate(); root.invalidate();
@@ -176,10 +197,29 @@ Variants {
function onWallpaperFillModeChanged() { function onWallpaperFillModeChanged() {
root.invalidate(); root.invalidate();
} }
function onWallpaperBackgroundColorModeChanged() { function onEffectiveWallpaperBackgroundColorChanged() {
root.invalidate(); root.invalidate();
} }
function onWallpaperBackgroundCustomColorChanged() { }
Connections {
target: SessionData
function onMonitorWallpaperFillModesChanged() {
root.invalidate();
}
function onPerMonitorWallpaperChanged() {
root.invalidate();
}
}
// Theme changes repaint DankBackdrop but nothing else wakes the render loop
Connections {
target: Theme
enabled: root.isColorSource || currentWallpaper.status === Image.Error
function onPrimaryChanged() {
root.invalidate();
}
function onBackgroundChanged() {
root.invalidate(); root.invalidate();
} }
} }
@@ -502,13 +542,13 @@ Variants {
} }
onSourceChanged: { onSourceChanged: {
invalidate();
if (!source || source.startsWith("#")) { if (!source || source.startsWith("#")) {
setWallpaperImmediate(""); setWallpaperImmediate("");
return; return;
} }
root.changePending = true; root.changePending = true;
invalidate();
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source); const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
@@ -528,10 +568,14 @@ Variants {
} }
function setWallpaperImmediate(newSource) { function setWallpaperImmediate(newSource) {
transitionDelayTimer.stop();
transitionAnimation.stop(); transitionAnimation.stop();
root.transitionProgress = 0.0; root.transitionProgress = 0.0;
root.effectActive = false; root.effectActive = false;
root.screenScale = CompositorService.getScreenScale(modelData); root.screenScale = CompositorService.getScreenScale(modelData);
// No status change coming to clear the flag
if (!newSource || currentWallpaper.source.toString() === newSource)
root.changePending = false;
currentWallpaper.source = newSource; currentWallpaper.source = newSource;
nextWallpaper.source = ""; nextWallpaper.source = "";
@@ -567,10 +611,14 @@ Variants {
} }
function changeWallpaper(newPath, force) { function changeWallpaper(newPath, force) {
if (!force && newPath === currentWallpaper.source) if (!force && newPath === currentWallpaper.source.toString()) {
root.changePending = false;
return; return;
if (!newPath || newPath.startsWith("#")) }
if (!newPath || newPath.startsWith("#")) {
root.changePending = false;
return; return;
}
root.screenScale = CompositorService.getScreenScale(modelData); root.screenScale = CompositorService.getScreenScale(modelData);
if (root.transitioning || root.effectActive) { if (root.transitioning || root.effectActive) {
root.pendingWallpaper = newPath; root.pendingWallpaper = newPath;