Skip to content

Commit 896f631

Browse files
committed
feat: implement wallpaper cache plugin for dde-services
The wallpapercache plugin provides the following main functions: - Wallpaper scaling: Asynchronously scales wallpapers to match different screen resolutions and caches the results for performance. - Image effects: Synchronously generates processed images (such as Gaussian blur) for the greeter and lock screen. - D-Bus API: Provides a unified interface for desktop components to request processed wallpaper paths, supporting both direct file paths and file descriptors (via FD passing). - Cache management: Automatically manages and cleans up processed image caches to optimize disk usage. Log: implement wallpaper cache plugin for dde-services Pms: TASK-384099 Change-Id: Ie67988592285bdfb9ec541ac849f5bef264852bd
1 parent 6cefac6 commit 896f631

26 files changed

Lines changed: 2554 additions & 1 deletion

src/plugin-qt/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
1+
# SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd.
22
#
33
# SPDX-License-Identifier: LGPL-3.0-or-later
44

55
add_subdirectory("thememanager")
6+
add_subdirectory("wallpapercache")
67
add_subdirectory("wallpaperslideshow")
78
add_subdirectory("xsettings")
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
#
3+
# SPDX-License-Identifier: LGPL-3.0-or-later
4+
5+
cmake_minimum_required(VERSION 3.16)
6+
7+
set(PLUGIN_NAME "plugin-qt-wallpapercache")
8+
9+
project(${PLUGIN_NAME})
10+
11+
set(CMAKE_AUTOMOC ON)
12+
set(CMAKE_AUTORCC ON)
13+
14+
include(GNUInstallDirs)
15+
file(GLOB_RECURSE SRCS "*.h" "*.cpp")
16+
17+
find_package(PkgConfig REQUIRED)
18+
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui DBus)
19+
pkg_check_modules(DtkCore REQUIRED dtk6core)
20+
pkg_check_modules(DtkGui REQUIRED dtk6gui)
21+
22+
add_library(${PLUGIN_NAME} MODULE
23+
${SRCS}
24+
)
25+
26+
target_include_directories(${PLUGIN_NAME} PRIVATE
27+
${DtkCore_INCLUDE_DIRS}
28+
${DtkGui_INCLUDE_DIRS}
29+
)
30+
31+
target_link_libraries(${PLUGIN_NAME} PRIVATE
32+
Qt${QT_VERSION_MAJOR}::Core
33+
Qt${QT_VERSION_MAJOR}::Gui
34+
Qt${QT_VERSION_MAJOR}::DBus
35+
${DtkCore_LIBRARIES}
36+
${DtkGui_LIBRARIES}
37+
)
38+
39+
target_compile_options(${PLUGIN_NAME} PRIVATE
40+
${DtkCore_CFLAGS_OTHER}
41+
${DtkGui_CFLAGS_OTHER}
42+
)
43+
44+
set(MISC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/misc)
45+
46+
install(TARGETS ${PLUGIN_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR}/deepin-service-manager/)
47+
# Plugin descriptor for deepin-service-manager (system bus)
48+
install(FILES ${MISC_DIR}/plugin-qt-wallpapercache.json DESTINATION share/deepin-service-manager/system/)
49+
# D-Bus access control policy files
50+
install(FILES
51+
${MISC_DIR}/org.deepin.dde.WallpaperCache.conf
52+
${MISC_DIR}/org.deepin.dde.ImageEffect1.conf
53+
${MISC_DIR}/org.deepin.dde.ImageBlur1.conf
54+
DESTINATION share/dbus-1/system.d/
55+
)
56+
# D-Bus activation service files (required for on-demand activation)
57+
install(FILES
58+
${MISC_DIR}/org.deepin.dde.WallpaperCache.service
59+
${MISC_DIR}/org.deepin.dde.ImageEffect1.service
60+
${MISC_DIR}/org.deepin.dde.ImageBlur1.service
61+
DESTINATION share/dbus-1/system-services/
62+
)
63+
# systemd drop-in override (User, CacheDirectory, sandbox settings)
64+
install(FILES
65+
${MISC_DIR}/deepin-service-plugin@org.deepin.dde.WallpaperCache.service.d/override.conf
66+
DESTINATION lib/systemd/system/deepin-service-plugin@org.deepin.dde.WallpaperCache.service.d/
67+
)
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: LGPL-3.0-or-later
4+
5+
#include "cachedwallpaper.h"
6+
#include "scaleimagethread.h"
7+
#include "imageeffectprocessor.h"
8+
9+
#include <QDebug>
10+
#include <QDir>
11+
#include <QFile>
12+
#include <QCryptographicHash>
13+
#include <QFileInfo>
14+
#include <QDateTime>
15+
#include <QImageReader>
16+
17+
CachedWallpaper::CachedWallpaper()
18+
{
19+
QDir().mkpath(kBlurCacheDir);
20+
}
21+
22+
CachedWallpaper::~CachedWallpaper()
23+
{
24+
m_cachedImages.clear();
25+
}
26+
27+
CachedWallpaper *CachedWallpaper::instance()
28+
{
29+
static CachedWallpaper cachedWallpaper;
30+
return &cachedWallpaper;
31+
}
32+
33+
QStringList CachedWallpaper::getCachedImagePaths(const QString &originalPath, const QList<QSize> &sizes, bool isMd5Path)
34+
{
35+
QList<QSize> noCachedSizes;
36+
QStringList results;
37+
38+
QString pathMd5 = ScaleImageThread::pathMd5(originalPath, isMd5Path);
39+
40+
if (m_cachedImages.contains(pathMd5)) {
41+
const QMap<QString, QString> &map = m_cachedImages[pathMd5];
42+
for (const QSize &size : sizes) {
43+
QString strSize = ScaleImageThread::sizeToString(size);
44+
if (map.contains(strSize)) {
45+
results.append(map[strSize]);
46+
} else {
47+
noCachedSizes.append(size);
48+
}
49+
}
50+
} else {
51+
noCachedSizes = sizes;
52+
}
53+
54+
if (!noCachedSizes.isEmpty()) {
55+
qDebug() << "need handle image:" << originalPath << " sizes:" << noCachedSizes;
56+
Q_EMIT needHandleImage(originalPath, noCachedSizes, isMd5Path);
57+
}
58+
59+
return results;
60+
}
61+
62+
void CachedWallpaper::cacheImage(const QString &originalPathMd5, const QString &size, const QString &processedPath)
63+
{
64+
qDebug() << "cache Image:" << processedPath;
65+
m_cachedImages[originalPathMd5].insert(size, processedPath);
66+
}
67+
68+
QString CachedWallpaper::getBlurImagePath(const QString &originalPath)
69+
{
70+
QString pathMd5 = QCryptographicHash::hash(originalPath.toUtf8(), QCryptographicHash::Md5).toHex();
71+
72+
QMutexLocker locker(&m_blurGenerateMutex);
73+
74+
auto it = m_blurImageCache.constFind(pathMd5);
75+
if (it != m_blurImageCache.constEnd() && QFile::exists(it.value())) {
76+
return it.value();
77+
}
78+
79+
QString blurPath = generateBlurImage(pathMd5, originalPath);
80+
if (!blurPath.isEmpty()) {
81+
cacheBlurImage(pathMd5, blurPath);
82+
return blurPath;
83+
}
84+
85+
return QString();
86+
}
87+
88+
void CachedWallpaper::cacheBlurImage(const QString &originalPathMd5, const QString &blurPath)
89+
{
90+
qDebug() << "cache blur image:" << blurPath;
91+
m_blurImageCache[originalPathMd5] = blurPath;
92+
}
93+
94+
QStringList CachedWallpaper::getProcessedImageWithBlur(const QString &originalPath, const QList<QSize> &sizes, bool needBlur)
95+
{
96+
QString sourcePath = originalPath;
97+
98+
if (needBlur) {
99+
QString blurPath = getBlurImagePath(originalPath);
100+
if (!blurPath.isEmpty()) {
101+
sourcePath = blurPath;
102+
}
103+
}
104+
105+
QStringList results = getCachedImagePaths(sourcePath, sizes);
106+
if (!results.isEmpty()) {
107+
return results;
108+
}
109+
110+
return QStringList() << sourcePath;
111+
}
112+
113+
QString CachedWallpaper::blurOutputPath(const QString &pathMd5, const QString &originalPath)
114+
{
115+
QFileInfo fi(originalPath);
116+
QString suffix = fi.suffix();
117+
return QString("%1/%2%3").arg(kBlurCacheDir, pathMd5, suffix.isEmpty() ? "" : "." + suffix);
118+
}
119+
120+
QString CachedWallpaper::generateBlurImage(const QString &pathMd5, const QString &originalPath)
121+
{
122+
QFileInfo originalFileInfo(originalPath);
123+
QString outputFile = blurOutputPath(pathMd5, originalPath);
124+
125+
// Return cached file if up-to-date
126+
QFileInfo outputFileInfo(outputFile);
127+
if (outputFileInfo.exists()
128+
&& outputFileInfo.lastModified() >= originalFileInfo.lastModified()
129+
&& outputFileInfo.size() > 0) {
130+
qDebug() << "Blur image already exists and up-to-date:" << outputFile;
131+
return outputFile;
132+
}
133+
134+
qDebug() << "Generating blur image:" << originalPath << "=>" << outputFile;
135+
136+
QImage blurredImage = ImageEffectProcessor::applyPixmixEffect(originalPath);
137+
if (blurredImage.isNull()) {
138+
qWarning() << "Failed to generate blur image:" << originalPath;
139+
return QString();
140+
}
141+
142+
if (!blurredImage.save(outputFile, QImageReader::imageFormat(originalPath), 100)) {
143+
qWarning() << "Failed to save blur image:" << outputFile;
144+
return QString();
145+
}
146+
147+
// Match output mtime to input for cache freshness check
148+
QFile timeFile(outputFile);
149+
if (timeFile.open(QIODevice::ReadWrite)) {
150+
timeFile.setFileTime(originalFileInfo.lastModified(), QFileDevice::FileModificationTime);
151+
timeFile.close();
152+
}
153+
154+
qDebug() << "Successfully generated blur image:" << outputFile;
155+
return outputFile;
156+
}
157+
158+
QStringList CachedWallpaper::getWallpaperListForScreen(const QString &originalPath, const QList<QSize> &sizes, bool needBlur)
159+
{
160+
if (needBlur) {
161+
QString blurPath = getBlurImagePath(originalPath);
162+
if (!blurPath.isEmpty()) {
163+
QStringList results = getCachedImagePaths(blurPath, sizes);
164+
if (!results.isEmpty()) {
165+
return results;
166+
}
167+
return QStringList() << blurPath;
168+
}
169+
qWarning() << "Blur processing failed for:" << originalPath << ", fallback to original";
170+
}
171+
172+
QStringList results = getCachedImagePaths(originalPath, sizes);
173+
if (!results.isEmpty()) {
174+
return results;
175+
}
176+
177+
return QStringList() << originalPath;
178+
}
179+
180+
bool CachedWallpaper::deleteBlurImage(const QString &originalPath)
181+
{
182+
QString pathMd5 = QCryptographicHash::hash(originalPath.toUtf8(), QCryptographicHash::Md5).toHex();
183+
184+
auto it = m_blurImageCache.find(pathMd5);
185+
if (it != m_blurImageCache.end()) {
186+
QString blurPath = it.value();
187+
m_blurImageCache.erase(it);
188+
if (QFile::remove(blurPath)) {
189+
qDebug() << "Deleted blur image:" << blurPath;
190+
return true;
191+
}
192+
qWarning() << "Failed to delete blur image file:" << blurPath;
193+
return false;
194+
}
195+
196+
// Try to delete on-disk file even if not in memory cache
197+
QString outputFile = blurOutputPath(pathMd5, originalPath);
198+
if (QFile::remove(outputFile)) {
199+
qDebug() << "Deleted blur image file:" << outputFile;
200+
}
201+
return true;
202+
}
203+
204+
void CachedWallpaper::clearBlurCache()
205+
{
206+
m_blurImageCache.clear();
207+
208+
QDir cacheDir(kBlurCacheDir);
209+
if (!cacheDir.exists()) {
210+
return;
211+
}
212+
213+
QFileInfoList files = cacheDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
214+
int deletedCount = 0;
215+
for (const QFileInfo &fileInfo : files) {
216+
if (QFile::remove(fileInfo.absoluteFilePath())) {
217+
deletedCount++;
218+
} else {
219+
qWarning() << "Failed to delete blur cache file:" << fileInfo.absoluteFilePath();
220+
}
221+
}
222+
qDebug() << "Cleared blur cache, deleted" << deletedCount << "files";
223+
}
224+
225+
bool CachedWallpaper::deleteEffectImage(const QString &originalPath, const QString &effect)
226+
{
227+
if (effect.isEmpty() || effect == "pixmix") {
228+
return deleteBlurImage(originalPath);
229+
}
230+
qWarning() << "Unsupported effect for deletion:" << effect;
231+
return false;
232+
}
233+
234+
void CachedWallpaper::clearEffectCache(const QString &effect)
235+
{
236+
if (effect == "all") {
237+
clearBlurCache();
238+
return;
239+
}
240+
241+
QString effectName = effect.isEmpty() ? "pixmix" : effect;
242+
if (effectName == "pixmix") {
243+
clearBlurCache();
244+
} else {
245+
qWarning() << "Unsupported effect for cache clearing:" << effectName;
246+
}
247+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: LGPL-3.0-or-later
4+
5+
#ifndef CACHED_WALLPAPER_H
6+
#define CACHED_WALLPAPER_H
7+
8+
#include <QObject>
9+
#include <QString>
10+
#include <QMap>
11+
#include <QSize>
12+
#include <QMutex>
13+
14+
// Shared cache path constants
15+
inline const QString kWallpaperCacheDir = QStringLiteral("/var/cache/dde-wallpaper-cache");
16+
inline const QString kBlurCacheDir = kWallpaperCacheDir + QStringLiteral("/blur");
17+
18+
class CachedWallpaper : public QObject
19+
{
20+
Q_OBJECT
21+
22+
private:
23+
explicit CachedWallpaper();
24+
~CachedWallpaper();
25+
26+
signals:
27+
void needHandleImage(const QString &originalPath, const QList<QSize> &size, bool isMd5Path);
28+
29+
public:
30+
static CachedWallpaper *instance();
31+
QStringList getCachedImagePaths(const QString &originalPath, const QList<QSize> &sizes, bool isMd5Path = false);
32+
void cacheImage(const QString &originalPathMd5, const QString &size, const QString &processedPath);
33+
34+
// Blur wallpaper interfaces
35+
QString getBlurImagePath(const QString &originalPath);
36+
QStringList getProcessedImageWithBlur(const QString &originalPath, const QList<QSize> &sizes, bool needBlur = false);
37+
38+
// Unified wallpaper processing interface
39+
QStringList getWallpaperListForScreen(const QString &originalPath, const QList<QSize> &sizes, bool needBlur = true);
40+
41+
// Blur image management interfaces
42+
bool deleteBlurImage(const QString &originalPath);
43+
bool deleteEffectImage(const QString &originalPath, const QString &effect);
44+
void clearBlurCache();
45+
void clearEffectCache(const QString &effect);
46+
47+
static QString blurOutputPath(const QString &pathMd5, const QString &originalPath);
48+
49+
private:
50+
QString generateBlurImage(const QString &pathMd5, const QString &originalPath);
51+
void cacheBlurImage(const QString &originalPathMd5, const QString &blurPath);
52+
53+
private:
54+
// originalPath's md5; sizeToString;processedWallpaperPath
55+
QMap<QString, QMap<QString, QString>> m_cachedImages;
56+
// 模糊壁纸缓存: originalPath's md5; blurImagePath
57+
QMap<QString, QString> m_blurImageCache;
58+
QMutex m_blurGenerateMutex;
59+
};
60+
61+
#endif // CACHED_WALLPAPER_H

0 commit comments

Comments
 (0)