File indexing completed on 2024-05-05 05:00:15
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2023 Stefano Crocco <stefano.crocco@alice.it> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "activitymanager.h" 0008 0009 #include "konqviewmanager.h" 0010 #include "konqmainwindow.h" 0011 #include "konqapplication.h" 0012 0013 #include <QStandardPaths> 0014 0015 #if QT_VERSION_MAJOR < 6 0016 #include <KActivities/Consumer> 0017 #else //QT_VERSION_MAJOR 0018 #include <PlasmaActivities/Consumer> 0019 #endif //QT_VERSION_MAJOR 0020 0021 #include <KX11Extras> 0022 #include <KWindowInfo> 0023 #include <KSharedConfig> 0024 #include <KConfigGroup> 0025 0026 #include <QTimer> 0027 0028 ActivityManager::ActivityManager(QObject* parent) : QObject(parent), m_activitiesConsumer(new KActivities::Consumer(this)) 0029 { 0030 connect(m_activitiesConsumer, &KActivities::Consumer::runningActivitiesChanged, this, &ActivityManager::handleRunningActivitiesChange); 0031 connect(m_activitiesConsumer, &KActivities::Consumer::activityRemoved, this, &ActivityManager::removeActivityState); 0032 connect(KX11Extras::self(), &KX11Extras::windowChanged, this, &ActivityManager::handleWindowChanged); 0033 } 0034 0035 ActivityManager::~ActivityManager() 0036 { 0037 } 0038 0039 QString ActivityManager::activitiesConfigPath() 0040 { 0041 static QString s_actitivitiesConfigPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1String("/activitiesrc"); 0042 return s_actitivitiesConfigPath; 0043 } 0044 0045 QString ActivityManager::activitiesGroupName() 0046 { 0047 static QString s_activitiesGroupName = QLatin1String("Activities"); 0048 return s_activitiesGroupName; 0049 } 0050 0051 void ActivityManager::closeWindowBecauseNotInRunningActivities(KonqMainWindow* window) 0052 { 0053 disconnect(window, &KonqMainWindow::closing, this, &ActivityManager::removeWindowFromActivities); 0054 0055 //If this is the last window, don't close it, because it would interfere with activities management. 0056 //Closing the last window would close the whole application, so the global activity manager wouldn't 0057 //restart Konqueror when switching to the activity the window was moved to. 0058 //TODO activities: check what happens with preloaded windows 0059 QList<KonqMainWindow*>* allWindows = KonqMainWindow::mainWindowList(); 0060 if (allWindows && allWindows->length() > 1) { 0061 window->close(); 0062 } 0063 } 0064 0065 void ActivityManager::handleRunningActivitiesChange(const QStringList& runningActivities) 0066 { 0067 QList<KonqMainWindow*> *windows = KonqMainWindow::mainWindowList(); 0068 if (!windows) { 0069 return; 0070 } 0071 0072 auto inRunningActivities = [runningActivities](const QStringList &activities) { 0073 //If activities is empty, it means that the window should be shown in all activities 0074 return activities.isEmpty() || 0075 std::any_of(activities.constBegin(), activities.constEnd(), [runningActivities](const QString &a){return runningActivities.contains(a);}); 0076 }; 0077 0078 QHash<KonqMainWindow*, QStringList> toClose; 0079 for (KonqMainWindow *w : *windows) { 0080 KWindowInfo info(w->winId(), NET::Properties(), NET::WM2Activities); 0081 QStringList activities = info.activities(); 0082 if (!inRunningActivities(activities)) { 0083 if (!w->isPreloaded()) { 0084 toClose.insert(w, activities); 0085 } 0086 } 0087 } 0088 0089 QStringList titlesToClose; 0090 QList<KonqMainWindow*> keys = toClose.keys(); 0091 std::transform(keys.constBegin(), keys.constEnd(), std::back_inserter(titlesToClose), [](KonqMainWindow *mw){return mw->windowTitle();}); 0092 0093 saveWindowsActivityInfo(toClose); 0094 0095 QStringList existingUuidsList; 0096 std::transform(windows->constBegin(), windows->constEnd(), std::back_inserter(existingUuidsList), [](KonqMainWindow *w){return w->uuid();}); 0097 QSet<QString> existingUuids(existingUuidsList.constBegin(), existingUuidsList.constEnd()); 0098 0099 QStringList expUuids; 0100 KConfigGroup grp = KSharedConfig::openConfig(activitiesConfigPath())->group(activitiesGroupName()); 0101 for (const QString &act : runningActivities) { 0102 QStringList uuids = grp.readEntry(act, QStringList{}); 0103 expUuids.append(uuids); 0104 } 0105 makeUnique(expUuids); 0106 0107 QStringList uuidsToRestore; 0108 std::copy_if(expUuids.constBegin(), expUuids.constEnd(), std::back_inserter(uuidsToRestore), [existingUuids](const QString &u){return !existingUuids.contains(u);}); 0109 0110 if (!uuidsToRestore.isEmpty()) { 0111 m_restoringStoppedActivity = true; 0112 // In theory, this should be set by KonquerorApplication::performStart. This is just for safety, to avoid the risk that m_restoringStoppedActivity remains set to true 0113 QTimer::singleShot(2000, [this](){m_restoringStoppedActivity = false;}); 0114 } 0115 0116 for (const QString &uuid : existingUuids) { 0117 restoreWindowFromActivityState(uuid); 0118 } 0119 0120 for (auto it = toClose.begin(); it != toClose.end(); ++it) { 0121 closeWindowBecauseNotInRunningActivities(it.key()); 0122 } 0123 } 0124 0125 void ActivityManager::saveWindowsActivityInfo(const QHash<KonqMainWindow *, QStringList>& windowsWithActivities) 0126 { 0127 KSharedConfig::Ptr config = KSharedConfig::openConfig(activitiesConfigPath()); 0128 0129 QStringList windowsUuids; 0130 0131 QMultiHash <QString, QString> activities; 0132 for (auto it = windowsWithActivities.constBegin(); it != windowsWithActivities.constEnd(); ++it) { 0133 KonqMainWindow *w = it.key(); 0134 windowsUuids.append(w->uuid()); 0135 if (w->isPreloaded()) { 0136 continue; 0137 } 0138 KConfigGroup grp = config->group(w->uuid()); 0139 w->saveProperties(grp); 0140 for (const QString & act : it.value()) { 0141 activities.insert(act, w->uuid()); 0142 } 0143 } 0144 0145 //Saving the windows belonging to each activity requires care: 0146 // - we can't just write the value of the entry in activities corresponding to each activity because there may 0147 // be windows belonging to that activity which aren't included in windowsWithActivities and which must be preserved 0148 // - we can't just append the existing entries for each activity to the corresponding entry in activities because if 0149 // one of the windows in windowsWithActivities used to belong to an activity but doesn't belong to it anymore, it 0150 // wouldn't be removed (since it was in the existing entry and thus would remain) 0151 // What we do is the following: 0152 // - read the existing windows for each activity from the configuration file 0153 // - remove from this list all the windows in windowsWithActivities (whose uuids are in windowsUuids) 0154 // - append the remaining existing entries to the new ones (in uuids) 0155 KConfigGroup grp = config->group(activitiesGroupName()); 0156 for (const QString & act : activities.uniqueKeys()) { 0157 QStringList uuids = activities.values(act); 0158 QStringList existingUuids = grp.readEntry(act, QStringList{}); 0159 for (const QString &u : windowsUuids) { 0160 existingUuids.removeOne(u); 0161 } 0162 uuids.append(existingUuids); 0163 makeUnique(uuids); 0164 grp.writeEntry(act, uuids); 0165 } 0166 0167 // grp = config->group("Windows UUID"); 0168 // for (KonqMainWindow *w : windowsWithActivities.keys()) { 0169 // grp.writeEntry(w->uuid(), w->windowTitle()); 0170 // } 0171 0172 config->sync(); 0173 } 0174 0175 void ActivityManager::handleWindowChanged(WId id, NET::Properties, NET::Properties2 prop2) 0176 { 0177 if (!(prop2 & NET::WM2Activities)) { 0178 return; 0179 } 0180 KonqMainWindow *w = qobject_cast<KonqMainWindow*>(QWidget::find(id)); 0181 if (!w) { 0182 return; 0183 } 0184 0185 KWindowInfo info(id, NET::Properties(), NET::WM2Activities); 0186 QStringList activities = info.activities(); 0187 //activities will be empty if the window should be shown in all activities. In this case, 0188 //there's nothing we need to do 0189 if (activities.isEmpty()) { 0190 return; 0191 } 0192 QStringList runningActivities = m_activitiesConsumer->runningActivities(); 0193 auto isRunning = [runningActivities](const QString &act){return runningActivities.contains(act);}; 0194 if (std::any_of(activities.constBegin(), activities.constEnd(), isRunning)) { 0195 return; 0196 } 0197 0198 QHash<KonqMainWindow*, QStringList> hash; 0199 hash.insert(w, activities); 0200 saveWindowsActivityInfo(hash); 0201 closeWindowBecauseNotInRunningActivities(w); 0202 } 0203 0204 void ActivityManager::removeWindowFromActivities(KonqMainWindow* window) 0205 { 0206 QString uuid = window->uuid(); 0207 KSharedConfig::Ptr config = KSharedConfig::openConfig(activitiesConfigPath()); 0208 0209 config->deleteGroup(uuid); 0210 0211 KConfigGroup grp = config->group(activitiesGroupName()); 0212 QStringList activities = grp.keyList(); 0213 for (const QString &act : activities) { 0214 QStringList windows = grp.readEntry(act, QStringList{}); 0215 //Remove the window uuid from the list of windows for the activity. If removeOne returns false, it means 0216 //the uuid wasn't in the the list, so there's no need to write it back 0217 if (windows.removeOne(uuid)) { 0218 grp.writeEntry(act, windows); 0219 } 0220 } 0221 config->sync(); 0222 } 0223 0224 void ActivityManager::removeActivityState(const QString& id) 0225 { 0226 KSharedConfig::Ptr config = KSharedConfig::openConfig(activitiesConfigPath()); 0227 0228 KConfigGroup activitiesGrp = config->group(activitiesGroupName()); 0229 0230 //We need to find out all windows which only belongs to the activity id 0231 //and remove all information about them 0232 QStringList activities = activitiesGrp.keyList(); 0233 QStringList uuids = activitiesGrp.readEntry(id, QStringList{}); 0234 QStringList otherActivitiesUuids; 0235 for (const QString &act : activities) { 0236 if (act != id) { 0237 otherActivitiesUuids.append(activitiesGrp.readEntry(act, QStringList{})); 0238 } 0239 } 0240 makeUnique(otherActivitiesUuids); 0241 for (const QString &uuid : uuids) { 0242 if (!otherActivitiesUuids.contains(uuid)) { 0243 config->deleteGroup(uuid); 0244 } 0245 } 0246 0247 activitiesGrp.deleteEntry(id); 0248 0249 config->sync(); 0250 } 0251 0252 KonqMainWindow* ActivityManager::restoreWindowFromActivityState(const QString& uuid) 0253 { 0254 //WARNING: for efficiency reasons, this method assumes no window with the given uuid exists. It's up to the caller to make sure of that 0255 0256 KSharedConfig::Ptr conf = KSharedConfig::openConfig(activitiesConfigPath()); 0257 KConfigGroup windowGrp = conf->group(uuid); 0258 KConfigGroup activitiesGrp = conf->group(activitiesGroupName()); 0259 0260 if (!windowGrp.exists()) { 0261 return nullptr; 0262 } 0263 KonqMainWindow *w = KonqViewManager::openSavedWindow(windowGrp); 0264 if (!w) { 0265 return nullptr; 0266 } 0267 0268 // QStringList activities; 0269 // QStringList activitiesEntries = activitiesGrp.keyList(); 0270 // auto activityHasWindow = [uuid, activitiesGrp] (const QString &act) { 0271 // return activitiesGrp.readEntry(act, QStringList{}).contains(uuid); 0272 // }; 0273 // std::copy_if(activitiesEntries.constBegin(), activitiesEntries.constEnd(), std::back_inserter(activities), activityHasWindow); 0274 // KX11Extras::setOnActivities(w->winId(), activities); 0275 w->show(); 0276 0277 //Don't keep information about the window, since they would become outdated 0278 conf->deleteGroup(uuid); 0279 conf->sync(); 0280 0281 return w; 0282 } 0283 0284 void ActivityManager::registerMainWindow(KonqMainWindow* window) 0285 { 0286 connect(window, &KonqMainWindow::closing, this, &ActivityManager::removeWindowFromActivities); 0287 } 0288 0289 void ActivityManager::makeUnique(QStringList& lst) 0290 { 0291 if (lst.isEmpty()) { 0292 return; 0293 } 0294 lst.sort(); 0295 auto last = std::unique(lst.begin(), lst.end()); 0296 lst.erase(last, lst.end()); 0297 }