File indexing completed on 2024-05-12 05:29:22
0001 /* 0002 * SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic@kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 // Self 0008 #include "Activities.h" 0009 #include "Activities_p.h" 0010 #include <kactivities-features.h> 0011 0012 // Qt 0013 #include <QDBusConnection> 0014 #include <QFile> 0015 #include <QMetaObject> 0016 #include <QStandardPaths> 0017 #include <QUuid> 0018 0019 // KDE 0020 #include <kauthorized.h> 0021 #include <klocalizedstring.h> 0022 #include <ksharedconfig.h> 0023 0024 // Utils 0025 #include <utils/d_ptr_implementation.h> 0026 #include <utils/range.h> 0027 0028 // Local 0029 #include "DebugActivities.h" 0030 #include "activitiesadaptor.h" 0031 #include "common/dbus/common.h" 0032 #include "ksmserver/KSMServer.h" 0033 0034 // Private 0035 #define ACTIVITY_MANAGER_CONFIG_FILE_NAME QStringLiteral("kactivitymanagerdrc") 0036 0037 namespace 0038 { 0039 inline bool nameBasedOrdering(const ActivityInfo &info, const ActivityInfo &other) 0040 { 0041 const auto comp = QString::compare(info.name, other.name, Qt::CaseInsensitive); 0042 return comp < 0 || (comp == 0 && info.id < other.id); 0043 } 0044 } 0045 0046 Activities::Private::Private(Activities *parent) 0047 : config(QStringLiteral("kactivitymanagerdrc")) 0048 , q(parent) 0049 { 0050 // qCDebug(KAMD_ACTIVITIES) << "Using this configuration file:" 0051 // << config.name() 0052 // << config.locationType() 0053 // << QStandardPaths::standardLocations(config.locationType()) 0054 // ; 0055 0056 // Reading activities from the config file. 0057 // Saving only the running activities means that if we have any 0058 // errors in the config, we might end up with all activities 0059 // stopped 0060 0061 const auto defaultState = !mainConfig().hasKey("runningActivities") ? Activities::Running 0062 : !mainConfig().hasKey("stoppedActivities") ? Activities::Stopped 0063 : Activities::Running; 0064 0065 const auto runningActivities = mainConfig().readEntry("runningActivities", QStringList()); 0066 const auto stoppedActivities = mainConfig().readEntry("stoppedActivities", QStringList()); 0067 0068 // Do we have a running activity? 0069 bool atLeastOneRunning = false; 0070 0071 for (const auto &activity : activityNameConfig().keyList()) { 0072 auto state = runningActivities.contains(activity) ? Activities::Running : stoppedActivities.contains(activity) ? Activities::Stopped : defaultState; 0073 0074 activities[activity] = state; 0075 0076 if (state == Activities::Running) { 0077 atLeastOneRunning = true; 0078 } 0079 } 0080 0081 // Is this our first start? 0082 if (activities.isEmpty()) { 0083 // We need to add this only after the service has been properly started 0084 KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("Activities")); 0085 // NOTE: config key still singular for retrocompatibility 0086 const QStringList names = cg.readEntry("defaultActivityName", QStringList{i18n("Default")}); 0087 0088 for (const auto &name : names) { 0089 QMetaObject::invokeMethod(q, "AddActivity", Qt::QueuedConnection, Q_ARG(QString, name)); 0090 } 0091 0092 } else if (!atLeastOneRunning) { 0093 // If we have no running activities, but we have activities, 0094 // we are in a problem. This should not happen unless the 0095 // configuration file is in a big problem and told us there 0096 // are no running activities, and enlists all of them as stopped. 0097 // In that case, we will pretend all of them are running 0098 qCWarning(KAMD_LOG_ACTIVITIES) << "The config file enlisted all activities as stopped"; 0099 for (auto activityKey = activities.keyBegin(); activityKey != activities.keyEnd(); ++activityKey) { 0100 activities[*activityKey] = Activities::Running; 0101 } 0102 } 0103 } 0104 0105 void Activities::Private::updateSortedActivityList() 0106 { 0107 QList<ActivityInfo> a; 0108 for (auto activityKey = activities.keyBegin(); activityKey != activities.keyEnd(); ++activityKey) { 0109 a.append(q->ActivityInformation(*activityKey)); 0110 } 0111 0112 std::sort(a.begin(), a.end(), &nameBasedOrdering); 0113 0114 QWriteLocker lock(&activitiesLock); 0115 sortedActivities = a; 0116 } 0117 0118 void Activities::Private::loadLastActivity() 0119 { 0120 // This is called from constructor, no need for locking 0121 0122 // If there are no public activities, try to load the last used activity 0123 const auto lastUsedActivity = mainConfig().readEntry("currentActivity", QString()); 0124 0125 setCurrentActivity((lastUsedActivity.isEmpty() && activities.size() > 0) ? activities.keys().at(0) : lastUsedActivity); 0126 } 0127 0128 Activities::Private::~Private() 0129 { 0130 configSync(); 0131 } 0132 0133 bool Activities::Private::setCurrentActivity(const QString &activity) 0134 { 0135 { 0136 // There is nothing expensive in this block, not a problem to lock 0137 QWriteLocker lock(&activitiesLock); 0138 0139 // Should we change the activity at all? 0140 if (currentActivity == activity) { 0141 return true; 0142 } 0143 0144 // If the activity is empty, this means we are entering a limbo state 0145 if (activity.isEmpty()) { 0146 currentActivity.clear(); 0147 Q_EMIT q->CurrentActivityChanged(currentActivity); 0148 return true; 0149 } 0150 0151 // Does the requested activity exist? 0152 if (!activities.contains(activity)) { 0153 return false; 0154 } 0155 } 0156 0157 // Start activity 0158 q->StartActivity(activity); 0159 0160 // Saving the current activity, and notifying 0161 // clients of the change 0162 currentActivity = activity; 0163 0164 mainConfig().writeEntry("currentActivity", activity); 0165 0166 scheduleConfigSync(); 0167 0168 Q_EMIT q->CurrentActivityChanged(activity); 0169 0170 return true; 0171 } 0172 0173 bool Activities::Private::previousActivity() 0174 { 0175 const auto a = q->ListActivities(Activities::Running); 0176 0177 for (int i = 0; i < a.count(); ++i) { 0178 if (a[i] == currentActivity) { 0179 return setCurrentActivity(a[(i + a.size() - 1) % a.size()]); 0180 } 0181 } 0182 0183 return false; 0184 } 0185 0186 bool Activities::Private::nextActivity() 0187 { 0188 const auto a = q->ListActivities(Activities::Running); 0189 0190 for (int i = 0; i < a.count(); ++i) { 0191 if (a[i] == currentActivity) { 0192 return setCurrentActivity(a[(i + 1) % a.size()]); 0193 } 0194 } 0195 0196 return false; 0197 } 0198 0199 QString Activities::Private::addActivity(const QString &name) 0200 { 0201 QString activity; 0202 0203 if (name.isEmpty()) { 0204 Q_ASSERT(!name.isEmpty()); 0205 return activity; 0206 } 0207 0208 int activitiesCount = 0; 0209 0210 { 0211 QWriteLocker lock(&activitiesLock); 0212 0213 // Ensuring a new Uuid. The loop should usually end after only 0214 // one iteration 0215 while (activity.isEmpty() || activities.contains(activity)) { 0216 activity = QUuid::createUuid().toString().mid(1, 36); 0217 } 0218 0219 // Saves the activity info to the config 0220 0221 activities[activity] = Invalid; 0222 activitiesCount = activities.size(); 0223 } 0224 0225 setActivityState(activity, Running); 0226 0227 q->SetActivityName(activity, name); 0228 0229 updateSortedActivityList(); 0230 0231 Q_EMIT q->ActivityAdded(activity); 0232 0233 scheduleConfigSync(); 0234 0235 if (activitiesCount == 1) { 0236 q->SetCurrentActivity(activity); 0237 } 0238 0239 return activity; 0240 } 0241 0242 void Activities::Private::removeActivity(const QString &activity) 0243 { 0244 Q_ASSERT(!activity.isEmpty()); 0245 0246 // Sanity checks 0247 { 0248 QWriteLocker lock(&activitiesLock); 0249 0250 if (!activities.contains(activity)) { 0251 return; 0252 } 0253 0254 // Is somebody trying to remove the last activity? 0255 if (activities.size() == 1) { 0256 return; 0257 } 0258 } 0259 0260 // If the activity is running, stash it 0261 q->StopActivity(activity); 0262 0263 setActivityState(activity, Activities::Invalid); 0264 0265 bool currentActivityDeleted = false; 0266 0267 { 0268 QWriteLocker lock(&activitiesLock); 0269 // Removing the activity 0270 activities.remove(activity); 0271 0272 for (int i = 0; i < sortedActivities.count(); ++i) { 0273 if (sortedActivities[i].id == activity) { 0274 sortedActivities.remove(i); 0275 break; 0276 } 0277 } 0278 0279 // If the removed activity was the current one, 0280 // set another activity as current 0281 currentActivityDeleted = (currentActivity == activity); 0282 } 0283 0284 activityNameConfig().deleteEntry(activity); 0285 activityDescriptionConfig().deleteEntry(activity); 0286 activityIconConfig().deleteEntry(activity); 0287 0288 if (currentActivityDeleted) { 0289 ensureCurrentActivityIsRunning(); 0290 } 0291 0292 Q_EMIT q->ActivityRemoved(activity); 0293 0294 QMetaObject::invokeMethod(q, "ActivityRemoved", Qt::QueuedConnection, Q_ARG(QString, activity)); 0295 0296 QMetaObject::invokeMethod(this, "configSync", Qt::QueuedConnection); 0297 } 0298 0299 void Activities::Private::scheduleConfigSync() 0300 { 0301 static const auto shortInterval = 1000; 0302 0303 // If the timer is not running, or has a longer interval than we need, 0304 // start it 0305 // Note: If you want to add multiple different delays for different 0306 // events based on the importance of how quickly something needs 0307 // to be synced to the config, don't. Since the QTimer lives in a 0308 // separate thread, we have to communicate with it in via 0309 // queued connections, which means that we don't know whether 0310 // the timer was already started when this method was invoked, 0311 // we do not know whether the interval is properly set etc. 0312 if (!configSyncTimer.isActive()) { 0313 QMetaObject::invokeMethod(&configSyncTimer, "start", Qt::QueuedConnection, Q_ARG(int, shortInterval)); 0314 } 0315 } 0316 0317 void Activities::Private::configSync() 0318 { 0319 // Stop the timer and reset the interval to zero 0320 QMetaObject::invokeMethod(&configSyncTimer, "stop", Qt::QueuedConnection); 0321 config.sync(); 0322 } 0323 0324 void Activities::Private::setActivityState(const QString &activity, Activities::State state) 0325 { 0326 bool configNeedsUpdating = false; 0327 0328 { 0329 QWriteLocker lock(&activitiesLock); 0330 0331 Q_ASSERT(activities.contains(activity)); 0332 0333 if (activities.value(activity) == state) { 0334 return; 0335 } 0336 0337 // Treating 'Starting' as 'Running', and 'Stopping' as 'Stopped' 0338 // as far as the config file is concerned 0339 configNeedsUpdating = ((activities[activity] & 4) != (state & 4)); 0340 0341 activities[activity] = state; 0342 } 0343 0344 switch (state) { 0345 case Activities::Running: 0346 Q_EMIT q->ActivityStarted(activity); 0347 break; 0348 0349 case Activities::Stopped: 0350 Q_EMIT q->ActivityStopped(activity); 0351 break; 0352 0353 default: 0354 break; 0355 } 0356 0357 Q_EMIT q->ActivityStateChanged(activity, state); 0358 0359 if (configNeedsUpdating) { 0360 QReadLocker lock(&activitiesLock); 0361 0362 mainConfig().writeEntry("runningActivities", activities.keys(Activities::Running) + activities.keys(Activities::Starting)); 0363 mainConfig().writeEntry("stoppedActivities", activities.keys(Activities::Stopped) + activities.keys(Activities::Stopping)); 0364 scheduleConfigSync(); 0365 } 0366 0367 updateSortedActivityList(); 0368 } 0369 0370 void Activities::Private::ensureCurrentActivityIsRunning() 0371 { 0372 // If the current activity is not running, 0373 // make some other activity current 0374 0375 const auto runningActivities = q->ListActivities(Activities::Running); 0376 0377 if (!runningActivities.contains(currentActivity) && runningActivities.size() > 0) { 0378 setCurrentActivity(runningActivities.first()); 0379 } 0380 } 0381 0382 void Activities::Private::activitySessionStateChanged(const QString &activity, int status) 0383 { 0384 QString currentActivity = this->currentActivity; 0385 0386 { 0387 QReadLocker lock(&activitiesLock); 0388 if (!activities.contains(activity)) { 0389 return; 0390 } 0391 } 0392 0393 switch (status) { 0394 case KSMServer::Started: 0395 case KSMServer::FailedToStop: 0396 setActivityState(activity, Activities::Running); 0397 break; 0398 0399 case KSMServer::Stopped: 0400 setActivityState(activity, Activities::Stopped); 0401 0402 if (currentActivity == activity) { 0403 ensureCurrentActivityIsRunning(); 0404 } 0405 0406 break; 0407 } 0408 0409 QMetaObject::invokeMethod(this, "configSync", Qt::QueuedConnection); 0410 } 0411 0412 // Main 0413 0414 Activities::Activities(QObject *parent) 0415 : Module(QStringLiteral("activities"), parent) 0416 , d(this) 0417 { 0418 qCDebug(KAMD_LOG_ACTIVITIES) << "Starting the KDE Activity Manager daemon" << QDateTime::currentDateTime(); 0419 0420 // Basic initialization //////////////////////////////////////////////////// 0421 0422 // Initializing D-Bus service 0423 0424 new ActivitiesAdaptor(this); 0425 QDBusConnection::sessionBus().registerObject(KAMD_DBUS_OBJECT_PATH("Activities"), this); 0426 0427 // Initializing config 0428 0429 qCDebug(KAMD_LOG_ACTIVITIES) << "Config timer connecting..."; 0430 d->connect(&d->configSyncTimer, SIGNAL(timeout()), SLOT(configSync()), Qt::QueuedConnection); 0431 0432 d->configSyncTimer.setSingleShot(true); 0433 0434 d->ksmserver = new KSMServer(this); 0435 d->connect(d->ksmserver, SIGNAL(activitySessionStateChanged(QString, int)), SLOT(activitySessionStateChanged(QString, int))); 0436 0437 d->updateSortedActivityList(); 0438 // Loading the last used activity, if possible 0439 d->loadLastActivity(); 0440 } 0441 0442 Activities::~Activities() 0443 { 0444 } 0445 0446 QString Activities::CurrentActivity() const 0447 { 0448 QReadLocker lock(&d->activitiesLock); 0449 return d->currentActivity; 0450 } 0451 0452 bool Activities::SetCurrentActivity(const QString &activity) 0453 { 0454 // Public method can not put us in a limbo state 0455 if (activity.isEmpty()) { 0456 return false; 0457 } 0458 0459 return d->setCurrentActivity(activity); 0460 } 0461 0462 bool Activities::PreviousActivity() 0463 { 0464 return d->previousActivity(); 0465 } 0466 0467 bool Activities::NextActivity() 0468 { 0469 return d->nextActivity(); 0470 } 0471 0472 QString Activities::AddActivity(const QString &name) 0473 { 0474 // We do not care about authorization if this is the first start 0475 if (!d->activities.isEmpty() && !KAuthorized::authorize(QStringLiteral("plasma-desktop/add_activities"))) { 0476 return QString(); 0477 } 0478 0479 return d->addActivity(name); 0480 } 0481 0482 void Activities::RemoveActivity(const QString &activity) 0483 { 0484 if (!KAuthorized::authorize(QStringLiteral("plasma-desktop/add_activities"))) { 0485 return; 0486 } 0487 0488 d->removeActivity(activity); 0489 } 0490 0491 QStringList Activities::ListActivities() const 0492 { 0493 QReadLocker lock(&d->activitiesLock); 0494 0495 QStringList s; 0496 for (const auto &a : d->sortedActivities) { 0497 s << a.id; 0498 } 0499 return s; 0500 } 0501 0502 QStringList Activities::ListActivities(int state) const 0503 { 0504 QReadLocker lock(&d->activitiesLock); 0505 0506 QStringList s; 0507 for (const auto &a : d->sortedActivities) { 0508 if (a.state == (State)state) { 0509 s << a.id; 0510 } 0511 } 0512 return s; 0513 } 0514 0515 QList<ActivityInfo> Activities::ListActivitiesWithInformation() const 0516 { 0517 using namespace kamd::utils; 0518 0519 // Mapping activity ids to info 0520 0521 return as_collection<QList<ActivityInfo>>(ListActivities() | transformed(&Activities::ActivityInformation, this)); 0522 } 0523 0524 ActivityInfo Activities::ActivityInformation(const QString &activity) const 0525 { 0526 return ActivityInfo{activity, ActivityName(activity), ActivityDescription(activity), ActivityIcon(activity), ActivityState(activity)}; 0527 } 0528 0529 #define CREATE_GETTER_AND_SETTER(What) \ 0530 QString Activities::Activity##What(const QString &activity) const \ 0531 { \ 0532 QReadLocker lock(&d->activitiesLock); \ 0533 return d->activities.contains(activity) ? d->activity##What(activity) : QString(); \ 0534 } \ 0535 \ 0536 void Activities::SetActivity##What(const QString &activity, const QString &value) \ 0537 { \ 0538 { \ 0539 QReadLocker lock(&d->activitiesLock); \ 0540 if (value == d->activity##What(activity) || !d->activities.contains(activity)) { \ 0541 return; \ 0542 } \ 0543 } \ 0544 \ 0545 d->activity##What##Config().writeEntry(activity, value); \ 0546 d->scheduleConfigSync(); \ 0547 \ 0548 Q_EMIT Activity##What##Changed(activity, value); \ 0549 Q_EMIT ActivityChanged(activity); \ 0550 } 0551 0552 CREATE_GETTER_AND_SETTER(Name) 0553 CREATE_GETTER_AND_SETTER(Description) 0554 CREATE_GETTER_AND_SETTER(Icon) 0555 0556 #undef CREATE_GETTER_AND_SETTER 0557 0558 // Main 0559 0560 void Activities::StartActivity(const QString &activity) 0561 { 0562 { 0563 QReadLocker lock(&d->activitiesLock); 0564 if (!d->activities.contains(activity) || d->activities[activity] != Stopped) { 0565 return; 0566 } 0567 } 0568 0569 d->setActivityState(activity, Starting); 0570 d->ksmserver->startActivitySession(activity); 0571 } 0572 0573 void Activities::StopActivity(const QString &activity) 0574 { 0575 { 0576 QReadLocker lock(&d->activitiesLock); 0577 if (!d->activities.contains(activity) // 0578 || d->activities[activity] == Stopped // 0579 || d->activities.size() == 1 // 0580 || d->activities.keys(Activities::Running).size() <= 1) { 0581 return; 0582 } 0583 } 0584 0585 d->setActivityState(activity, Stopping); 0586 d->ksmserver->stopActivitySession(activity); 0587 } 0588 0589 int Activities::ActivityState(const QString &activity) const 0590 { 0591 QReadLocker lock(&d->activitiesLock); 0592 return d->activities.contains(activity) ? d->activities[activity] : Invalid; 0593 } 0594 0595 #include "moc_Activities.cpp" 0596 0597 #include "moc_Activities_p.cpp"