From aa06497301c4a29601400bce4eb5765b745f0cef Mon Sep 17 00:00:00 2001 From: deepin-wm Date: Mon, 29 Jun 2026 17:13:21 +0800 Subject: [PATCH] feat(display): support video wallpaper thumbnail in monitor schematic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add resolveVideoThumbnail() to DisplayWorker for video wallpaper 2. Resolve thumbnail from metadata.json, ffmpegthumbnailer cache, or async generation via QtConcurrent 3. Add isVideoFile() helper and WallpaperSourceTypeVideo constant 4. Handle video wallpaper in both Wayland and X11 code paths 5. Add Qt6::Concurrent and libffmpegthumbnailer CMake dependencies Log: Monitor schematic now shows video wallpaper thumbnails correctly Influence: 1. Test monitor schematic display with static wallpaper 2. Test monitor schematic display with video wallpaper 3. Verify thumbnail generation for various video formats 4. Test multi-monitor setup with different wallpaper types 5. Verify async thumbnail generation completes and updates display feat(display): 显示器示意图支持视频壁纸缩略图 1. 在 DisplayWorker 中添加 resolveVideoThumbnail() 方法 2. 从 metadata.json、ffmpegthumbnailer 缓存或异步生成获取缩略图 3. 添加 isVideoFile() 辅助函数和 WallpaperSourceTypeVideo 常量 4. 在 Wayland 和 X11 两条路径中处理视频壁纸 5. 添加 Qt6::Concurrent 和 libffmpegthumbnailer CMake 依赖 Log: 显示器示意图可正常显示视频壁纸缩略图 Influence: 1. 测试静态壁纸下显示器示意图的显示 2. 测试视频壁纸下显示器示意图的显示 3. 验证各种视频格式的缩略图生成 4. 测试多显示器不同壁纸类型的配置 5. 验证异步缩略图生成完成后显示更新 PMS: BUG-367369 --- src/plugin-display/CMakeLists.txt | 5 +- .../operation/private/displayworker.cpp | 125 +++++++++++++++++- .../operation/private/displayworker.h | 6 + 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/src/plugin-display/CMakeLists.txt b/src/plugin-display/CMakeLists.txt index 6cb20abdbe..d9a1c57d11 100644 --- a/src/plugin-display/CMakeLists.txt +++ b/src/plugin-display/CMakeLists.txt @@ -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( @@ -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 diff --git a/src/plugin-display/operation/private/displayworker.cpp b/src/plugin-display/operation/private/displayworker.cpp index db7d5307e8..e7b687f088 100644 --- a/src/plugin-display/operation/private/displayworker.cpp +++ b/src/plugin-display/operation/private/displayworker.cpp @@ -13,19 +13,37 @@ #include #include +#include #include #include #include #include +#include +#include +#include #include #include #include +#include +#include #include +#include +#include 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) using namespace dccV25; @@ -84,6 +102,18 @@ DisplayWorker::DisplayWorker(DisplayModel *model, QObject *parent, bool isSync) 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() @@ -282,7 +312,11 @@ void DisplayWorker::updateWallpaper() 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() @@ -311,12 +345,20 @@ void DisplayWorker::onOutputWallpaperReady(WQt::Output *output) 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(sender()); if (!wp || !wp->output() || !m_reg) @@ -335,7 +377,11 @@ void DisplayWorker::onWallpaperChanged(const QString &fileSource, uint32_t sourc 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; } } @@ -1317,3 +1363,76 @@ void DisplayWorker::updateConcatScreenMode() qCDebug(DdcDisplayWorker) << "[ConcatScreen] updateConcatScreenMode via DBus property"; m_model->setIsConcatScreenMode(m_displayInter->isConcatScreenEnabled()); } + +QString DisplayWorker::resolveVideoThumbnail(const QString &videoPath, Monitor *monitor) +{ + 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; + + if (QFile::exists(cachePath)) { + return cachePath; + } + + if (m_videoWallpaperMonitors.contains(videoPath)) { + m_videoWallpaperMonitors[videoPath].append(monitor); + return videoPath; + } + + m_videoWallpaperMonitors[videoPath] = { monitor }; + + QPointer 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; +} diff --git a/src/plugin-display/operation/private/displayworker.h b/src/plugin-display/operation/private/displayworker.h index 853722a77a..b77b393ad4 100644 --- a/src/plugin-display/operation/private/displayworker.h +++ b/src/plugin-display/operation/private/displayworker.h @@ -11,7 +11,9 @@ #include +#include #include +#include #include #define GAMMA_SUPPORT false @@ -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; @@ -142,6 +147,7 @@ private Q_SLOTS: int m_tcMaxValue; int m_tcMinValue; int m_defaultMode; + QMap>> m_videoWallpaperMonitors; }; }