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 }