File indexing completed on 2023-10-01 04:11:43

0001 /*
0002     SPDX-FileCopyrightText: 2007 Matt Broadstone <mbroadst@gmail.com>
0003     SPDX-FileCopyrightText: 2007-2011 Aaron Seigo <aseigo@kde.org>
0004     SPDX-FileCopyrightText: 2007 Riccardo Iaconelli <riccardo@kde.org>
0005     SPDX-FileCopyrightText: 2009 Chani Armitage <chani@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "corona.h"
0011 #include "private/corona_p.h"
0012 
0013 #include <QDebug>
0014 #include <QGuiApplication>
0015 #include <QMimeData>
0016 #include <QPainter>
0017 #include <QScreen>
0018 #include <QTimer>
0019 
0020 #include <KLocalizedString>
0021 
0022 #include <cmath>
0023 
0024 #include "containment.h"
0025 #include "debug_p.h"
0026 #include "pluginloader.h"
0027 #include "private/applet_p.h"
0028 #include "private/containment_p.h"
0029 #include "private/timetracker.h"
0030 
0031 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
0032 #include "packagestructure.h"
0033 #include "private/package_p.h"
0034 #endif
0035 
0036 using namespace Plasma;
0037 
0038 namespace Plasma
0039 {
0040 Corona::Corona(QObject *parent)
0041     : QObject(parent)
0042     , d(new CoronaPrivate(this))
0043 {
0044     d->init();
0045 
0046 #ifndef NDEBUG
0047     if (qEnvironmentVariableIsSet("PLASMA_TRACK_STARTUP")) {
0048         new TimeTracker(this);
0049     }
0050 #endif
0051 }
0052 
0053 Corona::~Corona()
0054 {
0055     KConfigGroup trans(KSharedConfig::openConfig(), "PlasmaTransientsConfig");
0056     trans.deleteGroup();
0057 
0058     delete d;
0059 }
0060 
0061 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 6)
0062 Plasma::Package Corona::package() const
0063 {
0064     return Package(d->package);
0065 }
0066 
0067 void Corona::setPackage(const Plasma::Package &package)
0068 {
0069     setKPackage(*package.d->internalPackage);
0070     Q_EMIT packageChanged(package);
0071 }
0072 #endif
0073 
0074 KPackage::Package Corona::kPackage() const
0075 {
0076     return d->package;
0077 }
0078 
0079 void Corona::setKPackage(const KPackage::Package &package)
0080 {
0081     d->package = package;
0082     Q_EMIT kPackageChanged(package);
0083 }
0084 
0085 void Corona::saveLayout(const QString &configName) const
0086 {
0087     KSharedConfigPtr c;
0088 
0089     if (configName.isEmpty() || configName == d->configName) {
0090         c = config();
0091     } else {
0092         c = KSharedConfig::openConfig(configName, KConfig::SimpleConfig);
0093     }
0094 
0095     d->saveLayout(c);
0096 }
0097 
0098 void Corona::exportLayout(KConfigGroup &config, QList<Containment *> containments)
0099 {
0100     const auto groupList = config.groupList();
0101     for (const QString &group : groupList) {
0102         KConfigGroup cg(&config, group);
0103         cg.deleteGroup();
0104     }
0105 
0106     // temporarily unlock so that removal works
0107     Types::ImmutabilityType oldImm = immutability();
0108     d->immutability = Types::Mutable;
0109 
0110     KConfigGroup dest(&config, "Containments");
0111     KConfigGroup dummy;
0112     for (Plasma::Containment *c : std::as_const(containments)) {
0113         c->save(dummy);
0114         c->config().reparent(&dest);
0115 
0116         // ensure the containment is unlocked
0117         // this is done directly because we have to bypass any Types::SystemImmutable checks
0118         c->Applet::d->immutability = Types::Mutable;
0119         const auto lstApplet = c->applets();
0120         for (Applet *a : lstApplet) {
0121             a->d->immutability = Types::Mutable;
0122         }
0123 
0124         c->destroy();
0125     }
0126 
0127     // restore immutability
0128     d->immutability = oldImm;
0129 
0130     config.sync();
0131 }
0132 
0133 void Corona::requestConfigSync()
0134 {
0135     // constant controlling how long between requesting a configuration sync
0136     // and one happening should occur. currently 10 seconds
0137     static const int CONFIG_SYNC_TIMEOUT = 10000;
0138 
0139     // TODO: should we check into our immutability before doing this?
0140 
0141     // NOTE: this is a pretty simplistic model: we simply save no more than CONFIG_SYNC_TIMEOUT
0142     //      after the first time this is called. not much of a heuristic for save points, but
0143     //      it should at least compress these activities a bit and provide a way for applet
0144     //      authors to ween themselves from the sync() disease. A more interesting/dynamic
0145     //      algorithm for determining when to actually sync() to disk might be better, though.
0146     if (!d->configSyncTimer->isActive()) {
0147         d->configSyncTimer->start(CONFIG_SYNC_TIMEOUT);
0148     }
0149 }
0150 
0151 void Corona::requireConfigSync()
0152 {
0153     d->syncConfig();
0154 }
0155 
0156 void Corona::loadLayout(const QString &configName)
0157 {
0158     if (!configName.isEmpty() && configName != d->configName) {
0159         // if we have a new config name passed in, then use that as the config file for this Corona
0160         d->config = nullptr;
0161         d->configName = configName;
0162     }
0163 
0164     KConfigGroup conf(config(), QString());
0165     if (!config()->groupList().isEmpty()) {
0166         d->importLayout(conf, false);
0167     } else {
0168         loadDefaultLayout();
0169         d->notifyContainmentsReady();
0170     }
0171 
0172     KConfigGroup cg(config(), "General");
0173     setImmutability((Plasma::Types::ImmutabilityType)cg.readEntry("immutability", (int)Plasma::Types::Mutable));
0174 }
0175 
0176 QList<Plasma::Containment *> Corona::importLayout(const KConfigGroup &conf)
0177 {
0178     return d->importLayout(conf, true);
0179 }
0180 
0181 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 46)
0182 Containment *Corona::containmentForScreen(int screen) const
0183 {
0184     for (Containment *containment : std::as_const(d->containments)) {
0185         if (containment->screen() == screen //
0186             && (containment->containmentType() == Plasma::Types::DesktopContainment //
0187                 || containment->containmentType() == Plasma::Types::CustomContainment)) {
0188             return containment;
0189         }
0190     }
0191 
0192     return nullptr;
0193 }
0194 
0195 Containment *Corona::containmentForScreen(int screen, const QString &defaultPluginIfNonExistent, const QVariantList &defaultArgs)
0196 {
0197     return containmentForScreen(screen, QString(), defaultPluginIfNonExistent, defaultArgs);
0198 }
0199 #endif
0200 
0201 Containment *Corona::containmentForScreen(int screen, const QString &activity, const QString &defaultPluginIfNonExistent, const QVariantList &defaultArgs)
0202 {
0203     Containment *containment = nullptr;
0204 
0205     for (Containment *cont : std::as_const(d->containments)) {
0206         /* clang-format off */
0207         if (cont->lastScreen() == screen
0208             && ((cont->activity().isEmpty() || activity.isEmpty()) || cont->activity() == activity)
0209             && (cont->containmentType() == Plasma::Types::DesktopContainment
0210                 || cont->containmentType() == Plasma::Types::CustomContainment)) { /* clang-format on */
0211             containment = cont;
0212         }
0213     }
0214 
0215     if (!containment && !defaultPluginIfNonExistent.isEmpty()) {
0216         // screen requests are allowed to bypass immutability
0217         if (screen >= 0) {
0218             Plasma::Types::ImmutabilityType imm = d->immutability;
0219             d->immutability = Types::Mutable;
0220             containment = d->addContainment(defaultPluginIfNonExistent, defaultArgs, 0, screen, false);
0221 
0222             d->immutability = imm;
0223         }
0224     }
0225 
0226     if (containment) {
0227         containment->setActivity(activity);
0228     }
0229     return containment;
0230 }
0231 
0232 QList<Containment *> Corona::containmentsForActivity(const QString &activity)
0233 {
0234     QList<Containment *> conts;
0235 
0236     if (activity.isEmpty()) {
0237         return conts;
0238     }
0239 
0240     std::copy_if(d->containments.begin(), d->containments.end(), std::back_inserter(conts), [activity](Containment *cont) {
0241         return cont->activity() == activity //
0242             && (cont->containmentType() == Plasma::Types::DesktopContainment //
0243                 || cont->containmentType() == Plasma::Types::CustomContainment);
0244     });
0245 
0246     return conts;
0247 }
0248 
0249 QList<Containment *> Corona::containmentsForScreen(int screen)
0250 {
0251     QList<Containment *> conts;
0252 
0253     if (screen < 0) {
0254         return conts;
0255     }
0256 
0257     std::copy_if(d->containments.begin(), d->containments.end(), std::back_inserter(conts), [screen](Containment *cont) {
0258         return cont->lastScreen() == screen //
0259             && (cont->containmentType() == Plasma::Types::DesktopContainment //
0260                 || cont->containmentType() == Plasma::Types::CustomContainment);
0261     });
0262 
0263     return conts;
0264 }
0265 
0266 QList<Containment *> Corona::containments() const
0267 {
0268     return d->containments;
0269 }
0270 
0271 bool Corona::isStartupCompleted() const
0272 {
0273     return d->containmentsStarting <= 0;
0274 }
0275 
0276 KSharedConfigPtr Corona::config() const
0277 {
0278     if (!d->config) {
0279         d->config = KSharedConfig::openConfig(d->configName, KConfig::SimpleConfig);
0280     }
0281 
0282     return d->config;
0283 }
0284 
0285 Containment *Corona::createContainment(const QString &name, const QVariantList &args)
0286 {
0287     if (d->immutability == Types::Mutable || args.contains(QVariant::fromValue(QStringLiteral("org.kde.plasma:force-create")))) {
0288         return d->addContainment(name, args, 0, -1, false);
0289     }
0290 
0291     return nullptr;
0292 }
0293 
0294 Containment *Corona::createContainmentDelayed(const QString &name, const QVariantList &args)
0295 {
0296     if (d->immutability == Types::Mutable) {
0297         return d->addContainment(name, args, 0, -1, true);
0298     }
0299 
0300     return nullptr;
0301 }
0302 
0303 int Corona::screenForContainment(const Containment *) const
0304 {
0305     return -1;
0306 }
0307 
0308 int Corona::numScreens() const
0309 {
0310     return 1;
0311 }
0312 
0313 QRegion Corona::availableScreenRegion(int id) const
0314 {
0315     return QRegion(screenGeometry(id));
0316 }
0317 
0318 QRect Corona::availableScreenRect(int id) const
0319 {
0320     return screenGeometry(id);
0321 }
0322 
0323 void Corona::loadDefaultLayout()
0324 {
0325     // Default implementation does nothing
0326 }
0327 
0328 Types::ImmutabilityType Corona::immutability() const
0329 {
0330     return d->immutability;
0331 }
0332 
0333 void Corona::setImmutability(const Types::ImmutabilityType immutable)
0334 {
0335     if (d->immutability == immutable || d->immutability == Types::SystemImmutable) {
0336         return;
0337     }
0338 
0339 #ifndef NDEBUG
0340     // qCDebug(LOG_PLASMA) << "setting immutability to" << immutable;
0341 #endif
0342     d->immutability = immutable;
0343     d->updateContainmentImmutability();
0344     // tell non-containments that might care (like plasmaapp or a custom corona)
0345     Q_EMIT immutabilityChanged(immutable);
0346 
0347     // update our actions
0348     QAction *action = d->actions.action(QStringLiteral("lock widgets"));
0349     if (action) {
0350         if (d->immutability == Types::SystemImmutable) {
0351             action->setEnabled(false);
0352             action->setVisible(false);
0353         } else {
0354             bool unlocked = d->immutability == Types::Mutable;
0355             action->setText(unlocked ? i18n("Lock Widgets") : i18n("Unlock Widgets"));
0356             action->setIcon(QIcon::fromTheme(unlocked ? QStringLiteral("object-locked") : QStringLiteral("object-unlocked")));
0357             action->setEnabled(true);
0358             action->setVisible(true);
0359         }
0360     }
0361 
0362     action = d->actions.action(QStringLiteral("edit mode"));
0363     if (action) {
0364         switch (d->immutability) {
0365         case Types::UserImmutable:
0366             action->setEnabled(false);
0367             action->setVisible(true);
0368             break;
0369         case Types::SystemImmutable:
0370             action->setEnabled(false);
0371             action->setVisible(false);
0372             break;
0373         case Types::Mutable:
0374         default:
0375             action->setEnabled(true);
0376             action->setVisible(true);
0377             break;
0378         }
0379     }
0380 
0381     if (d->immutability != Types::SystemImmutable) {
0382         KConfigGroup cg(config(), "General");
0383 
0384         // we call the dptr member directly for locked since isImmutable()
0385         // also checks kiosk and parent containers
0386         cg.writeEntry("immutability", (int)d->immutability);
0387         requestConfigSync();
0388     }
0389 
0390     if (d->immutability != Types::Mutable) {
0391         setEditMode(false);
0392     }
0393 }
0394 
0395 void Corona::setEditMode(bool edit)
0396 {
0397     if (edit == d->editMode || (edit && d->immutability != Plasma::Types::Mutable)) {
0398         return;
0399     }
0400 
0401     QAction *editAction = d->actions.action(QStringLiteral("edit mode"));
0402     if (editAction) {
0403         if (edit) {
0404             editAction->setText(i18n("Exit Edit Mode"));
0405         } else {
0406             editAction->setText(i18n("Enter Edit Mode"));
0407         }
0408     }
0409 
0410     if (!edit) {
0411         requireConfigSync();
0412     }
0413 
0414     d->editMode = edit;
0415     Q_EMIT editModeChanged(edit);
0416 }
0417 
0418 bool Corona::isEditMode() const
0419 {
0420     return d->editMode;
0421 }
0422 
0423 QList<Plasma::Types::Location> Corona::freeEdges(int screen) const
0424 {
0425     QList<Plasma::Types::Location> freeEdges;
0426     /* clang-format off */
0427     freeEdges << Plasma::Types::TopEdge
0428               << Plasma::Types::BottomEdge
0429               << Plasma::Types::LeftEdge
0430               << Plasma::Types::RightEdge;
0431     /* clang-format on */
0432 
0433     const auto containments = this->containments();
0434     for (Containment *containment : containments) {
0435         if (containment->screen() == screen && freeEdges.contains(containment->location())) {
0436             freeEdges.removeAll(containment->location());
0437         }
0438     }
0439 
0440     return freeEdges;
0441 }
0442 
0443 KActionCollection *Corona::actions() const
0444 {
0445     return &d->actions;
0446 }
0447 
0448 CoronaPrivate::CoronaPrivate(Corona *corona)
0449     : q(corona)
0450     , immutability(Types::Mutable)
0451     , config(nullptr)
0452     , configSyncTimer(new QTimer(corona))
0453     , actions(corona)
0454     , containmentsStarting(0)
0455 {
0456     // TODO: make Package path configurable
0457 
0458     if (QCoreApplication::instance()) {
0459         configName = QCoreApplication::instance()->applicationName() + QStringLiteral("-appletsrc");
0460     } else {
0461         configName = QStringLiteral("plasma-appletsrc");
0462     }
0463 }
0464 
0465 CoronaPrivate::~CoronaPrivate()
0466 {
0467     qDeleteAll(containments);
0468 }
0469 
0470 void CoronaPrivate::init()
0471 {
0472     desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Desktop");
0473 
0474     configSyncTimer->setSingleShot(true);
0475     QObject::connect(configSyncTimer, SIGNAL(timeout()), q, SLOT(syncConfig()));
0476 
0477     // some common actions
0478     actions.setConfigGroup(QStringLiteral("Shortcuts"));
0479 
0480     QAction *lockAction = actions.add<QAction>(QStringLiteral("lock widgets"));
0481     QObject::connect(lockAction, SIGNAL(triggered(bool)), q, SLOT(toggleImmutability()));
0482     lockAction->setText(i18n("Lock Widgets"));
0483     lockAction->setAutoRepeat(true);
0484     lockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked")));
0485     lockAction->setData(Plasma::Types::ControlAction);
0486     lockAction->setShortcutContext(Qt::ApplicationShortcut);
0487 
0488     // fake containment/applet actions
0489     KActionCollection *containmentActions = AppletPrivate::defaultActions(q); // containment has to start with applet stuff
0490     ContainmentPrivate::addDefaultActions(containmentActions); // now it's really containment
0491 
0492     QAction *editAction = actions.add<QAction>(QStringLiteral("edit mode"));
0493     QObject::connect(editAction, &QAction::triggered, q, [this]() {
0494         q->setEditMode(!q->isEditMode());
0495     });
0496     editAction->setText(i18n("Enter Edit Mode"));
0497     editAction->setAutoRepeat(true);
0498     editAction->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
0499     editAction->setData(Plasma::Types::ControlAction);
0500     editAction->setShortcut(QKeySequence(QStringLiteral("alt+d, e")));
0501     editAction->setShortcutContext(Qt::ApplicationShortcut);
0502 }
0503 
0504 void CoronaPrivate::toggleImmutability()
0505 {
0506     if (immutability == Types::Mutable) {
0507         q->setImmutability(Types::UserImmutable);
0508     } else {
0509         q->setImmutability(Types::Mutable);
0510     }
0511 }
0512 
0513 void CoronaPrivate::saveLayout(KSharedConfigPtr cg) const
0514 {
0515     KConfigGroup containmentsGroup(cg, "Containments");
0516     for (const Containment *containment : containments) {
0517         QString cid = QString::number(containment->id());
0518         KConfigGroup containmentConfig(&containmentsGroup, cid);
0519         containment->save(containmentConfig);
0520     }
0521 }
0522 
0523 void CoronaPrivate::updateContainmentImmutability()
0524 {
0525     for (Containment *c : std::as_const(containments)) {
0526         // we need to tell each containment that immutability has been altered
0527         c->updateConstraints(Types::ImmutableConstraint);
0528     }
0529 }
0530 
0531 void CoronaPrivate::containmentDestroyed(QObject *obj)
0532 {
0533     // we do a static_cast here since it really isn't an Containment by this
0534     // point anymore since we are in the qobject dtor. we don't actually
0535     // try and do anything with it, we just need the value of the pointer
0536     // so this unsafe looking code is actually just fine.
0537     Containment *containment = static_cast<Plasma::Containment *>(obj);
0538     int index = containments.indexOf(containment);
0539 
0540     if (index > -1) {
0541         containments.removeAt(index);
0542         q->requestConfigSync();
0543     }
0544 }
0545 
0546 void CoronaPrivate::syncConfig()
0547 {
0548     q->config()->sync();
0549     Q_EMIT q->configSynced();
0550 }
0551 
0552 Containment *CoronaPrivate::addContainment(const QString &name, const QVariantList &args, uint id, int lastScreen, bool delayedInit)
0553 {
0554     QString pluginName = name;
0555     Containment *containment = nullptr;
0556     Applet *applet = nullptr;
0557 
0558     // qCDebug(LOG_PLASMA) << "Loading" << name << args << id;
0559 
0560     if (pluginName.isEmpty() || pluginName == QLatin1String("default")) {
0561         // default to the desktop containment
0562         pluginName = desktopDefaultsConfig.readEntry("Containment", "org.kde.desktopcontainment");
0563     }
0564 
0565     bool loadingNull = pluginName == QLatin1String("null");
0566     if (!loadingNull) {
0567         applet = PluginLoader::self()->loadApplet(pluginName, id, args);
0568         containment = dynamic_cast<Containment *>(applet);
0569         if (containment) {
0570             containment->setParent(q);
0571         }
0572     }
0573 
0574     if (!containment) {
0575         if (!loadingNull) {
0576 #ifndef NDEBUG
0577             // qCDebug(LOG_PLASMA) << "loading of containment" << name << "failed.";
0578 #endif
0579         }
0580 
0581         // in case we got a non-Containment from Applet::loadApplet or
0582         // a null containment was requested
0583         if (applet) {
0584             // the applet probably doesn't know what's hit it, so let's pretend it can be
0585             // initialized to make assumptions in the applet's dtor safer
0586             applet->init();
0587             delete applet;
0588         }
0589         applet = containment = new Containment(q, KPluginMetaData(), QVariantList{QVariant(), QVariant(), id});
0590         if (lastScreen >= 0) {
0591             containment->d->lastScreen = lastScreen;
0592         }
0593         // if it's a dummy containment, just say its ui is ready, not blocking the corona
0594         applet->updateConstraints(Plasma::Types::UiReadyConstraint);
0595 
0596         // we want to provide something and don't care about the failure to launch
0597         containment->setFormFactor(Plasma::Types::Planar);
0598     }
0599 
0600     // if this is a new containment, we need to ensure that there are no stale
0601     // configuration data around
0602     if (id == 0) {
0603         KConfigGroup conf(q->config(), "Containments");
0604         conf = KConfigGroup(&conf, QString::number(containment->id()));
0605         conf.deleteGroup();
0606     }
0607 
0608     // make sure the containments are sorted by id
0609     auto position = std::lower_bound(containments.begin(), containments.end(), containment, [](Plasma::Containment *c1, Plasma::Containment *c2) {
0610         return c1->id() < c2->id();
0611     });
0612     containments.insert(position, containment);
0613 
0614     QObject::connect(containment, SIGNAL(destroyed(QObject *)), q, SLOT(containmentDestroyed(QObject *)));
0615     QObject::connect(containment, &Applet::configNeedsSaving, q, &Corona::requestConfigSync);
0616     QObject::connect(containment, &Containment::screenChanged, q, &Corona::screenOwnerChanged);
0617 
0618     if (!delayedInit) {
0619         containment->init();
0620         KConfigGroup cg = containment->config();
0621         containment->restore(cg);
0622         containment->updateConstraints(Plasma::Types::StartupCompletedConstraint);
0623         containment->save(cg);
0624         q->requestConfigSync();
0625         containment->flushPendingConstraintsEvents();
0626         Q_EMIT q->containmentAdded(containment);
0627         // if id = 0 a new containment has been created, not restored
0628         if (id == 0) {
0629             Q_EMIT q->containmentCreated(containment);
0630         }
0631     }
0632 
0633     return containment;
0634 }
0635 
0636 QList<Plasma::Containment *> CoronaPrivate::importLayout(const KConfigGroup &conf, bool mergeConfig)
0637 {
0638     if (!conf.isValid()) {
0639         return QList<Containment *>();
0640     }
0641 
0642     QList<Plasma::Containment *> newContainments;
0643     QSet<uint> containmentsIds;
0644 
0645     for (Containment *containment : std::as_const(containments)) {
0646         containmentsIds.insert(containment->id());
0647     }
0648 
0649     KConfigGroup containmentsGroup(&conf, "Containments");
0650     QStringList groups = containmentsGroup.groupList();
0651     std::sort(groups.begin(), groups.end());
0652 
0653     for (const QString &group : std::as_const(groups)) {
0654         KConfigGroup containmentConfig(&containmentsGroup, group);
0655 
0656         if (containmentConfig.entryMap().isEmpty()) {
0657             continue;
0658         } else if (containmentConfig.readEntry(QStringLiteral("transient"), false)) {
0659             containmentConfig.deleteGroup();
0660             continue;
0661         }
0662 
0663         uint cid = group.toUInt();
0664         if (containmentsIds.contains(cid)) {
0665             cid = ++AppletPrivate::s_maxAppletId;
0666         } else if (cid > AppletPrivate::s_maxAppletId) {
0667             AppletPrivate::s_maxAppletId = cid;
0668         }
0669 
0670         if (mergeConfig) {
0671             KConfigGroup realConf(q->config(), "Containments");
0672             realConf = KConfigGroup(&realConf, QString::number(cid));
0673             // in case something was there before us
0674             realConf.deleteGroup();
0675             containmentConfig.copyTo(&realConf);
0676         }
0677 
0678         // qCDebug(LOG_PLASMA) << "got a containment in the config, trying to make a" << containmentConfig.readEntry("plugin", QString()) << "from" << group;
0679 #ifndef NDEBUG
0680         // qCDebug(LOG_PLASMA) << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Adding Containment" << containmentConfig.readEntry("plugin",
0681         // QString());
0682 #endif
0683         Containment *c = addContainment(containmentConfig.readEntry("plugin", QString()), QVariantList(), cid, -1);
0684         if (!c) {
0685             continue;
0686         }
0687 
0688         newContainments.append(c);
0689         containmentsIds.insert(c->id());
0690 
0691 #ifndef NDEBUG
0692 //         qCDebug(LOG_PLASMA) << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Restored Containment" << c->pluginName();
0693 #endif
0694     }
0695 
0696     if (!mergeConfig) {
0697         notifyContainmentsReady();
0698     }
0699 
0700     return newContainments;
0701 }
0702 
0703 void CoronaPrivate::notifyContainmentsReady()
0704 {
0705     containmentsStarting = 0;
0706     for (Containment *containment : std::as_const(containments)) {
0707         if (!containment->isUiReady() && containment->screen() >= 0) {
0708             ++containmentsStarting;
0709             QObject::connect(containment, &Plasma::Containment::uiReadyChanged, q, [this](bool ready) {
0710                 containmentReady(ready);
0711             });
0712         }
0713     }
0714 
0715     if (containmentsStarting <= 0) {
0716         Q_EMIT q->startupCompleted();
0717     }
0718 }
0719 
0720 void CoronaPrivate::containmentReady(bool ready)
0721 {
0722     if (!ready) {
0723         return;
0724     }
0725     --containmentsStarting;
0726     if (containmentsStarting <= 0) {
0727         Q_EMIT q->startupCompleted();
0728     }
0729 }
0730 
0731 } // namespace Plasma
0732 
0733 #include "moc_corona.cpp"