From 8b641963420311777217905ec13368dd850c13b6 Mon Sep 17 00:00:00 2001 From: Fushan Wen Date: Mon, 13 Jun 2022 14:58:58 +0800 Subject: [PATCH] wallpapers/image: move image part to `MediaProxy` and update `ImageBackendTest` This simplifies ImageBackend, and makes the config dialog load slightly faster. --- .../image/imagepackage/contents/ui/main.qml | 28 +- wallpapers/image/plugin/CMakeLists.txt | 2 + .../plugin/autotests/tst_imagebackend.qml | 26 +- wallpapers/image/plugin/imagebackend.cpp | 226 +------------- wallpapers/image/plugin/imagebackend.h | 50 +-- wallpapers/image/plugin/imageplugin.cpp | 3 + .../image/plugin/provider/providertype.h | 20 ++ wallpapers/image/plugin/utils/mediaproxy.cpp | 288 ++++++++++++++++++ wallpapers/image/plugin/utils/mediaproxy.h | 102 +++++++ 9 files changed, 457 insertions(+), 288 deletions(-) create mode 100644 wallpapers/image/plugin/provider/providertype.h create mode 100644 wallpapers/image/plugin/utils/mediaproxy.cpp create mode 100644 wallpapers/image/plugin/utils/mediaproxy.h diff --git a/wallpapers/image/imagepackage/contents/ui/main.qml b/wallpapers/image/imagepackage/contents/ui/main.qml index f7439fc97..da10ebcca 100644 --- a/wallpapers/image/imagepackage/contents/ui/main.qml +++ b/wallpapers/image/imagepackage/contents/ui/main.qml @@ -16,7 +16,7 @@ import org.kde.plasma.core 2.0 as PlasmaCore QQC2.StackView { id: root - readonly property url modelImage: imageWallpaper.modelImage + readonly property url modelImage: mediaProxy.modelImage readonly property int fillMode: wallpaper.configuration.FillMode readonly property string configColor: wallpaper.configuration.Color readonly property bool blur: wallpaper.configuration.Blur @@ -47,7 +47,7 @@ QQC2.StackView { // e.g. used by slideshow wallpaper plugin function action_open() { - imageWallpaper.openModelImage(); + mediaProxy.openModelImage(); } //private @@ -68,9 +68,20 @@ QQC2.StackView { usedInConfig: false //the oneliner of difference between image and slideshow wallpapers renderingMode: (wallpaper.pluginName === "org.kde.image") ? Wallpaper.ImageBackend.SingleImage : Wallpaper.ImageBackend.SlideShow - image: { - if (wallpaper.pluginName !== "org.kde.image") { - return ""; + targetSize: root.sourceSize + slidePaths: wallpaper.configuration.SlidePaths + slideTimer: wallpaper.configuration.SlideInterval + slideshowMode: wallpaper.configuration.SlideshowMode + slideshowFoldersFirst: wallpaper.configuration.SlideshowFoldersFirst + uncheckedSlides: wallpaper.configuration.UncheckedSlides + } + + Wallpaper.MediaProxy { + id: mediaProxy + + source: { + if (wallpaper.pluginName === "org.kde.slideshow") { + return imageWallpaper.image; } if (wallpaper.configuration.PreviewImage !== "null") { return wallpaper.configuration.PreviewImage; @@ -78,11 +89,6 @@ QQC2.StackView { return wallpaper.configuration.Image; } targetSize: root.sourceSize - slidePaths: wallpaper.configuration.SlidePaths - slideTimer: wallpaper.configuration.SlideInterval - slideshowMode: wallpaper.configuration.SlideshowMode - slideshowFoldersFirst: wallpaper.configuration.SlideshowFoldersFirst - uncheckedSlides: wallpaper.configuration.UncheckedSlides onColorSchemeChanged: Qt.callLater(loadImage); } @@ -110,7 +116,7 @@ QQC2.StackView { wallpaper.loading = false; if (pendingImage.status !== Image.Ready) { - imageWallpaper.useSingleImageDefaults(); + mediaProxy.useSingleImageDefaults(); } } } diff --git a/wallpapers/image/plugin/CMakeLists.txt b/wallpapers/image/plugin/CMakeLists.txt index 85b4d8c3b..3c069f515 100644 --- a/wallpapers/image/plugin/CMakeLists.txt +++ b/wallpapers/image/plugin/CMakeLists.txt @@ -15,6 +15,8 @@ set(image_SRCS model/imagelistmodel.cpp model/imageproxymodel.cpp provider/packageimageprovider.cpp + provider/providertype.h + utils/mediaproxy.cpp ) ecm_qt_declare_logging_category(image_SRCS HEADER debug.h diff --git a/wallpapers/image/plugin/autotests/tst_imagebackend.qml b/wallpapers/image/plugin/autotests/tst_imagebackend.qml index f72d6e9dc..71f9a68e4 100644 --- a/wallpapers/image/plugin/autotests/tst_imagebackend.qml +++ b/wallpapers/image/plugin/autotests/tst_imagebackend.qml @@ -27,9 +27,15 @@ TestCase { targetSize: Qt.size(root.width, root.height) } + Wallpaper.MediaProxy { + id: mediaProxy + source: imageWallpaper.image + targetSize: imageWallpaper.targetSize + } + SignalSpy { id: modelImageChangedSignalSpy - target: imageWallpaper + target: mediaProxy signalName: "modelImageChanged" } @@ -61,7 +67,7 @@ TestCase { width: root.width height: root.height visible: true - title: imageWallpaper.modelImage.toString() + title: mediaProxy.modelImage.toString() } function test_setSingleImage() { @@ -69,9 +75,9 @@ TestCase { verify(testImage.toString().length > 0); imageWallpaper.image = testImage; - compare(imageWallpaper.modelImage, testImage); + compare(mediaProxy.modelImage, testImage); - const image = createTemporaryObject(mainImage, window, {source: imageWallpaper.modelImage}); + const image = createTemporaryObject(mainImage, window, {source: mediaProxy.modelImage}); compare(image.status, Image.Ready); const grabbed = grabImage(image); compare(grabbed.pixel(0, 0), Qt.rgba(0, 0, 0, 255)); @@ -82,9 +88,9 @@ TestCase { verify(testPackage.toString().length > 0); imageWallpaper.image = testPackage; - compare(imageWallpaper.modelImage.toString().indexOf("image://package/get?dir="), 0); + compare(mediaProxy.modelImage.toString().indexOf("image://package/get?dir="), 0); - const image = createTemporaryObject(mainImage, window, {source: imageWallpaper.modelImage}); + const image = createTemporaryObject(mainImage, window, {source: mediaProxy.modelImage}); compare(image.status, Image.Loading); image.wait(); compare(image.status, Image.Ready); @@ -117,13 +123,13 @@ TestCase { imageWallpaper.renderingMode = Wallpaper.ImageBackend.SlideShow; wait(1000); // &SlideModel::done - let image = imageWallpaper.modelImage; + let image = mediaProxy.modelImage; imageWallpaper.nextSlide(); - verify(image != imageWallpaper.modelImage); + verify(image != mediaProxy.modelImage); - image = imageWallpaper.modelImage; + image = mediaProxy.modelImage; imageWallpaper.nextSlide(); - verify(image != imageWallpaper.modelImage); + verify(image != mediaProxy.modelImage); } } diff --git a/wallpapers/image/plugin/imagebackend.cpp b/wallpapers/image/plugin/imagebackend.cpp index 739386052..8b1e12773 100644 --- a/wallpapers/image/plugin/imagebackend.cpp +++ b/wallpapers/image/plugin/imagebackend.cpp @@ -19,16 +19,13 @@ #include #include #include -#include +#include -#include #include #include #include #include #include -#include -#include #include "debug.h" #include "finder/packagefinder.h" @@ -40,11 +37,8 @@ ImageBackend::ImageBackend(QObject *parent) : QObject(parent) , m_targetSize(qGuiApp->primaryScreen()->size() * qGuiApp->primaryScreen()->devicePixelRatio()) , m_slideFilterModel(new SlideFilterModel(this)) - , m_isDarkColorScheme(isDarkColorScheme()) { connect(&m_timer, &QTimer::timeout, this, &ImageBackend::nextSlide); - - useSingleImageDefaults(); } ImageBackend::~ImageBackend() @@ -58,17 +52,10 @@ void ImageBackend::classBegin() void ImageBackend::componentComplete() { - // don't bother loading single image until all properties have settled - // otherwise we would load a too small image (initial view size) just - // to load the proper one afterwards etc etc m_ready = true; - // Follow system color scheme - connect(qGuiApp, &QGuiApplication::paletteChanged, this, &ImageBackend::slotSystemPaletteChanged); - - if (m_mode == SingleImage) { - setSingleImage(); - } else if (m_mode == SlideShow) { + // MediaProxy will handle SingleImage case + if (m_mode == SlideShow) { startSlideshow(); } } @@ -80,19 +67,12 @@ QString ImageBackend::image() const void ImageBackend::setImage(const QString &url) { - if (m_image.toString() == url || url.isEmpty()) { + if (url.isEmpty() || m_image == QUrl(url)) { return; } m_image = QUrl(url); Q_EMIT imageChanged(); - - setSingleImage(); -} - -QUrl ImageBackend::modelImage() const -{ - return m_modelImage; } ImageBackend::RenderingMode ImageBackend::renderingMode() const @@ -110,9 +90,6 @@ void ImageBackend::setRenderingMode(RenderingMode mode) if (m_mode == SlideShow) { startSlideshow(); - } else { - // we need to reset the preferred image - setSingleImage(); } } @@ -167,69 +144,10 @@ void ImageBackend::setTargetSize(const QSize &size) m_targetSize = size; - if (m_ready && m_providerType == Provider::Package) { - Q_EMIT modelImageChanged(); - } - // Will relay to ImageProxyModel Q_EMIT targetSizeChanged(m_targetSize); } -void ImageBackend::useSingleImageDefaults() -{ - m_image.clear(); - - // Try from the look and feel package first, then from the plasma theme - KPackage::Package lookAndFeelPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); - KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); - const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); - // If empty, it will be the default (currently Breeze) - if (!packageName.isEmpty()) { - lookAndFeelPackage.setPath(packageName); - } - - KConfigGroup lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(lookAndFeelPackage.filePath("defaults")), "Wallpaper"); - - const QString image = lnfDefaultsConfig.readEntry("Image", ""); - KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); - - if (!image.isEmpty()) { - package.setPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("wallpapers/") + image, QStandardPaths::LocateDirectory)); - - if (package.isValid()) { - m_image = QUrl::fromLocalFile(package.path()); - } - } - - // Try to get a default from the plasma theme - if (m_image.isEmpty()) { - Plasma::Theme theme; - QString path = theme.wallpaperPath(); - int index = path.indexOf(QLatin1String("/contents/images/")); - if (index > -1) { // We have file from package -> get path to package - m_image = QUrl::fromLocalFile(path.left(index)); - } else { - m_image = QUrl::fromLocalFile(path); - } - - package.setPath(m_image.toLocalFile()); - - if (!package.isValid()) { - return; - } - } - - PackageFinder::findPreferredImageInPackage(package, m_targetSize); - - // Make sure the image can be read, or there will be dead loops. - if (m_image.isEmpty() || QImage(package.filePath("preferred")).isNull()) { - return; - } - - Q_EMIT imageChanged(); - setSingleImage(); -} - QAbstractItemModel *ImageBackend::wallpaperModel() { if (!m_model) { @@ -254,15 +172,6 @@ SlideModel *ImageBackend::slideshowModel() return m_slideshowModel; } -bool ImageBackend::isDarkColorScheme(const QPalette &palette) const noexcept -{ - // 192 is from kcm_colors - if (palette == QPalette()) { - return qGray(qGuiApp->palette().window().color().rgb()) < 192; - } - return qGray(palette.window().color().rgb()) < 192; -} - QAbstractItemModel *ImageBackend::slideFilterModel() { if (!m_slideFilterModel->sourceModel()) { @@ -384,69 +293,6 @@ void ImageBackend::addDirFromSelectionDialog() } } -void ImageBackend::setSingleImage() -{ - if (!m_ready || m_image.isEmpty()) { - return; - } - - // supposedly QSize::isEmpty() is true if "either width or height are >= 0" - if (!m_targetSize.width() || !m_targetSize.height()) { - return; - } - - if (m_image.isLocalFile()) { - const QFileInfo info(m_image.toLocalFile()); - - if (!info.exists()) { - return; - } - - if (info.isFile()) { - m_providerType = Provider::Image; - } else { - m_providerType = Provider::Package; - } - } else { - // The url can be without file://, try again. - const QFileInfo info(m_image.toString()); - - if (!info.exists()) { - return; - } - - if (info.isFile()) { - m_providerType = Provider::Image; - } else { - m_providerType = Provider::Package; - } - - m_image = QUrl::fromLocalFile(info.filePath()); - } - - switch (m_providerType) { - case Provider::Image: - m_modelImage = m_image; - break; - - case Provider::Package: { - // Use a custom image provider - QUrl url(QStringLiteral("image://package/get")); - - QUrlQuery urlQuery(url); - urlQuery.addQueryItem(QStringLiteral("dir"), m_image.toLocalFile()); - - url.setQuery(urlQuery); - m_modelImage = url; - break; - } - } - - if (!m_modelImage.isEmpty()) { - Q_EMIT modelImageChanged(); - } -} - void ImageBackend::startSlideshow() { if (!m_ready || m_usedInConfig || m_mode != SlideShow) { @@ -473,33 +319,11 @@ void ImageBackend::backgroundsFound() } // start slideshow - if (m_currentSlide == -1) { - m_currentSlide = m_slideFilterModel->indexOf(m_image.toString()) - 1; - } else { - m_currentSlide = -1; - } + m_currentSlide = -1; m_slideFilterModel->sort(0); nextSlide(); } -void ImageBackend::slotSystemPaletteChanged(const QPalette &palette) -{ - if (m_providerType != Provider::Package || m_usedInConfig) { - // Currently only KPackage supports adaptive wallpapers - return; - } - - const bool dark = isDarkColorScheme(palette); - - if (dark == m_isDarkColorScheme) { - return; - } - - m_isDarkColorScheme = dark; - - Q_EMIT colorSchemeChanged(); -} - void ImageBackend::showFileDialog() { if (!m_dialog) { @@ -603,11 +427,10 @@ void ImageBackend::nextSlide() m_timer.stop(); m_timer.start(m_delay * 1000); if (next.isEmpty()) { - m_image = QUrl(previousPath); // setSingleImage will add "file://" + m_image = QUrl(previousPath); } else { m_image = QUrl(next); Q_EMIT imageChanged(); - setSingleImage(); } } @@ -640,43 +463,6 @@ void ImageBackend::openFolder(const QString &path) job->start(); } -void ImageBackend::openModelImage() const -{ - QUrl url; - - switch (m_providerType) { - case Provider::Image: { - url = m_image; - break; - } - - case Provider::Package: { - KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); - package.setPath(m_image.toLocalFile()); - - if (!package.isValid()) { - return; - } - - PackageFinder::findPreferredImageInPackage(package, m_targetSize); - url = QUrl::fromLocalFile(package.filePath("preferred")); - - if (isDarkColorScheme()) { - const QUrl darkUrl = package.fileUrl("preferredDark"); - - if (!darkUrl.isEmpty()) { - url = darkUrl; - } - } - break; - } - } - - KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url); - job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); - job->start(); -} - QStringList ImageBackend::uncheckedSlides() const { return m_uncheckedSlides; diff --git a/wallpapers/image/plugin/imagebackend.h b/wallpapers/image/plugin/imagebackend.h index d0fd8215b..7a0bc4aef 100644 --- a/wallpapers/image/plugin/imagebackend.h +++ b/wallpapers/image/plugin/imagebackend.h @@ -11,7 +11,6 @@ #pragma once #include -#include #include #include #include @@ -38,17 +37,12 @@ class ImageBackend : public QObject, public QQmlParserStatus, public SortingMode Q_PROPERTY(RenderingMode renderingMode READ renderingMode WRITE setRenderingMode NOTIFY renderingModeChanged) Q_PROPERTY(SortingMode::Mode slideshowMode READ slideshowMode WRITE setSlideshowMode NOTIFY slideshowModeChanged) Q_PROPERTY(bool slideshowFoldersFirst READ slideshowFoldersFirst WRITE setSlideshowFoldersFirst NOTIFY slideshowFoldersFirstChanged) + /** - * Package path from the saved configuration, can be an image file, a url with - * "image://" scheme or a folder (KPackage). + * Provides source url for \MediaProxy */ Q_PROPERTY(QString image READ image WRITE setImage NOTIFY imageChanged) - /** - * The real path of the image - * e.g. /home/kde/Pictures/image.png - * image://package/get? (KPackage) - */ - Q_PROPERTY(QUrl modelImage READ modelImage NOTIFY modelImageChanged) + Q_PROPERTY(QAbstractItemModel *wallpaperModel READ wallpaperModel CONSTANT) Q_PROPERTY(QAbstractItemModel *slideFilterModel READ slideFilterModel CONSTANT) Q_PROPERTY(int slideTimer READ slideTimer WRITE setSlideTimer NOTIFY slideTimerChanged) @@ -68,32 +62,21 @@ public: }; Q_ENUM(RenderingMode) - enum class Provider { - Image, - Package, - }; - Q_ENUM(Provider) - explicit ImageBackend(QObject *parent = nullptr); ~ImageBackend() override; QString image() const; void setImage(const QString &url); - QUrl modelImage() const; - // this is for QML use Q_INVOKABLE void addSlidePath(const QUrl &url); Q_INVOKABLE void removeSlidePath(const QString &path); Q_INVOKABLE void openFolder(const QString &path); - Q_INVOKABLE void openModelImage() const; Q_INVOKABLE void showFileDialog(); Q_INVOKABLE QString addUsersWallpaper(const QUrl &url); - Q_INVOKABLE void useSingleImageDefaults(); - RenderingMode renderingMode() const; void setRenderingMode(RenderingMode mode); @@ -130,16 +113,12 @@ public Q_SLOTS: Q_SIGNALS: void settingsChanged(); void imageChanged(); - void modelImageChanged(); void renderingModeChanged(); void slideshowModeChanged(); void slideshowFoldersFirstChanged(); void targetSizeChanged(const QSize &size); void slideTimerChanged(); - void usersWallpapersChanged(); void slidePathsChanged(); - void resizeMethodChanged(); - void customWallpaperPicked(const QString &path); void uncheckedSlidesChanged(); void loadingChanged(); @@ -148,12 +127,6 @@ Q_SIGNALS: */ void wallpaperBrowseCompleted(); - /** - * Emitted when system color scheme changes. The frontend is required to - * reload the wallpaper even if the image path is not changed. - */ - void colorSchemeChanged(); - protected Q_SLOTS: void showAddSlidePathsDialog(); void slotWallpaperBrowseCompleted(); @@ -161,32 +134,17 @@ protected Q_SLOTS: void addDirFromSelectionDialog(); void backgroundsFound(); - /** - * Switches to dark-colored wallpaper if available when system color - * scheme is dark. - * - * @since 5.26 - */ - void slotSystemPaletteChanged(const QPalette &palette); - -protected: - void setSingleImage(); - private: SlideModel *slideshowModel(); - inline bool isDarkColorScheme(const QPalette &palette = {}) const noexcept; - bool m_ready = false; int m_delay = 10; QUrl m_image; - QUrl m_modelImage; QSize m_targetSize; bool m_usedInConfig = true; RenderingMode m_mode = SingleImage; - Provider m_providerType = Provider::Image; SortingMode::Mode m_slideshowMode = SortingMode::Random; bool m_slideshowFoldersFirst = false; @@ -199,6 +157,4 @@ private: SlideModel *m_slideshowModel = nullptr; SlideFilterModel *m_slideFilterModel; QFileDialog *m_dialog = nullptr; - - bool m_isDarkColorScheme; }; diff --git a/wallpapers/image/plugin/imageplugin.cpp b/wallpapers/image/plugin/imageplugin.cpp index 531c58b74..bd19bf523 100644 --- a/wallpapers/image/plugin/imageplugin.cpp +++ b/wallpapers/image/plugin/imageplugin.cpp @@ -12,6 +12,7 @@ #include "imagebackend.h" #include "provider/packageimageprovider.h" #include "sortingmode.h" +#include "utils/mediaproxy.h" const auto pluginName = QByteArrayLiteral("org.kde.plasma.wallpapers.image"); @@ -29,6 +30,8 @@ void ImagePlugin::registerTypes(const char *uri) qRegisterMetaType(); // For image preview qmlRegisterType(uri, 2, 0, "ImageBackend"); + qmlRegisterType(uri, 2, 0, "MediaProxy"); + qmlRegisterAnonymousType("QAbstractItemModel", 1); qmlRegisterUncreatableType(uri, 2, 0, "SortingMode", QStringLiteral("error: only enums")); } diff --git a/wallpapers/image/plugin/provider/providertype.h b/wallpapers/image/plugin/provider/providertype.h new file mode 100644 index 000000000..cbd591d1b --- /dev/null +++ b/wallpapers/image/plugin/provider/providertype.h @@ -0,0 +1,20 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +class Provider +{ + Q_GADGET + +public: + enum class Type { + Unknown, + Image, + Package, + }; + Q_ENUM(Provider) +}; diff --git a/wallpapers/image/plugin/utils/mediaproxy.cpp b/wallpapers/image/plugin/utils/mediaproxy.cpp new file mode 100644 index 000000000..3a80dd641 --- /dev/null +++ b/wallpapers/image/plugin/utils/mediaproxy.cpp @@ -0,0 +1,288 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "mediaproxy.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "../finder/packagefinder.h" + +#include "debug.h" + +MediaProxy::MediaProxy(QObject *parent) + : QObject(parent) + , m_targetSize(qGuiApp->primaryScreen()->size() * qGuiApp->primaryScreen()->devicePixelRatio()) + , m_isDarkColorScheme(isDarkColorScheme()) +{ + useSingleImageDefaults(); +} + +void MediaProxy::classBegin() +{ +} + +void MediaProxy::componentComplete() +{ + // don't bother loading single image until all properties have settled + // otherwise we would load a too small image (initial view size) just + // to load the proper one afterwards etc etc + m_ready = true; + + // Follow system color scheme + connect(qGuiApp, &QGuiApplication::paletteChanged, this, &MediaProxy::slotSystemPaletteChanged); + + updateModelImage(); +} + +QString MediaProxy::source() const +{ + return m_source.toString(); +} + +void MediaProxy::setSource(const QString &url) +{ + if (m_source.toString() == url) { + return; + } + + m_source = QUrl(url); + Q_EMIT sourceChanged(); + + m_providerType = determineType(m_source); + updateModelImage(); +} + +QUrl MediaProxy::modelImage() const +{ + return m_modelImage; +} + +QSize MediaProxy::targetSize() const +{ + return m_targetSize; +} + +void MediaProxy::setTargetSize(const QSize &size) +{ + if (m_targetSize == size) { + return; + } + + m_targetSize = size; + Q_EMIT targetSizeChanged(size); + + if (m_providerType == Provider::Type::Package) { + updateModelImage(); + } +} + +Provider::Type MediaProxy::providerType() const +{ + return m_providerType; +} + +void MediaProxy::openModelImage() +{ + QUrl url; + + switch (m_providerType) { + case Provider::Type::Image: { + url = m_modelImage; + break; + } + + case Provider::Type::Package: { + url = findPreferredImageInPackage(); + break; + } + + default: + return; + } + + KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url); + job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); + job->start(); +} + +void MediaProxy::useSingleImageDefaults() +{ + // Try from the look and feel package first, then from the plasma theme + KPackage::Package lookAndFeelPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); + KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); + const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); + // If empty, it will be the default (currently Breeze) + if (!packageName.isEmpty()) { + lookAndFeelPackage.setPath(packageName); + } + + KConfigGroup lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(lookAndFeelPackage.filePath("defaults")), "Wallpaper"); + + const QString image = lnfDefaultsConfig.readEntry("Image", ""); + KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); + + if (!image.isEmpty()) { + package.setPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("wallpapers/") + image, QStandardPaths::LocateDirectory)); + + if (package.isValid()) { + m_source = QUrl::fromLocalFile(package.path()); + } + } + + // Try to get a default from the plasma theme + if (m_source.isEmpty()) { + Plasma::Theme theme; + QString path = theme.wallpaperPath(); + int index = path.indexOf(QLatin1String("/contents/images/")); + if (index > -1) { // We have file from package -> get path to package + m_source = QUrl::fromLocalFile(path.left(index)); + } else { + m_source = QUrl::fromLocalFile(path); + } + + package.setPath(m_source.toLocalFile()); + + if (!package.isValid()) { + return; + } + } + + PackageFinder::findPreferredImageInPackage(package, m_targetSize); + + // Make sure the image can be read, or there will be dead loops. + if (m_source.isEmpty() || QImage(package.filePath("preferred")).isNull()) { + return; + } + + m_providerType = determineType(m_source); + updateModelImage(); +} + +QUrl MediaProxy::formatUrl(const QUrl &url) +{ + if (url.isLocalFile()) { + return url; + } + + // The url can be without file://, try again. + return QUrl::fromLocalFile(url.toString()); +} + +void MediaProxy::slotSystemPaletteChanged(const QPalette &palette) +{ + if (m_providerType != Provider::Type::Package) { + // Currently only KPackage supports adaptive wallpapers + return; + } + + const bool dark = isDarkColorScheme(palette); + + if (dark == m_isDarkColorScheme) { + return; + } + + m_isDarkColorScheme = dark; + Q_EMIT colorSchemeChanged(); +} + +bool MediaProxy::isDarkColorScheme(const QPalette &palette) const noexcept +{ + // 192 is from kcm_colors + if (palette == QPalette()) { + return qGray(qGuiApp->palette().window().color().rgb()) < 192; + } + return qGray(palette.window().color().rgb()) < 192; +} + +Provider::Type MediaProxy::determineType(const QUrl &url) +{ + QFileInfo info(formatUrl(url).toLocalFile()); + + if (info.isFile()) { + return Provider::Type::Image; + } else if (info.isDir()) { + return Provider::Type::Package; + } + + return Provider::Type::Unknown; +} + +QUrl MediaProxy::findPreferredImageInPackage() +{ + KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); + package.setPath(m_source.toLocalFile()); + + QUrl url; + + if (!package.isValid()) { + return url; + } + + PackageFinder::findPreferredImageInPackage(package, m_targetSize); + url = QUrl::fromLocalFile(package.filePath("preferred")); + + if (isDarkColorScheme()) { + const QUrl darkUrl = package.fileUrl("preferredDark"); + + if (!darkUrl.isEmpty()) { + url = darkUrl; + } + } + + return url; +} + +void MediaProxy::updateModelImage() +{ + if (!m_ready) { + return; + } + + QUrl newRealSource; + + switch (m_providerType) { + case Provider::Type::Image: { + newRealSource = formatUrl(m_source); + break; + } + + case Provider::Type::Package: { + // Use a custom image provider + QUrl composedUrl(QStringLiteral("image://package/get")); + + QUrlQuery urlQuery(composedUrl); + urlQuery.addQueryItem(QStringLiteral("dir"), formatUrl(m_source).toLocalFile()); + // To make modelImageChaged work + urlQuery.addQueryItem(QStringLiteral("targetWidth"), QString::number(m_targetSize.width())); + urlQuery.addQueryItem(QStringLiteral("targetHeight"), QString::number(m_targetSize.height())); + + composedUrl.setQuery(urlQuery); + newRealSource = composedUrl; + break; + } + + case Provider::Type::Unknown: + default: + return; + } + + if (m_modelImage == newRealSource) { + return; + } + + m_modelImage = newRealSource; + Q_EMIT modelImageChanged(); +} diff --git a/wallpapers/image/plugin/utils/mediaproxy.h b/wallpapers/image/plugin/utils/mediaproxy.h new file mode 100644 index 000000000..5ee7c8cb0 --- /dev/null +++ b/wallpapers/image/plugin/utils/mediaproxy.h @@ -0,0 +1,102 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef MEDIAPROXY_H +#define MEDIAPROXY_H + +#include +#include +#include +#include +#include + +#include "../provider/providertype.h" + +/** + * A proxy class that converts a provider url to a real resource url. + */ +class MediaProxy : public QObject, public QQmlParserStatus, public Provider +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + /** + * Package path from the saved configuration, can be an image file, a url with + * "image://" scheme or a folder (KPackage). + */ + Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged) + + /** + * The real path of the image + * e.g. /home/kde/Pictures/image.png + * image://package/get? (KPackage) + */ + Q_PROPERTY(QUrl modelImage READ modelImage NOTIFY modelImageChanged) + + Q_PROPERTY(QSize targetSize READ targetSize WRITE setTargetSize NOTIFY targetSizeChanged) + +public: + explicit MediaProxy(QObject *parent = nullptr); + + void classBegin() override; + void componentComplete() override; + + QString source() const; + void setSource(const QString &url); + + QUrl modelImage() const; + + QSize targetSize() const; + void setTargetSize(const QSize &size); + + Provider::Type providerType() const; + + Q_INVOKABLE void openModelImage(); + + Q_INVOKABLE void useSingleImageDefaults(); + + static QUrl formatUrl(const QUrl &url); + +Q_SIGNALS: + void sourceChanged(); + void modelImageChanged(); + void targetSizeChanged(const QSize &size); + + /** + * Emitted when system color scheme changes. The frontend is required to + * reload the wallpaper even if the image path is not changed. + */ + void colorSchemeChanged(); + +private Q_SLOTS: + /** + * Switches to dark-colored wallpaper if available when system color + * scheme is dark. + * + * @since 5.26 + */ + void slotSystemPaletteChanged(const QPalette &palette); + +private: + inline bool isDarkColorScheme(const QPalette &palette = {}) const noexcept; + + Provider::Type determineType(const QUrl &url); + + QUrl findPreferredImageInPackage(); + void updateModelImage(); + + bool m_ready = false; + + QUrl m_source; + QUrl m_modelImage; + Provider::Type m_providerType = Provider::Type::Unknown; + + QSize m_targetSize; + + bool m_isDarkColorScheme; +}; + +#endif // MEDIAPROXY_H