qs: better background blur & update properties to readonly in various services and components
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"editor.formatOnSave": false
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Constants
|
||||
@@ -8,9 +9,20 @@ Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var modelData
|
||||
readonly property color tintColor: BackgroundService.tintColor
|
||||
readonly property real tintOpacity: BackgroundService.tintOpacity
|
||||
readonly property real blurPercentage: BackgroundService.blurPercentage
|
||||
readonly property real blurRadius: BackgroundService.blurRadius
|
||||
|
||||
PanelWindow {
|
||||
id: bgWindow
|
||||
|
||||
readonly property bool doBlur: BarService.focusMode && (BackgroundService.previewPath === "")
|
||||
readonly property string imagePath: BackgroundService.previewPath || BackgroundService.cachedPath
|
||||
|
||||
screen: modelData
|
||||
WlrLayershell.namespace: "quickshell-background"
|
||||
WlrLayershell.layer: WlrLayer.Background
|
||||
@@ -28,50 +40,81 @@ Variants {
|
||||
color: Colors.mSurface
|
||||
|
||||
Item {
|
||||
id: bgManager
|
||||
|
||||
property string activeSource: BackgroundService.previewPath || (BarService.focusMode ? BackgroundService.cachedBlurredPath : BackgroundService.cachedPath)
|
||||
property bool showFirst: true
|
||||
|
||||
anchors.fill: parent
|
||||
onActiveSourceChanged: {
|
||||
showFirst = !showFirst;
|
||||
if (showFirst)
|
||||
img1.source = activeSource;
|
||||
else
|
||||
img2.source = activeSource;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (showFirst)
|
||||
img1.source = activeSource;
|
||||
else
|
||||
img2.source = activeSource;
|
||||
}
|
||||
|
||||
Image {
|
||||
id: img1
|
||||
Item {
|
||||
id: bgManager
|
||||
|
||||
property string activeSource: bgWindow.imagePath
|
||||
property bool showFirst: true
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (bgManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
visible: false
|
||||
onActiveSourceChanged: {
|
||||
showFirst = !showFirst;
|
||||
if (showFirst)
|
||||
bgImg1.source = activeSource;
|
||||
else
|
||||
bgImg2.source = activeSource;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (showFirst)
|
||||
bgImg1.source = activeSource;
|
||||
else
|
||||
bgImg2.source = activeSource;
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bgImg1
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (bgManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bgImg2
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (!bgManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Image {
|
||||
id: img2
|
||||
MultiEffect {
|
||||
source: bgManager
|
||||
anchors.fill: bgManager
|
||||
colorizationColor: root.tintColor
|
||||
colorization: bgWindow.doBlur ? root.tintOpacity : 0
|
||||
blurEnabled: true
|
||||
blur: bgWindow.doBlur ? root.blurPercentage : 0
|
||||
blurMax: root.blurRadius
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (!bgManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
Behavior on blur {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
}
|
||||
|
||||
Behavior on colorization {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
@@ -87,6 +130,11 @@ Variants {
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: bdWindow
|
||||
|
||||
property bool doBlur: true
|
||||
property string imagePath: BackgroundService.cachedPath
|
||||
|
||||
screen: modelData
|
||||
WlrLayershell.namespace: "quickshell-backdrop"
|
||||
WlrLayershell.layer: WlrLayer.Background
|
||||
@@ -104,50 +152,81 @@ Variants {
|
||||
color: Colors.mSurface
|
||||
|
||||
Item {
|
||||
id: backdropManager
|
||||
|
||||
property string activeSource: BackgroundService.cachedBlurredPath
|
||||
property bool showFirst: true
|
||||
|
||||
anchors.fill: parent
|
||||
onActiveSourceChanged: {
|
||||
showFirst = !showFirst;
|
||||
if (showFirst)
|
||||
backImg1.source = activeSource;
|
||||
else
|
||||
backImg2.source = activeSource;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (showFirst)
|
||||
backImg1.source = activeSource;
|
||||
else
|
||||
backImg2.source = activeSource;
|
||||
}
|
||||
|
||||
Image {
|
||||
id: backImg1
|
||||
Item {
|
||||
id: backdropManager
|
||||
|
||||
property string activeSource: bdWindow.imagePath
|
||||
property bool showFirst: true
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (backdropManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
visible: false
|
||||
onActiveSourceChanged: {
|
||||
showFirst = !showFirst;
|
||||
if (showFirst)
|
||||
bdImg1.source = activeSource;
|
||||
else
|
||||
bdImg2.source = activeSource;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (showFirst)
|
||||
bdImg1.source = activeSource;
|
||||
else
|
||||
bdImg2.source = activeSource;
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bdImg1
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (backdropManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bdImg2
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (!backdropManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Image {
|
||||
id: backImg2
|
||||
MultiEffect {
|
||||
source: backdropManager
|
||||
anchors.fill: backdropManager
|
||||
colorizationColor: root.tintColor
|
||||
colorization: bdWindow.doBlur ? root.tintOpacity : 0
|
||||
blurEnabled: true
|
||||
blur: bdWindow.doBlur ? root.blurPercentage : 0
|
||||
blurMax: root.blurRadius
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (!backdropManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
Behavior on blur {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
}
|
||||
|
||||
Behavior on colorization {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import qs.Services
|
||||
UBox {
|
||||
id: root
|
||||
|
||||
property string currentPanel: ShellState.leftSiderbarTab // "bluetooth", "wifi"
|
||||
readonly property string currentPanel: ShellState.leftSiderbarTab // "bluetooth", "wifi"
|
||||
|
||||
implicitHeight: (root.currentPanel === "bluetooth" ? btContentLoader.implicitHeight : wifiContentLoader.implicitHeight) + toggleGroup.implicitHeight + Style.marginXS * 2 + Style.marginS * 2
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string currentPanel: ShellState.rightSiderbarTab // "notifications", "notes"
|
||||
readonly property string currentPanel: ShellState.rightSiderbarTab // "notifications", "notes"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -8,14 +8,15 @@ pragma Singleton
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string backgroundWidth: "2560"
|
||||
property string backgroundHeight: "1440"
|
||||
readonly property string backgroundWidth: "2560"
|
||||
readonly property string backgroundHeight: "1440"
|
||||
property string cachedPath: ""
|
||||
property string cachedBlurredPath: ""
|
||||
property string previewPath: ""
|
||||
// Preserved for getBlurredOverview
|
||||
property string tintColor: Colors.mSurface
|
||||
property bool isDarkMode: false
|
||||
readonly property string tintColor: Colors.mSurface
|
||||
readonly property real tintOpacity: 0.5
|
||||
readonly property real blurPercentage: 1
|
||||
readonly property real blurRadius: 32
|
||||
|
||||
function loadBackground() {
|
||||
if (!SettingsService.backgroundPath) {
|
||||
@@ -29,14 +30,6 @@ Singleton {
|
||||
}
|
||||
cachedPath = path;
|
||||
Logger.i("BackgroundService", "Loaded background image as cached path: " + path);
|
||||
ImageCacheService.getBlurredOverview(SettingsService.backgroundPath, backgroundWidth, backgroundHeight, tintColor, isDarkMode, function(blurredPath) {
|
||||
if (!blurredPath) {
|
||||
Logger.e("BackgroundService", "Failed to load blurred background image from path: " + SettingsService.backgroundPath);
|
||||
return ;
|
||||
}
|
||||
cachedBlurredPath = blurredPath;
|
||||
Logger.i("BackgroundService", "Loaded blurred background image as cached blurred path: " + blurredPath);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,17 +56,19 @@ Singleton {
|
||||
if (!exists)
|
||||
return ;
|
||||
|
||||
SettingsService.backgroundPath = path;
|
||||
loadWallpaperDebouncer.pendingPath = path;
|
||||
loadWallpaperDebouncer.start();
|
||||
});
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadWallpaperDebouncer.pendingPath = SettingsService.backgroundPath;
|
||||
loadWallpaperDebouncer.start();
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onBackgroundPathChanged() {
|
||||
loadWallpaperDebouncer.pendingPath = SettingsService.backgroundPath;
|
||||
loadWallpaperDebouncer.start();
|
||||
}
|
||||
|
||||
@@ -83,10 +78,13 @@ Singleton {
|
||||
Timer {
|
||||
id: loadWallpaperDebouncer
|
||||
|
||||
property string pendingPath: ""
|
||||
|
||||
interval: 200
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
SettingsService.backgroundPath = pendingPath;
|
||||
root.loadBackground();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,8 @@ Singleton {
|
||||
property bool initialized: false
|
||||
// Cache directories
|
||||
readonly property string baseDir: Paths.cacheDir + "images/"
|
||||
readonly property string wpThumbDir: baseDir + "wallpapers/thumbnails/"
|
||||
readonly property string wpLargeDir: baseDir + "wallpapers/large/"
|
||||
readonly property string wpOverviewDir: baseDir + "wallpapers/overview/"
|
||||
readonly property string notificationsDir: baseDir + "notifications/"
|
||||
readonly property string contributorsDir: baseDir + "contributors/"
|
||||
// Supported image formats - extended list when ImageMagick is available
|
||||
readonly property var basicImageFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.bmp"]
|
||||
readonly property var extendedImageFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.bmp", "*.webp", "*.avif", "*.heic", "*.heif", "*.tiff", "*.tif", "*.pnm", "*.pgm", "*.ppm", "*.pbm", "*.svg", "*.svgz", "*.ico", "*.icns", "*.jxl", "*.jp2", "*.j2k", "*.exr", "*.hdr", "*.dds", "*.tga"]
|
||||
@@ -67,41 +64,18 @@ Singleton {
|
||||
}
|
||||
|
||||
function createDirectories() {
|
||||
Quickshell.execDetached(["mkdir", "-p", wpThumbDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", wpLargeDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", wpOverviewDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", notificationsDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", contributorsDir]);
|
||||
}
|
||||
|
||||
function cleanupOldCache() {
|
||||
const dirs = [wpThumbDir, wpLargeDir, wpOverviewDir, notificationsDir, contributorsDir];
|
||||
const dirs = [wpLargeDir, notificationsDir];
|
||||
dirs.forEach(function(dir) {
|
||||
Quickshell.execDetached(["find", dir, "-type", "f", "-mtime", "+30", "-delete"]);
|
||||
});
|
||||
Logger.d("ImageCache", "Cleanup triggered for files older than 30 days");
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Public API: Get Thumbnail (384x384)
|
||||
// -------------------------------------------------
|
||||
function getThumbnail(sourcePath, callback) {
|
||||
if (!sourcePath || sourcePath === "") {
|
||||
callback("", false);
|
||||
return ;
|
||||
}
|
||||
getMtime(sourcePath, function(mtime) {
|
||||
const cacheKey = generateThumbnailKey(sourcePath, mtime);
|
||||
const cachedPath = wpThumbDir + cacheKey + ".png";
|
||||
processRequest(cacheKey, cachedPath, sourcePath, callback, function() {
|
||||
if (imageMagickAvailable)
|
||||
startThumbnailProcessing(sourcePath, cachedPath, cacheKey);
|
||||
else
|
||||
queueFallbackProcessing(sourcePath, cachedPath, cacheKey, 384);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Public API: Get Large Image (scaled to specified dimensions)
|
||||
// -------------------------------------------------
|
||||
@@ -209,57 +183,9 @@ Singleton {
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Public API: Get Circular Avatar (256x256)
|
||||
// -------------------------------------------------
|
||||
function getCircularAvatar(url, username, callback) {
|
||||
if (!url || !username) {
|
||||
callback("", false);
|
||||
return ;
|
||||
}
|
||||
const cacheKey = username;
|
||||
const cachedPath = contributorsDir + username + "_circular.png";
|
||||
processRequest(cacheKey, cachedPath, url, callback, function() {
|
||||
if (imageMagickAvailable) {
|
||||
downloadAndProcessAvatar(url, username, cachedPath, cacheKey);
|
||||
} else {
|
||||
// No fallback for circular avatars without ImageMagick
|
||||
Logger.w("ImageCache", "Circular avatars require ImageMagick");
|
||||
notifyCallbacks(cacheKey, "", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Public API: Get Blurred Overview (for Niri overview background)
|
||||
// -------------------------------------------------
|
||||
function getBlurredOverview(sourcePath, width, height, tintColor, isDarkMode, callback) {
|
||||
if (!sourcePath || sourcePath === "") {
|
||||
callback("", false);
|
||||
return ;
|
||||
}
|
||||
if (!imageMagickAvailable) {
|
||||
Logger.d("ImageCache", "ImageMagick not available for overview blur, using original:", sourcePath);
|
||||
callback(sourcePath, false);
|
||||
return ;
|
||||
}
|
||||
getMtime(sourcePath, function(mtime) {
|
||||
const cacheKey = generateOverviewKey(sourcePath, width, height, tintColor, isDarkMode, mtime);
|
||||
const cachedPath = wpOverviewDir + cacheKey + ".png";
|
||||
processRequest(cacheKey, cachedPath, sourcePath, callback, function() {
|
||||
startOverviewProcessing(sourcePath, cachedPath, width, height, tintColor, isDarkMode, cacheKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Cache Key Generation
|
||||
// -------------------------------------------------
|
||||
function generateThumbnailKey(sourcePath, mtime) {
|
||||
const keyString = sourcePath + "@384x384@" + (mtime || "unknown");
|
||||
return Checksum.sha256(keyString);
|
||||
}
|
||||
|
||||
function generateLargeKey(sourcePath, width, height, mtime) {
|
||||
const keyString = sourcePath + "@" + width + "x" + height + "@" + (mtime || "unknown");
|
||||
return Checksum.sha256(keyString);
|
||||
@@ -272,11 +198,6 @@ Singleton {
|
||||
return Checksum.sha256(imageUri);
|
||||
}
|
||||
|
||||
function generateOverviewKey(sourcePath, width, height, tintColor, isDarkMode, mtime) {
|
||||
const keyString = sourcePath + "@" + width + "x" + height + "@" + tintColor + "@" + (isDarkMode ? "dark" : "light") + "@" + (mtime || "unknown");
|
||||
return Checksum.sha256(keyString);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Request Processing (with coalescing)
|
||||
// -------------------------------------------------
|
||||
@@ -325,17 +246,6 @@ Singleton {
|
||||
processingFailed(cacheKey, "Processing failed");
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// ImageMagick Processing: Thumbnail
|
||||
// -------------------------------------------------
|
||||
function startThumbnailProcessing(sourcePath, outputPath, cacheKey) {
|
||||
const srcEsc = sourcePath.replace(/'/g, "'\\''");
|
||||
const dstEsc = outputPath.replace(/'/g, "'\\''");
|
||||
// Use Lanczos filter for high-quality downscaling, subtle unsharp mask, and PNG for lossless output
|
||||
const command = `magick '${srcEsc}' -auto-orient -filter Lanczos -resize '384x384^' -gravity center -extent 384x384 -unsharp 0x0.5 '${dstEsc}'`;
|
||||
runProcess(command, cacheKey, outputPath, sourcePath);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// ImageMagick Processing: Large
|
||||
// -------------------------------------------------
|
||||
@@ -347,79 +257,6 @@ Singleton {
|
||||
runProcess(command, cacheKey, outputPath, sourcePath);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// ImageMagick Processing: Blurred Overview
|
||||
// -------------------------------------------------
|
||||
function startOverviewProcessing(sourcePath, outputPath, width, height, tintColor, isDarkMode, cacheKey) {
|
||||
const srcEsc = sourcePath.replace(/'/g, "'\\''");
|
||||
const dstEsc = outputPath.replace(/'/g, "'\\''");
|
||||
// Resize, blur, then tint overlay
|
||||
const command = `magick '${srcEsc}' -auto-orient -resize '${width}x${height}' -gravity center -blur 0x20 \\( +clone -fill '${tintColor}' -colorize 100 -alpha set -channel A -evaluate set 50% +channel \\) -composite '${dstEsc}'`;
|
||||
runProcess(command, cacheKey, outputPath, sourcePath);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// ImageMagick Processing: Circular Avatar
|
||||
// -------------------------------------------------
|
||||
function downloadAndProcessAvatar(url, username, outputPath, cacheKey) {
|
||||
const tempPath = contributorsDir + username + "_temp.png";
|
||||
const tempEsc = tempPath.replace(/'/g, "'\\''");
|
||||
const urlEsc = url.replace(/'/g, "'\\''");
|
||||
// Download first (uses utility queue since curl/wget are lightweight)
|
||||
const downloadCmd = `curl -L -s -o '${tempEsc}' '${urlEsc}' || wget -q -O '${tempEsc}' '${urlEsc}'`;
|
||||
const processString = `
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["sh", "-c", "${downloadCmd.replace(/"/g, '\\"')}"]
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
`;
|
||||
queueUtilityProcess({
|
||||
"name": "DownloadProcess_" + cacheKey,
|
||||
"processString": processString,
|
||||
"onComplete": function(exitCode) {
|
||||
if (exitCode !== 0) {
|
||||
Logger.e("ImageCache", "Failed to download avatar for", username);
|
||||
notifyCallbacks(cacheKey, "", false);
|
||||
return ;
|
||||
}
|
||||
// Now process with ImageMagick
|
||||
processCircularAvatar(tempPath, outputPath, cacheKey);
|
||||
},
|
||||
"onError": function() {
|
||||
notifyCallbacks(cacheKey, "", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processCircularAvatar(inputPath, outputPath, cacheKey) {
|
||||
const srcEsc = inputPath.replace(/'/g, "'\\''");
|
||||
const dstEsc = outputPath.replace(/'/g, "'\\''");
|
||||
// ImageMagick command for circular crop with alpha
|
||||
const command = `magick '${srcEsc}' -resize 256x256^ -gravity center -extent 256x256 -alpha set \\( +clone -channel A -evaluate set 0 +channel -fill white -draw 'circle 128,128 128,0' \\) -compose DstIn -composite '${dstEsc}'`;
|
||||
queueImageMagickProcess({
|
||||
"command": command,
|
||||
"cacheKey": cacheKey,
|
||||
"onComplete": function(exitCode) {
|
||||
// Clean up temp file
|
||||
Quickshell.execDetached(["rm", "-f", inputPath]);
|
||||
if (exitCode !== 0) {
|
||||
Logger.e("ImageCache", "Failed to create circular avatar");
|
||||
notifyCallbacks(cacheKey, "", false);
|
||||
} else {
|
||||
Logger.d("ImageCache", "Circular avatar created:", outputPath);
|
||||
notifyCallbacks(cacheKey, outputPath, true);
|
||||
}
|
||||
},
|
||||
"onError": function() {
|
||||
Quickshell.execDetached(["rm", "-f", inputPath]);
|
||||
notifyCallbacks(cacheKey, "", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Queue an ImageMagick process and run it when a slot is available
|
||||
function queueImageMagickProcess(request) {
|
||||
imageMagickQueue.push(request);
|
||||
@@ -616,13 +453,6 @@ Singleton {
|
||||
// -------------------------------------------------
|
||||
// Cache Invalidation
|
||||
// -------------------------------------------------
|
||||
function invalidateThumbnail(sourcePath) {
|
||||
Logger.i("ImageCache", "Invalidating thumbnail for:", sourcePath);
|
||||
// Since cache keys include hash, we'd need to track mappings
|
||||
// For simplicity, clear all thumbnails
|
||||
clearThumbnails();
|
||||
}
|
||||
|
||||
function invalidateLarge(sourcePath) {
|
||||
Logger.i("ImageCache", "Invalidating large for:", sourcePath);
|
||||
clearLarge();
|
||||
@@ -633,26 +463,13 @@ Singleton {
|
||||
Quickshell.execDetached(["rm", "-f", path]);
|
||||
}
|
||||
|
||||
function invalidateAvatar(username) {
|
||||
const path = contributorsDir + username + "_circular.png";
|
||||
Quickshell.execDetached(["rm", "-f", path]);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Clear Cache Functions
|
||||
// -------------------------------------------------
|
||||
function clearAll() {
|
||||
Logger.i("ImageCache", "Clearing all cache");
|
||||
clearThumbnails();
|
||||
clearLarge();
|
||||
clearNotifications();
|
||||
clearContributors();
|
||||
}
|
||||
|
||||
function clearThumbnails() {
|
||||
Logger.i("ImageCache", "Clearing thumbnails cache");
|
||||
Quickshell.execDetached(["rm", "-rf", wpThumbDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", wpThumbDir]);
|
||||
}
|
||||
|
||||
function clearLarge() {
|
||||
@@ -667,82 +484,6 @@ Singleton {
|
||||
Quickshell.execDetached(["mkdir", "-p", notificationsDir]);
|
||||
}
|
||||
|
||||
function clearContributors() {
|
||||
Logger.i("ImageCache", "Clearing contributors cache");
|
||||
Quickshell.execDetached(["rm", "-rf", contributorsDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", contributorsDir]);
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Qt Fallback Renderer
|
||||
// -------------------------------------------------
|
||||
PanelWindow {
|
||||
id: fallbackRenderer
|
||||
|
||||
implicitWidth: 0
|
||||
implicitHeight: 0
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "noctalia-image-cache-renderer"
|
||||
color: "transparent"
|
||||
|
||||
Image {
|
||||
id: fallbackImage
|
||||
|
||||
property string cacheKey: ""
|
||||
property string destPath: ""
|
||||
property int targetSize: 256
|
||||
|
||||
function processNextFallback() {
|
||||
cacheKey = "";
|
||||
destPath = "";
|
||||
source = "";
|
||||
if (fallbackQueue.length > 0) {
|
||||
const next = fallbackQueue.shift();
|
||||
cacheKey = next.cacheKey;
|
||||
destPath = next.destPath;
|
||||
targetSize = next.size;
|
||||
source = next.sourcePath;
|
||||
} else {
|
||||
fallbackProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
width: targetSize
|
||||
height: targetSize
|
||||
visible: true
|
||||
cache: false
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
mipmap: true
|
||||
antialiasing: true
|
||||
onStatusChanged: {
|
||||
if (!cacheKey)
|
||||
return ;
|
||||
|
||||
if (status === Image.Ready) {
|
||||
grabToImage(function(result) {
|
||||
if (result.saveToFile(destPath)) {
|
||||
Logger.d("ImageCache", "Fallback cache created:", destPath);
|
||||
root.notifyCallbacks(cacheKey, destPath, true);
|
||||
} else {
|
||||
Logger.e("ImageCache", "Failed to save fallback cache");
|
||||
root.notifyCallbacks(cacheKey, "", false);
|
||||
}
|
||||
processNextFallback();
|
||||
});
|
||||
} else if (status === Image.Error) {
|
||||
Logger.e("ImageCache", "Fallback image load failed");
|
||||
root.notifyCallbacks(cacheKey, "", false);
|
||||
processNextFallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// ImageMagick Detection
|
||||
// -------------------------------------------------
|
||||
|
||||
@@ -21,7 +21,7 @@ Singleton {
|
||||
// line 2 <- current line
|
||||
// line 3
|
||||
property var lyrics: Array(linesCount).fill(" ")
|
||||
property bool showLyricsBar: ShellState.lyricsState.showLyricsBar || false
|
||||
readonly property bool showLyricsBar: ShellState.lyricsState.showLyricsBar || false
|
||||
|
||||
function toggleLyricsBar() {
|
||||
ShellState.lyricsState = {
|
||||
|
||||
@@ -20,8 +20,8 @@ Singleton {
|
||||
property string historyFile: Paths.cacheDir + "notifications.json"
|
||||
|
||||
// State
|
||||
property real lastSeenTs: ShellState.notificationsState.lastSeenTs || 0
|
||||
property bool doNotDisturb: ShellState.notificationsState.doNotDisturb || false
|
||||
readonly property real lastSeenTs: ShellState.notificationsState.lastSeenTs || 0
|
||||
readonly property bool doNotDisturb: ShellState.notificationsState.doNotDisturb || false
|
||||
|
||||
// Models
|
||||
property ListModel activeList: ListModel {}
|
||||
|
||||
@@ -11,7 +11,7 @@ Singleton {
|
||||
property double _latitude: -1
|
||||
property double _longitude: -1
|
||||
property int temperature: 0
|
||||
property bool isEnabled: ShellState.sunsetState.enabled || false
|
||||
readonly property bool isEnabled: ShellState.sunsetState.enabled || false
|
||||
|
||||
function toggleSunset() {
|
||||
ShellState.sunsetState = {
|
||||
|
||||
Reference in New Issue
Block a user