Warning, file /plasma/plasma-desktop/imports/activitymanager/sortedactivitiesmodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2016 Ivan Cukic <ivan.cukic(at)kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 // Self 0008 #include "sortedactivitiesmodel.h" 0009 0010 // C++ 0011 #include <functional> 0012 0013 // Qt 0014 #include <QColor> 0015 #include <QObject> 0016 #include <QTimer> 0017 0018 // KDE 0019 #include <KConfigGroup> 0020 #include <KDirWatch> 0021 #include <KLocalizedString> 0022 #include <KSharedConfig> 0023 #include <KWindowInfo> 0024 #include <KX11Extras> 0025 0026 static const char *s_plasma_config = "plasma-org.kde.plasma.desktop-appletsrc"; 0027 0028 namespace 0029 { 0030 class BackgroundCache : public QObject 0031 { 0032 public: 0033 BackgroundCache() 0034 : initialized(false) 0035 , plasmaConfig(KSharedConfig::openConfig(QString::fromLatin1(s_plasma_config))) 0036 { 0037 using namespace std::placeholders; 0038 0039 const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char{'/'} + QLatin1String{s_plasma_config}; 0040 0041 KDirWatch::self()->addFile(configFile); 0042 0043 QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &BackgroundCache::settingsFileChanged, Qt::QueuedConnection); 0044 QObject::connect(KDirWatch::self(), &KDirWatch::created, this, &BackgroundCache::settingsFileChanged, Qt::QueuedConnection); 0045 } 0046 0047 void settingsFileChanged(const QString &file) 0048 { 0049 if (!file.endsWith(QLatin1String{s_plasma_config})) { 0050 return; 0051 } 0052 0053 if (initialized) { 0054 plasmaConfig->reparseConfiguration(); 0055 reload(); 0056 } 0057 } 0058 0059 void subscribe(SortedActivitiesModel *model) 0060 { 0061 if (!initialized) { 0062 reload(); 0063 } 0064 0065 models << model; 0066 } 0067 0068 void unsubscribe(SortedActivitiesModel *model) 0069 { 0070 models.removeAll(model); 0071 0072 if (models.isEmpty()) { 0073 initialized = false; 0074 forActivity.clear(); 0075 } 0076 } 0077 0078 QString backgroundFromConfig(const KConfigGroup &config) const 0079 { 0080 auto wallpaperPlugin = config.readEntry("wallpaperplugin"); 0081 auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General"); 0082 0083 if (wallpaperConfig.hasKey("Image")) { 0084 // Trying for the wallpaper 0085 auto wallpaper = wallpaperConfig.readEntry("Image", QString()); 0086 if (!wallpaper.isEmpty()) { 0087 return wallpaper; 0088 } 0089 } 0090 if (wallpaperConfig.hasKey("Color")) { 0091 auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0)); 0092 return backgroundColor.name(); 0093 } 0094 0095 return QString(); 0096 } 0097 0098 void reload() 0099 { 0100 auto newForActivity = forActivity; 0101 QHash<QString, int> lastScreenForActivity; 0102 0103 // contains activities for which the wallpaper 0104 // has updated 0105 QStringList changedActivities; 0106 0107 // Contains activities not covered by any containment 0108 QStringList ghostActivities = forActivity.keys(); 0109 0110 // Traversing through all containments in search for 0111 // containments that define activities in plasma 0112 for (const auto &containmentId : plasmaConfigContainments().groupList()) { 0113 const auto containment = plasmaConfigContainments().group(containmentId); 0114 const auto lastScreen = containment.readEntry("lastScreen", 0); 0115 const auto activity = containment.readEntry("activityId", QString()); 0116 0117 // Ignore the containment if the activity is not defined 0118 if (activity.isEmpty()) 0119 continue; 0120 0121 // If we have already found the same activity from another 0122 // containment, we are using the new one only if 0123 // the previous one was a color and not a proper wallpaper, 0124 // or if the screen ID is closer to zero 0125 const bool processed = !ghostActivities.contains(activity) && newForActivity.contains(activity) && (lastScreenForActivity[activity] <= lastScreen); 0126 0127 // qDebug() << "GREPME Searching containment " << containmentId 0128 // << "for the wallpaper of the " << activity << " activity - " 0129 // << "currently, we think that the wallpaper is " << processed << (processed ? newForActivity[activity] : QString()) 0130 // << "last screen is" << lastScreen 0131 // ; 0132 0133 if (processed && newForActivity[activity][0] != QLatin1Char{'#'}) 0134 continue; 0135 0136 // Marking the current activity as processed 0137 ghostActivities.removeAll(activity); 0138 0139 const auto background = backgroundFromConfig(containment); 0140 0141 // qDebug() << " GREPME Found wallpaper: " << background; 0142 0143 if (background.isEmpty()) 0144 continue; 0145 0146 // If we got this far and we already had a new wallpaper for 0147 // this activity, it means we now have a better one 0148 bool foundBetterWallpaper = changedActivities.contains(activity); 0149 0150 if (foundBetterWallpaper || newForActivity[activity] != background) { 0151 if (!foundBetterWallpaper) { 0152 changedActivities << activity; 0153 } 0154 0155 // qDebug() << " GREPME Setting: " << activity << " = " << background << "," << lastScreen; 0156 newForActivity[activity] = background; 0157 lastScreenForActivity[activity] = lastScreen; 0158 } 0159 } 0160 0161 initialized = true; 0162 0163 // Removing the activities from the list if we haven't found them 0164 // while traversing through the containments 0165 for (const auto &activity : ghostActivities) { 0166 newForActivity.remove(activity); 0167 } 0168 0169 // If we have detected the changes, lets notify everyone 0170 if (!changedActivities.isEmpty()) { 0171 forActivity = newForActivity; 0172 0173 for (auto model : models) { 0174 model->onBackgroundsUpdated(changedActivities); 0175 } 0176 } 0177 } 0178 0179 KConfigGroup plasmaConfigContainments() 0180 { 0181 return plasmaConfig->group("Containments"); 0182 } 0183 0184 QHash<QString, QString> forActivity; 0185 QList<SortedActivitiesModel *> models; 0186 0187 bool initialized; 0188 KSharedConfig::Ptr plasmaConfig; 0189 }; 0190 0191 static BackgroundCache &backgrounds() 0192 { 0193 // If you convert this to a shared pointer, 0194 // fix the connections to KDirWatcher 0195 static BackgroundCache cache; 0196 return cache; 0197 } 0198 0199 } 0200 0201 SortedActivitiesModel::SortedActivitiesModel(const QVector<KActivities::Info::State> &states, QObject *parent) 0202 : QSortFilterProxyModel(parent) 0203 , m_activitiesModel(new KActivities::ActivitiesModel(states, this)) 0204 , m_activities(new KActivities::Consumer(this)) 0205 { 0206 setSourceModel(m_activitiesModel); 0207 0208 setDynamicSortFilter(true); 0209 setSortRole(LastTimeUsed); 0210 sort(0, Qt::DescendingOrder); 0211 0212 backgrounds().subscribe(this); 0213 0214 const QList<WId> windows = KX11Extras::stackingOrder(); 0215 0216 for (const auto &window : windows) { 0217 KWindowInfo info(window, NET::WMVisibleName, NET::WM2Activities); 0218 const QStringList activities = info.activities(); 0219 0220 if (activities.isEmpty() || activities.contains(QLatin1String{"00000000-0000-0000-0000-000000000000"})) 0221 continue; 0222 0223 for (const auto &activity : activities) { 0224 m_activitiesWindows[activity] << window; 0225 } 0226 } 0227 0228 connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &SortedActivitiesModel::onWindowAdded); 0229 connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &SortedActivitiesModel::onWindowRemoved); 0230 connect(KX11Extras::self(), &KX11Extras::windowChanged, this, &SortedActivitiesModel::onWindowChanged); 0231 } 0232 0233 SortedActivitiesModel::~SortedActivitiesModel() 0234 { 0235 backgrounds().unsubscribe(this); 0236 } 0237 0238 bool SortedActivitiesModel::inhibitUpdates() const 0239 { 0240 return m_inhibitUpdates; 0241 } 0242 0243 void SortedActivitiesModel::setInhibitUpdates(bool inhibitUpdates) 0244 { 0245 if (m_inhibitUpdates != inhibitUpdates) { 0246 m_inhibitUpdates = inhibitUpdates; 0247 Q_EMIT inhibitUpdatesChanged(m_inhibitUpdates); 0248 0249 setDynamicSortFilter(!inhibitUpdates); 0250 } 0251 } 0252 0253 uint SortedActivitiesModel::lastUsedTime(const QString &activity) const 0254 { 0255 if (m_activities->currentActivity() == activity) { 0256 return ~(uint)0; 0257 0258 } else { 0259 KConfig config(QStringLiteral("kactivitymanagerd-switcher"), KConfig::SimpleConfig); 0260 KConfigGroup times(&config, "LastUsed"); 0261 0262 return times.readEntry(activity, (uint)0); 0263 } 0264 } 0265 0266 bool SortedActivitiesModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const 0267 { 0268 const auto activityLeft = sourceModel()->data(sourceLeft, KActivities::ActivitiesModel::ActivityId).toString(); 0269 const auto activityRight = sourceModel()->data(sourceRight, KActivities::ActivitiesModel::ActivityId).toString(); 0270 0271 const auto timeLeft = lastUsedTime(activityLeft); 0272 const auto timeRight = lastUsedTime(activityRight); 0273 0274 return (timeLeft < timeRight) || (timeLeft == timeRight && activityLeft < activityRight); 0275 } 0276 0277 QHash<int, QByteArray> SortedActivitiesModel::roleNames() const 0278 { 0279 if (!sourceModel()) 0280 return QHash<int, QByteArray>(); 0281 0282 auto roleNames = sourceModel()->roleNames(); 0283 0284 roleNames[LastTimeUsed] = "lastTimeUsed"; 0285 roleNames[LastTimeUsedString] = "lastTimeUsedString"; 0286 roleNames[WindowCount] = "windowCount"; 0287 roleNames[HasWindows] = "hasWindows"; 0288 0289 return roleNames; 0290 } 0291 0292 QVariant SortedActivitiesModel::data(const QModelIndex &index, int role) const 0293 { 0294 if (role == KActivities::ActivitiesModel::ActivityBackground) { 0295 const auto activity = activityIdForIndex(index); 0296 0297 return backgrounds().forActivity[activity]; 0298 0299 } else if (role == LastTimeUsed || role == LastTimeUsedString) { 0300 const auto activity = activityIdForIndex(index); 0301 0302 const auto time = lastUsedTime(activity); 0303 0304 if (role == LastTimeUsed) { 0305 return QVariant(time); 0306 0307 } else { 0308 const auto now = QDateTime::currentDateTime().toSecsSinceEpoch(); 0309 0310 if (time == 0) 0311 return i18n("Used some time ago"); 0312 0313 auto diff = now - time; 0314 0315 // We do not need to be precise 0316 diff /= 60; 0317 const auto minutes = diff % 60; 0318 diff /= 60; 0319 const auto hours = diff % 24; 0320 diff /= 24; 0321 const auto days = diff % 30; 0322 diff /= 30; 0323 const auto months = diff % 12; 0324 diff /= 12; 0325 const auto years = diff; 0326 0327 return (years > 0) ? i18n("Used more than a year ago") 0328 : (months > 0) ? i18ncp("amount in months", "Used a month ago", "Used %1 months ago", months) 0329 : (days > 0) ? i18ncp("amount in days", "Used a day ago", "Used %1 days ago", days) 0330 : (hours > 0) ? i18ncp("amount in hours", "Used an hour ago", "Used %1 hours ago", hours) 0331 : (minutes > 0) ? i18ncp("amount in minutes", "Used a minute ago", "Used %1 minutes ago", minutes) 0332 : i18n("Used a moment ago"); 0333 } 0334 0335 } else if (role == HasWindows || role == WindowCount) { 0336 const auto activity = activityIdForIndex(index); 0337 0338 if (role == HasWindows) { 0339 return (m_activitiesWindows[activity].size() > 0); 0340 } else { 0341 return m_activitiesWindows[activity].size(); 0342 } 0343 0344 } else { 0345 return QSortFilterProxyModel::data(index, role); 0346 } 0347 } 0348 0349 QString SortedActivitiesModel::activityIdForIndex(const QModelIndex &index) const 0350 { 0351 return data(index, KActivities::ActivitiesModel::ActivityId).toString(); 0352 } 0353 0354 QString SortedActivitiesModel::activityIdForRow(int row) const 0355 { 0356 return activityIdForIndex(index(row, 0)); 0357 } 0358 0359 int SortedActivitiesModel::rowForActivityId(const QString &activity) const 0360 { 0361 int position = -1; 0362 0363 for (int row = 0; row < rowCount(); ++row) { 0364 if (activity == activityIdForRow(row)) { 0365 position = row; 0366 } 0367 } 0368 0369 return position; 0370 } 0371 0372 QString SortedActivitiesModel::relativeActivity(int relative) const 0373 { 0374 const auto currentActivity = m_activities->currentActivity(); 0375 0376 if (!sourceModel()) 0377 return QString(); 0378 0379 const auto currentRowCount = sourceModel()->rowCount(); 0380 0381 // x % 0 is undefined in c++ 0382 if (currentRowCount == 0) { 0383 return QString(); 0384 } 0385 0386 int currentActivityRow = 0; 0387 0388 for (; currentActivityRow < currentRowCount; currentActivityRow++) { 0389 if (activityIdForRow(currentActivityRow) == currentActivity) 0390 break; 0391 } 0392 0393 currentActivityRow = currentActivityRow + relative; 0394 0395 // wrap to within bounds for both positive and negative currentActivityRows 0396 currentActivityRow = (currentRowCount + (currentActivityRow % currentRowCount)) % currentRowCount; 0397 0398 return activityIdForRow(currentActivityRow); 0399 } 0400 0401 void SortedActivitiesModel::onCurrentActivityChanged(const QString ¤tActivity) 0402 { 0403 if (m_previousActivity == currentActivity) 0404 return; 0405 0406 const int previousActivityRow = rowForActivityId(m_previousActivity); 0407 rowChanged(previousActivityRow, {LastTimeUsed, LastTimeUsedString}); 0408 0409 m_previousActivity = currentActivity; 0410 0411 const int currentActivityRow = rowForActivityId(m_previousActivity); 0412 rowChanged(currentActivityRow, {LastTimeUsed, LastTimeUsedString}); 0413 } 0414 0415 void SortedActivitiesModel::onBackgroundsUpdated(const QStringList &activities) 0416 { 0417 for (const auto &activity : activities) { 0418 const int row = rowForActivityId(activity); 0419 rowChanged(row, {KActivities::ActivitiesModel::ActivityBackground}); 0420 } 0421 } 0422 0423 void SortedActivitiesModel::onWindowAdded(WId window) 0424 { 0425 KWindowInfo info(window, NET::Properties(), NET::WM2Activities); 0426 const QStringList activities = info.activities(); 0427 0428 if (activities.isEmpty() || activities.contains(QLatin1String{"00000000-0000-0000-0000-000000000000"})) 0429 return; 0430 0431 for (const auto &activity : activities) { 0432 if (!m_activitiesWindows[activity].contains(window)) { 0433 m_activitiesWindows[activity] << window; 0434 0435 rowChanged(rowForActivityId(activity), 0436 m_activitiesWindows.size() == 1 // 0437 ? QVector<int>{WindowCount, HasWindows} 0438 : QVector<int>{WindowCount}); 0439 } 0440 } 0441 } 0442 0443 void SortedActivitiesModel::onWindowRemoved(WId window) 0444 { 0445 for (const auto &activity : m_activitiesWindows.keys()) { 0446 if (m_activitiesWindows[activity].contains(window)) { 0447 m_activitiesWindows[activity].removeAll(window); 0448 0449 rowChanged(rowForActivityId(activity), 0450 m_activitiesWindows.size() == 0 // 0451 ? QVector<int>{WindowCount, HasWindows} 0452 : QVector<int>{WindowCount}); 0453 } 0454 } 0455 } 0456 0457 void SortedActivitiesModel::onWindowChanged(WId window, NET::Properties properties, NET::Properties2 properties2) 0458 { 0459 Q_UNUSED(properties); 0460 0461 if (properties2 & NET::WM2Activities) { 0462 onWindowRemoved(window); 0463 onWindowAdded(window); 0464 } 0465 } 0466 0467 void SortedActivitiesModel::rowChanged(int row, const QVector<int> &roles) 0468 { 0469 if (row == -1) 0470 return; 0471 Q_EMIT dataChanged(index(row, 0), index(row, 0), roles); 0472 }