File indexing completed on 2024-09-29 10:43:06
0001 /* 0002 SPDX-FileCopyrightText: 2018 Michail Vourlakos <mvourlakos@gmail.com> 0003 SPDX-License-Identifier: GPL-2.0-or-later 0004 */ 0005 0006 #include "backgroundcache.h" 0007 0008 // local 0009 #include "../../tools/commontools.h" 0010 0011 // Qt 0012 #include <QDebug> 0013 #include <QFileInfo> 0014 #include <QImage> 0015 #include <QList> 0016 #include <QRgb> 0017 #include <QtMath> 0018 #include <QLatin1String> 0019 0020 // Plasma 0021 #include <Plasma> 0022 0023 // KDE 0024 #include <KConfigGroup> 0025 #include <KDirWatch> 0026 0027 #define MAXHASHSIZE 300 0028 0029 #define PLASMACONFIG "plasma-org.kde.plasma.desktop-appletsrc" 0030 #define DEFAULTWALLPAPER "wallpapers/Next/contents/images/1920x1080.png" 0031 0032 namespace Latte{ 0033 namespace PlasmaExtended { 0034 0035 BackgroundCache::BackgroundCache(QObject *parent) 0036 : QObject(parent), 0037 m_initialized(false), 0038 m_plasmaConfig(KSharedConfig::openConfig(PLASMACONFIG)) 0039 { 0040 const auto configFile = QStandardPaths::writableLocation( 0041 QStandardPaths::GenericConfigLocation) + 0042 QLatin1Char('/') + PLASMACONFIG; 0043 0044 m_defaultWallpaperPath = Latte::standardPath(DEFAULTWALLPAPER); 0045 0046 qDebug() << "Default Wallpaper path ::: " << m_defaultWallpaperPath; 0047 0048 KDirWatch::self()->addFile(configFile); 0049 0050 connect(KDirWatch::self(), &KDirWatch::dirty, this, &BackgroundCache::settingsFileChanged); 0051 connect(KDirWatch::self(), &KDirWatch::created, this, &BackgroundCache::settingsFileChanged); 0052 0053 if (!m_pool) { 0054 m_pool = new ScreenPool(this); 0055 connect(m_pool, &ScreenPool::idsChanged, this, &BackgroundCache::reload); 0056 } 0057 0058 reload(); 0059 } 0060 0061 BackgroundCache::~BackgroundCache() 0062 { 0063 if (m_pool) { 0064 m_pool->deleteLater(); 0065 } 0066 } 0067 0068 BackgroundCache *BackgroundCache::self() 0069 { 0070 static BackgroundCache cache; 0071 return &cache; 0072 } 0073 0074 void BackgroundCache::settingsFileChanged(const QString &file) { 0075 if (!file.endsWith(PLASMACONFIG)) { 0076 return; 0077 } 0078 0079 if (m_initialized) { 0080 m_plasmaConfig->reparseConfiguration(); 0081 reload(); 0082 } 0083 } 0084 0085 QString BackgroundCache::backgroundFromConfig(const KConfigGroup &config, QString wallpaperPlugin) const 0086 { 0087 auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General"); 0088 0089 if (wallpaperConfig.hasKey("Image")) { 0090 // Trying for the wallpaper 0091 auto wallpaper = wallpaperConfig.readEntry("Image", QString()); 0092 0093 if (!wallpaper.isEmpty()) { 0094 return wallpaper; 0095 } 0096 } 0097 0098 if (wallpaperConfig.hasKey("Color")) { 0099 auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0)); 0100 return backgroundColor.name(); 0101 } 0102 0103 return QString(); 0104 } 0105 0106 bool BackgroundCache::isDesktopContainment(const KConfigGroup &containment) const 0107 { 0108 const auto type = containment.readEntry("plugin", QString()); 0109 0110 if (type == QLatin1String("org.kde.desktopcontainment") || type == QLatin1String("org.kde.plasma.folder") ) { 0111 return true; 0112 } 0113 0114 return false; 0115 } 0116 0117 void BackgroundCache::reload() 0118 { 0119 // Traversing through all containments in search for 0120 // containments that define activities in plasma 0121 KConfigGroup plasmaConfigContainments = m_plasmaConfig->group("Containments"); 0122 0123 //!activityId and screen names for which their background was updated 0124 QHash<QString, QList<QString>> updates; 0125 0126 for (const auto &containmentId : plasmaConfigContainments.groupList()) { 0127 const auto containment = plasmaConfigContainments.group(containmentId); 0128 const auto wallpaperPlugin = containment.readEntry("wallpaperplugin", QString()); 0129 const auto lastScreen = containment.readEntry("lastScreen", 0); 0130 const auto activity = containment.readEntry("activityId", QString()); 0131 0132 //! Ignore the containment if the activity is not defined or 0133 //! the containment is not a plasma desktop 0134 if (activity.isEmpty() || !isDesktopContainment(containment)) continue; 0135 0136 const auto returnedBackground = backgroundFromConfig(containment, wallpaperPlugin); 0137 0138 QString background = returnedBackground; 0139 0140 if (background.startsWith("file://")) { 0141 background = returnedBackground.mid(7); 0142 } 0143 0144 QString screenName = m_pool->connector(lastScreen); 0145 0146 //! Take case of broadcasted backgrounds, when their plugin is changed they should be disabled 0147 if (pluginExistsFor(activity,screenName) 0148 && m_plugins[activity][screenName] != wallpaperPlugin 0149 && backgroundIsBroadcasted(activity, screenName)){ 0150 //! in such case the Desktop changed wallpaper plugin and the broadcasted wallpapers should be removed 0151 setBroadcastedBackgroundsEnabled(activity, screenName, false); 0152 } 0153 0154 m_plugins[activity][screenName] = wallpaperPlugin; 0155 0156 if (background.isEmpty() || backgroundIsBroadcasted(activity, screenName)) { 0157 continue; 0158 } 0159 0160 if(!m_backgrounds.contains(activity) 0161 || !m_backgrounds[activity].contains(screenName) 0162 || m_backgrounds[activity][screenName] != background) { 0163 0164 updates[activity].append(screenName); 0165 } 0166 0167 m_backgrounds[activity][screenName] = background; 0168 } 0169 0170 m_initialized = true; 0171 0172 for (const auto &activity : updates.keys()) { 0173 for (const auto &screen : updates[activity]) { 0174 emit backgroundChanged(activity, screen); 0175 } 0176 } 0177 } 0178 0179 QString BackgroundCache::background(QString activity, QString screen) const 0180 { 0181 if (m_backgrounds.contains(activity) && m_backgrounds[activity].contains(screen)) { 0182 return m_backgrounds[activity][screen]; 0183 } else { 0184 return m_defaultWallpaperPath; 0185 } 0186 } 0187 0188 bool BackgroundCache::busyFor(QString activity, QString screen, Plasma::Types::Location location) 0189 { 0190 QString assignedBackground = background(activity, screen); 0191 0192 if (!assignedBackground.isEmpty()) { 0193 return busyForFile(assignedBackground, location); 0194 } 0195 0196 return false; 0197 } 0198 0199 float BackgroundCache::brightnessFor(QString activity, QString screen, Plasma::Types::Location location) 0200 { 0201 QString assignedBackground = background(activity, screen); 0202 0203 if (!assignedBackground.isEmpty()) { 0204 return brightnessForFile(assignedBackground, location); 0205 } 0206 0207 return -1000; 0208 } 0209 0210 float BackgroundCache::brightnessFromArea(QImage &image, int firstRow, int firstColumn, int endRow, int endColumn) 0211 { 0212 float areaBrightness = -1000; 0213 0214 if (image.format() != QImage::Format_Invalid) { 0215 for (int row = firstRow; row < endRow; ++row) { 0216 QRgb *line = (QRgb *)image.scanLine(row); 0217 0218 for (int col = firstColumn; col < endColumn ; ++col) { 0219 QRgb pixelData = line[col]; 0220 float pixelBrightness = Latte::colorBrightness(pixelData); 0221 0222 areaBrightness = (areaBrightness == -1000) ? pixelBrightness : (areaBrightness + pixelBrightness); 0223 } 0224 } 0225 0226 float areaSize = (endRow - firstRow) * (endColumn - firstColumn); 0227 areaBrightness = areaBrightness / areaSize; 0228 } 0229 0230 return areaBrightness; 0231 } 0232 0233 bool BackgroundCache::areaIsBusy(float bright1, float bright2) const 0234 { 0235 bool bright1IsLight = bright1>=123; 0236 bool bright2IsLight = bright2>=123; 0237 0238 bool inBounds = bright1>=0 && bright2<=255 && bright2>=0 && bright2<=255; 0239 0240 return !inBounds || bright1IsLight != bright2IsLight; 0241 } 0242 0243 //! In order to calculate the brightness and busy hints for specific image 0244 //! the code is doing the following. It is not needed to calculate these values 0245 //! for the entire image that would also be cpu costly. The function takes 0246 //! the location of the area in the image for which we are interested. 0247 //! The area is split in ten different Tiles and for each one its brightness 0248 //! is computed. The brightness average from these tiles provides the entire 0249 //! area brightness. In order to indicate if this area is busy or not we 0250 //! compare the minimum and the maximum values of brightness from these 0251 //! tiles. If the difference it too big then the area is busy 0252 void BackgroundCache::updateImageCalculations(QString imageFile, Plasma::Types::Location location) 0253 { 0254 if (m_hintsCache.size() > MAXHASHSIZE) { 0255 cleanupHashes(); 0256 } 0257 0258 //! if it is a local image 0259 QImage image(imageFile); 0260 0261 if (image.format() != QImage::Format_Invalid) { 0262 float brightness{-1000}; 0263 float maxBrightness{0}; 0264 float minBrightness{255}; 0265 0266 bool vertical = (location == Plasma::Types::LeftEdge || location == Plasma::Types::RightEdge) ? true : false; 0267 int imageLength = !vertical ? image.width() : image.height(); 0268 int tiles{qMin(10,imageLength)}; 0269 0270 //! 24px. should be enough because the views are always snapped to edges 0271 int tileThickness = !vertical ? qMin(24,image.height()) : qMin(24,image.width()); 0272 int tileLength = imageLength / tiles ; 0273 0274 int tileWidth = !vertical ? tileLength : tileThickness; 0275 int tileHeight = !vertical ? tileThickness : tileLength; 0276 0277 float factor = ((float)100/tiles)/100; 0278 0279 QList<float> subBrightness; 0280 0281 qDebug() << "------------ -- Image Calculations -- --------------" ; 0282 qDebug() << "Hints for Background image | " << imageFile; 0283 qDebug() << "Hints for Background image | Edge: " << location << ", Image size: " << image.width() << "x" << image.height() << ", Tiles: " << tiles << ", subsize: " << tileWidth << "x" << tileHeight; 0284 0285 //! Iterating algorigthm 0286 int firstRow = 0; int firstColumn = 0; int endRow = 0; int endColumn = 0; 0287 0288 //! horizontal tiles calculations 0289 if (location == Plasma::Types::TopEdge) { 0290 firstRow = 0; endRow = tileThickness; 0291 } else if (location == Plasma::Types::BottomEdge) { 0292 firstRow = image.height() - tileThickness - 1; endRow = image.height() - 1; 0293 } 0294 0295 if (!vertical) { 0296 for (int i=1; i<=tiles; ++i) { 0297 float subFactor = ((float)i) * factor; 0298 firstColumn = endColumn+1; endColumn = (subFactor*imageLength) - 1; 0299 endColumn = qMin(endColumn, imageLength-1); 0300 0301 int tempBrightness = brightnessFromArea(image, firstRow, firstColumn, endRow, endColumn); 0302 qDebug() << " Tile considering horizontal << (" << firstColumn << "," << firstRow << ") - (" << endColumn << "," << endRow << "), subfactor: " << subFactor 0303 << ", brightness: " << tempBrightness; 0304 0305 subBrightness.append(tempBrightness); 0306 0307 if (tempBrightness > maxBrightness) { 0308 maxBrightness = tempBrightness; 0309 } 0310 if (tempBrightness < minBrightness) { 0311 minBrightness = tempBrightness; 0312 } 0313 } 0314 } 0315 0316 //! vertical tiles calculations 0317 if (location == Plasma::Types::LeftEdge) { 0318 firstColumn = 0; endColumn = tileThickness; 0319 } else if (location == Plasma::Types::RightEdge) { 0320 firstColumn = image.width() - 1 - tileThickness; endColumn = image.width() - 1; 0321 } 0322 0323 if (vertical) { 0324 for (int i=1; i<=tiles; ++i) { 0325 float subFactor = ((float)i) * factor; 0326 firstRow = endRow+1; endRow = (subFactor*imageLength) - 1; 0327 endRow = qMin(endRow, imageLength-1); 0328 0329 int tempBrightness = brightnessFromArea(image, firstRow, firstColumn, endRow, endColumn); 0330 qDebug() << " Tile considering vertical << (" << firstColumn << "," << firstRow << ") - (" << endColumn << "," << endRow << "), subfactor: " << subFactor 0331 << ", brightness: " << tempBrightness; 0332 0333 subBrightness.append(tempBrightness); 0334 0335 if (tempBrightness > maxBrightness) { 0336 maxBrightness = tempBrightness; 0337 } 0338 if (tempBrightness < minBrightness) { 0339 minBrightness = tempBrightness; 0340 } 0341 } 0342 } 0343 //! compute total brightness for this area 0344 float subBrightnessSum = 0; 0345 0346 for (int i=0; i<subBrightness.count(); ++i) { 0347 subBrightnessSum = subBrightnessSum + subBrightness[i]; 0348 } 0349 0350 brightness = subBrightnessSum / subBrightness.count(); 0351 0352 bool areaBusy = areaIsBusy(minBrightness, maxBrightness); 0353 0354 qDebug() << "Hints for Background image | Brightness: " << brightness << ", Busy: " << areaBusy << ", minBright:" << minBrightness << ", maxBright:" << maxBrightness; 0355 0356 if (!m_hintsCache.keys().contains(imageFile)) { 0357 m_hintsCache[imageFile] = EdgesHash(); 0358 } 0359 0360 if (!m_hintsCache[imageFile].contains(location)) { 0361 imageHints iHints; 0362 iHints.brightness = brightness; iHints.busy = areaBusy; 0363 m_hintsCache[imageFile].insert(location, iHints); 0364 } else { 0365 m_hintsCache[imageFile][location].brightness = brightness; 0366 m_hintsCache[imageFile][location].busy = areaBusy; 0367 } 0368 } 0369 } 0370 0371 float BackgroundCache::brightnessForFile(QString imageFile, Plasma::Types::Location location) 0372 { 0373 if (m_hintsCache.keys().contains(imageFile)) { 0374 if (m_hintsCache[imageFile].keys().contains(location)) { 0375 return m_hintsCache[imageFile][location].brightness; 0376 } 0377 } 0378 0379 //! if it is a color 0380 if (imageFile.startsWith("#")) { 0381 return Latte::colorBrightness(QColor(imageFile)); 0382 } 0383 0384 updateImageCalculations(imageFile, location); 0385 0386 if (m_hintsCache.keys().contains(imageFile)) { 0387 return m_hintsCache[imageFile][location].brightness; 0388 } 0389 0390 return -1000; 0391 } 0392 0393 bool BackgroundCache::busyForFile(QString imageFile, Plasma::Types::Location location) 0394 { 0395 if (m_hintsCache.keys().contains(imageFile)) { 0396 if (m_hintsCache[imageFile].keys().contains(location)) { 0397 return m_hintsCache[imageFile][location].busy; 0398 } 0399 } 0400 0401 //! if it is a color 0402 if (imageFile.startsWith("#")) { 0403 return false; 0404 } 0405 0406 updateImageCalculations(imageFile, location); 0407 0408 if (m_hintsCache.keys().contains(imageFile)) { 0409 return m_hintsCache[imageFile][location].busy; 0410 } 0411 0412 return false; 0413 } 0414 0415 void BackgroundCache::cleanupHashes() 0416 { 0417 if (m_hintsCache.count() <= MAXHASHSIZE) { 0418 return; 0419 } 0420 0421 m_hintsCache.clear(); 0422 } 0423 0424 void BackgroundCache::setBackgroundFromBroadcast(QString activity, QString screen, QString filename) 0425 { 0426 if (QFileInfo(filename).exists()) { 0427 setBroadcastedBackgroundsEnabled(activity, screen, true); 0428 m_backgrounds[activity][screen] = filename; 0429 emit backgroundChanged(activity, screen); 0430 } 0431 } 0432 0433 void BackgroundCache::setBroadcastedBackgroundsEnabled(QString activity, QString screen, bool enabled) 0434 { 0435 if (enabled && !backgroundIsBroadcasted(activity, screen)) { 0436 if (!m_broadcasted.contains(activity)) { 0437 m_broadcasted[activity] = QList<QString>(); 0438 } 0439 0440 m_broadcasted[activity].append(screen); 0441 } else if (!enabled && backgroundIsBroadcasted(activity, screen)) { 0442 m_broadcasted[activity].removeAll(screen); 0443 0444 if (m_broadcasted[activity].isEmpty()) { 0445 m_broadcasted.remove(activity); 0446 } 0447 0448 reload(); 0449 } 0450 } 0451 0452 bool BackgroundCache::backgroundIsBroadcasted(QString activity, QString screenName) const 0453 { 0454 return m_broadcasted.contains(activity) && m_broadcasted[activity].contains(screenName); 0455 } 0456 0457 bool BackgroundCache::pluginExistsFor(QString activity, QString screenName) const 0458 { 0459 return m_plugins.contains(activity) && m_plugins[activity].contains(screenName); 0460 } 0461 0462 } 0463 }