File indexing completed on 2024-11-10 05:02:45
0001 /* 0002 SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "mediaproxy.h" 0008 0009 #include "defaultwallpaper.h" 0010 0011 #include <QBuffer> 0012 #include <QFileInfo> 0013 #include <QGuiApplication> 0014 #include <QMimeDatabase> 0015 #include <QMovie> 0016 #include <QScreen> 0017 #include <QUrlQuery> 0018 0019 #include <KConfigGroup> 0020 #include <KIO/OpenUrlJob> 0021 #include <KNotificationJobUiDelegate> 0022 #include <KPackage/PackageLoader> 0023 0024 #include <Plasma/Theme> 0025 0026 #include "../finder/packagefinder.h" 0027 0028 #include "debug.h" 0029 0030 namespace 0031 { 0032 static const QString s_wallpaperPackageName = QStringLiteral("Wallpaper/Images"); 0033 } 0034 0035 MediaProxy::MediaProxy(QObject *parent) 0036 : QObject(parent) 0037 , m_targetSize(qGuiApp->primaryScreen()->size() * qGuiApp->primaryScreen()->devicePixelRatio()) 0038 , m_isDarkColorScheme(isDarkColorScheme()) 0039 { 0040 connect(&m_dirWatch, &KDirWatch::created, this, &MediaProxy::slotSourceFileUpdated); 0041 } 0042 0043 void MediaProxy::classBegin() 0044 { 0045 } 0046 0047 void MediaProxy::componentComplete() 0048 { 0049 // don't bother loading single image until all properties have settled 0050 // otherwise we would load a too small image (initial view size) just 0051 // to load the proper one afterwards etc etc 0052 m_ready = true; 0053 0054 // Follow system color scheme 0055 connect(qGuiApp, &QGuiApplication::paletteChanged, this, &MediaProxy::slotSystemPaletteChanged); 0056 0057 processSource(); 0058 } 0059 0060 QString MediaProxy::source() const 0061 { 0062 return m_source.toString(); 0063 } 0064 0065 void MediaProxy::setSource(const QString &url) 0066 { 0067 // New desktop has empty url 0068 if (url.isEmpty()) { 0069 if (!m_isDefaultSource) { 0070 useSingleImageDefaults(); 0071 m_isDefaultSource = true; 0072 } 0073 return; 0074 } 0075 0076 m_isDefaultSource = false; 0077 0078 const QUrl sanitizedUrl = QUrl::fromUserInput(url); 0079 if (m_source == sanitizedUrl) { 0080 return; 0081 } 0082 0083 if (!m_source.isEmpty()) { 0084 m_dirWatch.removeFile(m_source.toLocalFile()); 0085 } 0086 m_source = sanitizedUrl; 0087 if (QFileInfo(m_source.toLocalFile()).isFile()) { 0088 m_dirWatch.addFile(m_source.toLocalFile()); 0089 } 0090 Q_EMIT sourceChanged(); 0091 0092 m_providerType = Provider::Type::Unknown; 0093 processSource(); 0094 } 0095 0096 QUrl MediaProxy::modelImage() const 0097 { 0098 return m_modelImage; 0099 } 0100 0101 QSize MediaProxy::targetSize() const 0102 { 0103 return m_targetSize; 0104 } 0105 0106 void MediaProxy::setTargetSize(const QSize &size) 0107 { 0108 if (m_targetSize == size) { 0109 return; 0110 } 0111 0112 m_targetSize = size; 0113 Q_EMIT targetSizeChanged(size); 0114 0115 if (m_providerType == Provider::Type::Package) { 0116 // In case file format changes after size changes 0117 processSource(); 0118 } 0119 if (m_providerType == Provider::Type::Image || m_backgroundType == BackgroundType::Type::AnimatedImage) { 0120 // When KPackage contains animated wallpapers, image provider is not used. 0121 Q_EMIT actualSizeChanged(); 0122 } 0123 } 0124 0125 Provider::Type MediaProxy::providerType() const 0126 { 0127 return m_providerType; 0128 } 0129 0130 void MediaProxy::openModelImage() 0131 { 0132 QUrl url; 0133 0134 switch (m_providerType) { 0135 case Provider::Type::Image: { 0136 url = m_modelImage; 0137 break; 0138 } 0139 0140 case Provider::Type::Package: { 0141 auto package = KPackage::PackageLoader::self()->loadPackage(s_wallpaperPackageName); 0142 package.setPath(m_source.toLocalFile()); 0143 0144 url = findPreferredImageInPackage(package); 0145 break; 0146 } 0147 0148 default: 0149 return; 0150 } 0151 0152 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url); 0153 job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); 0154 job->start(); 0155 } 0156 0157 void MediaProxy::useSingleImageDefaults() 0158 { 0159 m_source.clear(); // BUG 460692 0160 0161 auto package = DefaultWallpaper::defaultWallpaperPackage(); 0162 0163 if (!package.isValid()) { 0164 return; 0165 } 0166 0167 m_source = QUrl::fromLocalFile(package.path()); 0168 0169 PackageFinder::findPreferredImageInPackage(package, m_targetSize); 0170 0171 // Make sure the image can be read, or there will be dead loops. 0172 if (m_source.isEmpty() || QImage(package.filePath("preferred")).isNull()) { 0173 return; 0174 } 0175 0176 Q_EMIT sourceChanged(); 0177 0178 m_providerType = Provider::Type::Unknown; 0179 processSource(&package); 0180 } 0181 0182 void MediaProxy::slotSystemPaletteChanged(const QPalette &palette) 0183 { 0184 if (m_providerType != Provider::Type::Package) { 0185 // Currently only KPackage supports adaptive wallpapers 0186 return; 0187 } 0188 0189 const bool dark = isDarkColorScheme(palette); 0190 0191 if (dark == m_isDarkColorScheme) { 0192 return; 0193 } 0194 0195 m_isDarkColorScheme = dark; 0196 0197 if (m_providerType == Provider::Type::Package) { 0198 // In case light and dark variants have different formats 0199 processSource(nullptr, true /* Immediately change the wallpaper */); 0200 } 0201 0202 Q_EMIT colorSchemeChanged(); 0203 } 0204 0205 void MediaProxy::slotSourceFileUpdated(const QString &path) 0206 { 0207 Q_ASSERT(path == m_source.toLocalFile()); 0208 if (m_providerType == Provider::Type::Unknown) { 0209 processSource(); 0210 } 0211 Q_EMIT sourceFileUpdated(); 0212 } 0213 0214 bool MediaProxy::isDarkColorScheme(const QPalette &palette) 0215 { 0216 // 192 is from kcm_colors 0217 if (palette == QPalette()) { 0218 return qGray(qGuiApp->palette().window().color().rgb()) < 192; 0219 } 0220 return qGray(palette.window().color().rgb()) < 192; 0221 } 0222 0223 QColor MediaProxy::getAccentColorFromMetaData(const KPackage::Package &package) 0224 { 0225 const QJsonObject metaData = package.metadata().rawData(); 0226 const auto jsonIt = metaData.constFind(QStringLiteral("X-KDE-PlasmaImageWallpaper-AccentColor")); 0227 if (jsonIt == metaData.constEnd()) { 0228 return QColor(); 0229 } 0230 0231 QString colorString = QStringLiteral("transparent"); 0232 const auto accentColorValue = jsonIt.value(); 0233 switch (accentColorValue.type()) { 0234 case QJsonValue::String: { 0235 colorString = accentColorValue.toString(); 0236 if (!colorString.isEmpty()) { 0237 break; 0238 } 0239 [[fallthrough]]; 0240 } 0241 0242 case QJsonValue::Object: { 0243 const QJsonObject accentColorDict = accentColorValue.toObject(); 0244 if (isDarkColorScheme()) { 0245 const auto darkIt = accentColorDict.constFind(QStringLiteral("Dark")); 0246 if (darkIt != accentColorDict.constEnd()) { 0247 colorString = darkIt.value().toString(); 0248 if (!colorString.isEmpty()) { 0249 break; 0250 } 0251 } 0252 } 0253 0254 // Light color as fallback 0255 const auto lightIt = accentColorDict.constFind(QStringLiteral("Light")); 0256 if (lightIt != accentColorDict.constEnd()) { 0257 colorString = lightIt.value().toString(); 0258 break; 0259 } 0260 [[fallthrough]]; 0261 } 0262 0263 default: 0264 qCWarning(IMAGEWALLPAPER, "Invalid value from \"X-KDE-PlasmaImageWallpaper-AccentColor\""); 0265 break; 0266 } 0267 0268 return QColor(colorString); 0269 } 0270 0271 void MediaProxy::determineBackgroundType(KPackage::Package *package) 0272 { 0273 QString filePath; 0274 if (package) { 0275 filePath = findPreferredImageInPackage(*package).toLocalFile(); 0276 } else { 0277 filePath = m_source.toLocalFile(); 0278 } 0279 0280 QMimeDatabase db; 0281 const QString type = db.mimeTypeForFile(filePath).name(); 0282 0283 QBuffer dummyBuffer; 0284 dummyBuffer.open(QIODevice::ReadOnly); 0285 // Don't use QMovie::supportedFormats() as it loads all available image plugins 0286 const bool isAnimated = QImageReader(&dummyBuffer, QFileInfo(filePath).suffix().toLower().toLatin1()).supportsOption(QImageIOHandler::Animation); 0287 0288 if (isAnimated) { 0289 // Derived from the suffix 0290 m_backgroundType = BackgroundType::Type::AnimatedImage; 0291 } else if (type.startsWith(QLatin1String("image/"))) { 0292 m_backgroundType = BackgroundType::Type::Image; 0293 } else { 0294 m_backgroundType = BackgroundType::Type::Unknown; 0295 } 0296 0297 Q_EMIT backgroundTypeChanged(); 0298 } 0299 0300 void MediaProxy::determineProviderType() 0301 { 0302 QFileInfo info(m_source.toLocalFile()); 0303 0304 auto oldType = m_providerType; 0305 0306 if (info.isFile()) { 0307 m_providerType = Provider::Type::Image; 0308 } else if (info.isDir()) { 0309 m_providerType = Provider::Type::Package; 0310 } else { 0311 m_providerType = Provider::Type::Unknown; 0312 } 0313 0314 if (oldType != m_providerType) { 0315 Q_EMIT providerTypeChanged(); 0316 } 0317 } 0318 0319 void MediaProxy::processSource(KPackage::Package *package, bool doesBlockSignal) 0320 { 0321 if (!m_ready) { 0322 return; 0323 } 0324 0325 if (m_providerType == Provider::Type::Unknown) { 0326 determineProviderType(); 0327 } 0328 0329 if (!package && m_providerType == Provider::Type::Package) { 0330 KPackage::Package _package = KPackage::PackageLoader::self()->loadPackage(s_wallpaperPackageName); 0331 _package.setPath(m_source.toLocalFile()); 0332 determineBackgroundType(&_package); 0333 updateModelImage(&_package, doesBlockSignal); 0334 } else { 0335 determineBackgroundType(package); 0336 updateModelImage(package, doesBlockSignal); 0337 } 0338 } 0339 0340 QUrl MediaProxy::findPreferredImageInPackage(KPackage::Package &package) 0341 { 0342 QUrl url; 0343 0344 if (!package.isValid()) { 0345 return url; 0346 } 0347 0348 PackageFinder::findPreferredImageInPackage(package, m_targetSize); 0349 url = package.fileUrl("preferred"); 0350 0351 if (isDarkColorScheme()) { 0352 const QUrl darkUrl = package.fileUrl("preferredDark"); 0353 0354 if (!darkUrl.isEmpty()) { 0355 url = darkUrl; 0356 } 0357 } 0358 0359 return url; 0360 } 0361 0362 void MediaProxy::updateModelImage(KPackage::Package *package, bool doesBlockSignal) 0363 { 0364 if (!m_ready) { 0365 return; 0366 } 0367 0368 m_customColor = Qt::transparent; 0369 0370 QUrl newRealSource; 0371 0372 switch (m_providerType) { 0373 case Provider::Type::Image: { 0374 newRealSource = m_source; 0375 break; 0376 } 0377 0378 case Provider::Type::Package: { 0379 // Get custom accent color from 0380 0381 const QColor color(getAccentColorFromMetaData(*package)); 0382 if (m_customColor != color && color.isValid() && color != Qt::transparent) { 0383 m_customColor = color; 0384 Q_EMIT customColorChanged(); 0385 } 0386 0387 if (m_backgroundType == BackgroundType::Type::AnimatedImage) { 0388 // Is an animated image 0389 newRealSource = findPreferredImageInPackage(*package); 0390 break; 0391 } 0392 0393 // Use a custom image provider 0394 QUrl composedUrl(QStringLiteral("image://package/get")); 0395 0396 QUrlQuery urlQuery(composedUrl); 0397 urlQuery.addQueryItem(QStringLiteral("dir"), m_source.toLocalFile()); 0398 // To make modelImageChaged work 0399 urlQuery.addQueryItem(QStringLiteral("targetWidth"), QString::number(m_targetSize.width())); 0400 urlQuery.addQueryItem(QStringLiteral("targetHeight"), QString::number(m_targetSize.height())); 0401 urlQuery.addQueryItem(QStringLiteral("darkMode"), QString::number(isDarkColorScheme() ? 1 : 0)); 0402 0403 composedUrl.setQuery(urlQuery); 0404 newRealSource = composedUrl; 0405 break; 0406 } 0407 0408 case Provider::Type::Unknown: 0409 default: 0410 return; 0411 } 0412 0413 if (m_modelImage == newRealSource) { 0414 return; 0415 } 0416 0417 m_modelImage = newRealSource; 0418 if (!doesBlockSignal) { 0419 Q_EMIT modelImageChanged(); 0420 } 0421 } 0422 0423 void MediaProxy::updateModelImageWithoutSignal(KPackage::Package *package) 0424 { 0425 updateModelImage(package, true); 0426 }