Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/plugin-display/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
cmake_minimum_required(VERSION 3.18)

find_package(${QT_NS} REQUIRED COMPONENTS WaylandClient)
find_package(${QT_NS} REQUIRED COMPONENTS WaylandClient Concurrent)
find_package(PkgConfig REQUIRED)
find_package(TreelandProtocols REQUIRED)
pkg_check_modules(WaylandClient REQUIRED IMPORTED_TARGET wayland-client)
pkg_check_modules(FFMPEGTHUMBNAILER REQUIRED IMPORTED_TARGET libffmpegthumbnailer)
pkg_check_modules(WLR_PROTOCOLS REQUIRED wlr-protocols)

execute_process(
Expand Down Expand Up @@ -67,7 +68,9 @@ set(Display_Libraries
${QT_NS}::Gui
${QT_NS}::DBus
${QT_NS}::Quick
${QT_NS}::Concurrent
${DTK_NS}::Core
PkgConfig::FFMPEGTHUMBNAILER
)

target_link_libraries(${Display_Name} PRIVATE
Expand Down
125 changes: 122 additions & 3 deletions src/plugin-display/operation/private/displayworker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,40 @@
#include <Registry.h>
#include <TreeLandOutputManager.h>
#include <WallpaperManager.h>
#include <WayQtUtils.h>

Check warning on line 13 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <WayQtUtils.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <dconfig.h>

Check warning on line 14 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dconfig.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <QCryptographicHash>

Check warning on line 16 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QCryptographicHash> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDateTime>

Check warning on line 17 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusPendingCallWatcher>

Check warning on line 18 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusPendingCallWatcher> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDebug>

Check warning on line 19 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDebug> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QFile>

Check warning on line 20 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFile> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QFileInfo>

Check warning on line 21 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFileInfo> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDir>

Check warning on line 22 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QJsonArray>

Check warning on line 23 in src/plugin-display/operation/private/displayworker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QJsonArray> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QPointer>
#include <QStandardPaths>
#include <QXmlStreamReader>
#include <QtConcurrent/QtConcurrent>
#include <libffmpegthumbnailer/videothumbnailer.h>

Q_LOGGING_CATEGORY(DdcDisplayWorker, "dcc-display-worker")

const QString DisplayInterface("org.deepin.dde.Display1");
static const QString EyeProtectionConfig = "/usr/share/dde-wloutput-daemon/eyeprotection.xml";
static const QString SysLiveWallpaperDir = "/usr/share/wallpapers/deepin-livewallpapers";

static const QStringList videoSuffixes = { "mp4", "mov", "avi", "webm", "mkv" };

static bool isVideoFile(const QString &path)
{
return videoSuffixes.contains(QFileInfo(path).suffix().toLower());
}

static constexpr uint32_t WallpaperSourceTypeVideo = 1;

Q_DECLARE_METATYPE(QList<QDBusObjectPath>)
using namespace dccV25;
Expand Down Expand Up @@ -84,6 +102,18 @@
m_displayInter->Save().waitForFinished();
});
}

connect(this, &DisplayWorker::videoThumbnailReady, this, [this](const QString &videoPath, const QString &thumbnailPath) {
auto monitors = m_videoWallpaperMonitors.value(videoPath);
if (!thumbnailPath.isEmpty()) {
for (auto &mon : monitors) {
if (mon) {
mon->setWallpaper(thumbnailPath);
}
}
}
m_videoWallpaperMonitors.remove(videoPath);
});
}

DisplayWorker::~DisplayWorker()
Expand Down Expand Up @@ -282,7 +312,11 @@

void DisplayWorker::updateMonitorWallpaper(Monitor *mon)
{
mon->setWallpaper(m_displayInter->GetCurrentWorkspaceBackgroundForMonitor(mon->name()));
QString wallpaper = m_displayInter->GetCurrentWorkspaceBackgroundForMonitor(mon->name());
if (isVideoFile(wallpaper)) {
wallpaper = resolveVideoThumbnail(wallpaper, mon);
}
mon->setWallpaper(wallpaper);
}

void DisplayWorker::updateWallpaperFromWayland()
Expand Down Expand Up @@ -311,12 +345,20 @@
auto *wp = wpMgr->getWallpaper(output->get());
if (wp) {
connect(wp, &WQt::Wallpaper::changed, this, &DisplayWorker::onWallpaperChanged, Qt::UniqueConnection);

if (wp->sourceType() == WallpaperSourceTypeVideo && !wp->fileSource().isEmpty()) {
for (auto it(m_wl_monitors.cbegin()); it != m_wl_monitors.cend(); ++it) {
if (it.key()->name() == output->name()) {
it.key()->setWallpaper(resolveVideoThumbnail(wp->fileSource(), it.key()));
break;
}
}
}
}
}

void DisplayWorker::onWallpaperChanged(const QString &fileSource, uint32_t sourceType, uint32_t role)
{
Q_UNUSED(sourceType);
Q_UNUSED(role);
auto *wp = qobject_cast<WQt::Wallpaper *>(sender());
if (!wp || !wp->output() || !m_reg)
Expand All @@ -335,7 +377,11 @@

for (auto it(m_wl_monitors.cbegin()); it != m_wl_monitors.cend(); ++it) {
if (it.key()->name() == outputName) {
it.key()->setWallpaper(fileSource);
QString wallpaper = fileSource;
if (sourceType == WallpaperSourceTypeVideo) {
wallpaper = resolveVideoThumbnail(fileSource, it.key());
}
it.key()->setWallpaper(wallpaper);
break;
}
}
Expand Down Expand Up @@ -1317,3 +1363,76 @@
qCDebug(DdcDisplayWorker) << "[ConcatScreen] updateConcatScreenMode via DBus property";
m_model->setIsConcatScreenMode(m_displayInter->isConcatScreenEnabled());
}

QString DisplayWorker::resolveVideoThumbnail(const QString &videoPath, Monitor *monitor)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring the new video wallpaper handling into shared helpers that hide thumbnail resolution details and isolate state management around m_videoWallpaperMonitors.

The added video handling is functionally fine but does increase complexity in three separate call sites and in resolveVideoThumbnail. You can keep the feature intact while reducing cognitive load by:

  1. Centralizing “wallpaper vs video thumbnail” resolution
  2. Splitting resolveVideoThumbnail into smaller helpers (metadata lookup vs async generation)
  3. Narrowing where m_videoWallpaperMonitors is touched

1. Centralize wallpaper resolution

Instead of branching in each caller, introduce a small helper that hides video-specific logic:

// in DisplayWorker.h
private:
    QString resolveWallpaperForMonitor(Monitor *mon,
                                       const QString &fileSource,
                                       uint32_t sourceType = 0);

// in DisplayWorker.cpp
QString DisplayWorker::resolveWallpaperForMonitor(Monitor *mon,
                                                  const QString &fileSource,
                                                  uint32_t sourceType)
{
    const QString path = fileSource.isEmpty()
        ? m_displayInter->GetCurrentWorkspaceBackgroundForMonitor(mon->name())
        : fileSource;

    const bool isVideo = (sourceType == WallpaperSourceTypeVideo) || isVideoFile(path);
    return isVideo ? resolveVideoThumbnail(path, mon) : path;
}

Then use this helper in the three call sites:

void DisplayWorker::updateMonitorWallpaper(Monitor *mon)
{
    mon->setWallpaper(resolveWallpaperForMonitor(mon, QString{}));
}

void DisplayWorker::onOutputWallpaperReady(WQt::Output *output)
{
    ...
    if (wp) {
        connect(...);
        if (wp->sourceType() == WallpaperSourceTypeVideo && !wp->fileSource().isEmpty()) {
            for (auto it = m_wl_monitors.cbegin(); it != m_wl_monitors.cend(); ++it) {
                if (it.key()->name() == output->name()) {
                    it.key()->setWallpaper(resolveWallpaperForMonitor(it.key(),
                                                                       wp->fileSource(),
                                                                       wp->sourceType()));
                    break;
                }
            }
        }
    }
}

void DisplayWorker::onWallpaperChanged(const QString &fileSource,
                                       uint32_t sourceType,
                                       uint32_t role)
{
    Q_UNUSED(role);
    ...
    for (auto it = m_wl_monitors.cbegin(); it != m_wl_monitors.cend(); ++it) {
        if (it.key()->name() == outputName) {
            it.key()->setWallpaper(resolveWallpaperForMonitor(it.key(),
                                                              fileSource,
                                                              sourceType));
            break;
        }
    }
}

This keeps the existing methods mostly unaware of video-specific details.

2. Split resolveVideoThumbnail responsibilities

resolveVideoThumbnail currently mixes JSON parsing, cache management, async job scheduling, and monitor bookkeeping. Splitting into tiny helpers makes it easier to follow and test:

// in DisplayWorker.h
private:
    QString findLiveWallpaperThumbnail(const QString &videoPath) const;
    QString ensureThumbnailCachePath(const QString &videoPath) const;
    void scheduleThumbnailGeneration(const QString &videoPath,
                                     const QString &cachePath,
                                     Monitor *monitor);

Implementation:

QString DisplayWorker::findLiveWallpaperThumbnail(const QString &videoPath) const
{
    QDir liveDir(SysLiveWallpaperDir);
    if (!liveDir.exists())
        return {};

    QFile metaFile(liveDir.absoluteFilePath("metadata.json"));
    if (!metaFile.open(QIODevice::ReadOnly))
        return {};

    QJsonParseError parseErr;
    const QJsonDocument doc = QJsonDocument::fromJson(metaFile.readAll(), &parseErr);
    if (parseErr.error != QJsonParseError::NoError || !doc.isArray())
        return {};

    for (const auto &entry : doc.array()) {
        const QJsonObject obj = entry.toObject();
        const QString videoAbsPath = liveDir.absoluteFilePath(obj.value("path").toString());
        if (videoAbsPath == videoPath) {
            const QString thumbnailRel = obj.value("thumbnail").toString();
            const QString thumbAbsPath = liveDir.absoluteFilePath(thumbnailRel);
            if (!thumbnailRel.isEmpty() && QFile::exists(thumbAbsPath))
                return thumbAbsPath;
            break;
        }
    }
    return {};
}

QString DisplayWorker::ensureThumbnailCachePath(const QString &videoPath) const
{
    const QString cacheDir =
        QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
        + "/live-wallpaper-thumbnails";
    QDir().mkpath(cacheDir);

    const QString cacheName =
        QString(QCryptographicHash::hash(videoPath.toUtf8(),
                                         QCryptographicHash::Md5).toHex())
        + ".png";
    return cacheDir + "/" + cacheName;
}

void DisplayWorker::scheduleThumbnailGeneration(const QString &videoPath,
                                                const QString &cachePath,
                                                Monitor *monitor)
{
    m_videoWallpaperMonitors[videoPath] = monitor;

    QPointer<DisplayWorker> guard(this);
    (void)QtConcurrent::run([guard, videoPath, cachePath]() {
        ffmpegthumbnailer::VideoThumbnailer thumbnailer(480, false, true, 8, false);
        thumbnailer.setThumbnailSize(480, -1);
        thumbnailer.setSeekTime("00:00:01");

        try {
            thumbnailer.generateThumbnail(videoPath.toStdString(),
                                          Png,
                                          cachePath.toStdString());
        } catch (const std::exception &e) {
            qCWarning(DdcDisplayWorker) << "Failed to generate video thumbnail:" << e.what();
            if (guard)
                Q_EMIT guard->videoThumbnailReady(videoPath, {});
            return;
        }

        if (guard && QFile::exists(cachePath))
            Q_EMIT guard->videoThumbnailReady(videoPath, cachePath);
    });
}

Now resolveVideoThumbnail becomes a simple orchestration method:

QString DisplayWorker::resolveVideoThumbnail(const QString &videoPath, Monitor *monitor)
{
    if (const QString liveThumb = findLiveWallpaperThumbnail(videoPath); !liveThumb.isEmpty())
        return liveThumb;

    const QString cachePath = ensureThumbnailCachePath(videoPath);
    if (QFile::exists(cachePath))
        return cachePath;

    if (!m_videoWallpaperMonitors.contains(videoPath))
        scheduleThumbnailGeneration(videoPath, cachePath, monitor);

    // fall back to video path until thumbnail is ready
    return videoPath;
}

This keeps filesystem/JSON logic, cache naming, and async job management clearly separated.

3. Narrow m_videoWallpaperMonitors usage

With the above split, m_videoWallpaperMonitors is only touched in:

  • scheduleThumbnailGeneration (insert)
  • the videoThumbnailReady lambda in the constructor (lookup + remove)

No other code paths need to know about this map, making the monitor update flow easier to track and reducing mutable state spread inside the class.

{
QDir liveDir(SysLiveWallpaperDir);
if (liveDir.exists()) {
QFile metaFile(liveDir.absoluteFilePath("metadata.json"));
if (metaFile.open(QIODevice::ReadOnly)) {
QJsonParseError parseErr;
QJsonDocument doc = QJsonDocument::fromJson(metaFile.readAll(), &parseErr);
metaFile.close();
if (parseErr.error != QJsonParseError::NoError || !doc.isArray()) {
qCWarning(DdcDisplayWorker) << "metadata.json parse error:" << parseErr.errorString();
} else {
for (const auto &entry : doc.array()) {
QJsonObject obj = entry.toObject();
QString videoAbsPath = liveDir.absoluteFilePath(obj.value("path").toString());
if (videoAbsPath == videoPath) {
QString thumbnailRel = obj.value("thumbnail").toString();
if (!thumbnailRel.isEmpty()) {
QString thumbAbsPath = liveDir.absoluteFilePath(thumbnailRel);
if (QFile::exists(thumbAbsPath)) {
return thumbAbsPath;
}
}
break;
}
}
}
}
}

QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/live-wallpaper-thumbnails";
if (cacheDir.isEmpty() || !QDir().mkpath(cacheDir)) {
qCWarning(DdcDisplayWorker) << "Failed to create thumbnail cache directory:" << cacheDir;
return videoPath;
}
QString cacheName = QString(QCryptographicHash::hash(videoPath.toUtf8(), QCryptographicHash::Md5).toHex()) + ".png";
QString cachePath = cacheDir + "/" + cacheName;
Comment thread
sourcery-ai[bot] marked this conversation as resolved.

if (QFile::exists(cachePath)) {
return cachePath;
}

if (m_videoWallpaperMonitors.contains(videoPath)) {
m_videoWallpaperMonitors[videoPath].append(monitor);
return videoPath;
}

m_videoWallpaperMonitors[videoPath] = { monitor };

QPointer<DisplayWorker> guard(this);
(void)QtConcurrent::run([guard, videoPath, cachePath]() {
ffmpegthumbnailer::VideoThumbnailer thumbnailer(480, false, true, 8, false);
thumbnailer.setThumbnailSize(480, -1);
thumbnailer.setSeekTime("00:00:01");

try {
thumbnailer.generateThumbnail(videoPath.toStdString(), Png, cachePath.toStdString());
} catch (const std::exception &e) {
qCWarning(DdcDisplayWorker) << "Failed to generate video thumbnail:" << e.what();
if (guard) {
Q_EMIT guard->videoThumbnailReady(videoPath, {});
}
return;
}

if (guard && QFile::exists(cachePath)) {
Q_EMIT guard->videoThumbnailReady(videoPath, cachePath);
}
});

return videoPath;
}
6 changes: 6 additions & 0 deletions src/plugin-display/operation/private/displayworker.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

#include <dtkcore_global.h>

#include <QMap>
#include <QObject>
#include <QPointer>
#include <QTimer>

#define GAMMA_SUPPORT false
Expand Down Expand Up @@ -115,8 +117,11 @@ private Q_SLOTS:
// task 264375
void initCTMData();

QString resolveVideoThumbnail(const QString &videoPath, Monitor *monitor);

Q_SIGNALS:
void requestUpdateModeList();
void videoThumbnailReady(const QString &videoPath, const QString &thumbnailPath);

private:
DisplayModel *m_model;
Expand All @@ -142,6 +147,7 @@ private Q_SLOTS:
int m_tcMaxValue;
int m_tcMinValue;
int m_defaultMode;
QMap<QString, QList<QPointer<Monitor>>> m_videoWallpaperMonitors;
};
}

Expand Down
Loading