File indexing completed on 2024-05-05 17:33:03

0001 /*
0002  *   SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *
0004  *   SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "DiscoverObject.h"
0008 #include "CachedNetworkAccessManager.h"
0009 #include "DiscoverBackendsFactory.h"
0010 #include "DiscoverDeclarativePlugin.h"
0011 #include "FeaturedModel.h"
0012 #include "OdrsAppsModel.h"
0013 #include "PaginateModel.h"
0014 #include "UnityLauncher.h"
0015 #include <Transaction/TransactionModel.h>
0016 
0017 // Qt includes
0018 #include "discover_debug.h"
0019 #include <QClipboard>
0020 #include <QDBusConnection>
0021 #include <QDBusMessage>
0022 #include <QDBusPendingCall>
0023 #include <QDesktopServices>
0024 #include <QFile>
0025 #include <QGuiApplication>
0026 #include <QPointer>
0027 #include <QQmlApplicationEngine>
0028 #include <QQmlContext>
0029 #include <QQmlEngine>
0030 #include <QSessionManager>
0031 #include <QSortFilterProxyModel>
0032 #include <QTimer>
0033 #include <QtQuick/QQuickItem>
0034 #include <qqml.h>
0035 
0036 // KDE includes
0037 #include <KAboutData>
0038 #include <KAuthorized>
0039 #include <KConfigGroup>
0040 #include <KCrash>
0041 #include <KLocalizedContext>
0042 #include <KLocalizedString>
0043 #include <KOSRelease>
0044 #include <KSharedConfig>
0045 #include <KStatusNotifierItem>
0046 #include <KUiServerV2JobTracker>
0047 #include <kcoreaddons_version.h>
0048 // #include <KSwitchLanguageDialog>
0049 
0050 // DiscoverCommon includes
0051 #include <Category/Category.h>
0052 #include <Category/CategoryModel.h>
0053 #include <resources/AbstractResource.h>
0054 #include <resources/ResourcesModel.h>
0055 #include <resources/ResourcesProxyModel.h>
0056 
0057 #include <QMimeDatabase>
0058 #include <cmath>
0059 #include <functional>
0060 #include <resources/StoredResultsStream.h>
0061 #include <unistd.h>
0062 #include <utils.h>
0063 
0064 #ifdef WITH_FEEDBACK
0065 #include "plasmauserfeedback.h"
0066 #endif
0067 #include "PowerManagementInterface.h"
0068 #include "discoversettings.h"
0069 
0070 class CachedNetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
0071 {
0072     virtual QNetworkAccessManager *create(QObject *parent) override
0073     {
0074         return new CachedNetworkAccessManager(QStringLiteral("images"), parent);
0075     }
0076 };
0077 
0078 class OurSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus
0079 {
0080     Q_OBJECT
0081     Q_INTERFACES(QQmlParserStatus)
0082 public:
0083     void classBegin() override
0084     {
0085     }
0086     void componentComplete() override
0087     {
0088         if (dynamicSortFilter())
0089             sort(0);
0090     }
0091 };
0092 
0093 DiscoverObject::DiscoverObject(CompactMode mode, const QVariantMap &initialProperties)
0094     : QObject()
0095     , m_engine(new QQmlApplicationEngine)
0096     , m_mode(mode)
0097     , m_networkAccessManagerFactory(new CachedNetworkAccessManagerFactory)
0098 {
0099     setObjectName(QStringLiteral("DiscoverMain"));
0100     m_engine->rootContext()->setContextObject(new KLocalizedContext(m_engine));
0101     auto factory = m_engine->networkAccessManagerFactory();
0102     m_engine->setNetworkAccessManagerFactory(nullptr);
0103     delete factory;
0104     m_engine->setNetworkAccessManagerFactory(m_networkAccessManagerFactory.data());
0105 
0106     qmlRegisterType<UnityLauncher>("org.kde.discover.app", 1, 0, "UnityLauncher");
0107     qmlRegisterType<PaginateModel>("org.kde.discover.app", 1, 0, "PaginateModel");
0108     qmlRegisterType<FeaturedModel>("org.kde.discover.app", 1, 0, "FeaturedModel");
0109     qmlRegisterType<OdrsAppsModel>("org.kde.discover.app", 1, 0, "OdrsAppsModel");
0110     qmlRegisterType<PowerManagementInterface>("org.kde.discover.app", 1, 0, "PowerManagementInterface");
0111     qmlRegisterType<OurSortFilterProxyModel>("org.kde.discover.app", 1, 0, "QSortFilterProxyModel");
0112 #ifdef WITH_FEEDBACK
0113     qmlRegisterSingletonType<PlasmaUserFeedback>("org.kde.discover.app", 1, 0, "UserFeedbackSettings", [](QQmlEngine *, QJSEngine *) -> QObject * {
0114         return new PlasmaUserFeedback(KSharedConfig::openConfig(QStringLiteral("PlasmaUserFeedback"), KConfig::NoGlobals));
0115     });
0116 #endif
0117     qmlRegisterSingletonType<DiscoverSettings>("org.kde.discover.app", 1, 0, "DiscoverSettings", [](QQmlEngine *engine, QJSEngine *) -> QObject * {
0118         auto r = new DiscoverSettings;
0119         r->setParent(engine);
0120         connect(r, &DiscoverSettings::installedPageSortingChanged, r, &DiscoverSettings::save);
0121         connect(r, &DiscoverSettings::appsListPageSortingChanged, r, &DiscoverSettings::save);
0122         return r;
0123     });
0124     qmlRegisterAnonymousType<QQuickView>("org.kde.discover.app", 1);
0125 
0126     qmlRegisterAnonymousType<KAboutData>("org.kde.discover.app", 1);
0127     qmlRegisterAnonymousType<KAboutLicense>("org.kde.discover.app", 1);
0128     qmlRegisterAnonymousType<KAboutPerson>("org.kde.discover.app", 1);
0129 
0130     qmlRegisterUncreatableType<DiscoverObject>("org.kde.discover.app", 1, 0, "DiscoverMainWindow", QStringLiteral("don't do that"));
0131 
0132     auto uri = "org.kde.discover";
0133     DiscoverDeclarativePlugin *plugin = new DiscoverDeclarativePlugin;
0134     plugin->setParent(this);
0135     plugin->initializeEngine(m_engine, uri);
0136     plugin->registerTypes(uri);
0137 
0138     m_engine->setInitialProperties(initialProperties);
0139     m_engine->rootContext()->setContextProperty(QStringLiteral("app"), this);
0140     m_engine->rootContext()->setContextProperty(QStringLiteral("discoverAboutData"), QVariant::fromValue(KAboutData::applicationData()));
0141 
0142     connect(m_engine, &QQmlApplicationEngine::objectCreated, this, &DiscoverObject::integrateObject);
0143     m_engine->load(QUrl(QStringLiteral("qrc:/qml/DiscoverWindow.qml")));
0144 
0145     connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
0146         const auto objs = m_engine->rootObjects();
0147         for (auto o : objs)
0148             delete o;
0149     });
0150     auto action = new OneTimeAction(
0151         [this]() {
0152             bool found = DiscoverBackendsFactory::hasRequestedBackends();
0153             const auto backends = ResourcesModel::global()->backends();
0154             for (auto b : backends)
0155                 found |= b->hasApplications();
0156 
0157             if (!found) {
0158                 QString errorText = i18n(
0159                     "Discover currently cannot be used to install any apps or "
0160                     "perform system updates because none of its app backends are "
0161                     "available.");
0162                 QString errorExplanation = xi18nc("@info",
0163                     "You can install some on the Settings page, under the "
0164                     "<interface>Missing Backends</interface> section.<nl/><nl/>"
0165                     "Also please consider reporting this as a packaging issue to "
0166                     "your distribution.");
0167                 QString buttonIcon = QStringLiteral("tools-report-bug");
0168                 QString buttonText = i18n("Report This Issue");
0169                 QString buttonUrl = KOSRelease().bugReportUrl();
0170 
0171                 if (KOSRelease().name().contains(QStringLiteral("Arch Linux"))) {
0172                     errorExplanation = xi18nc("@info",
0173                         "You can use <command>pacman</command> to "
0174                         "install the optional dependencies that are needed to "
0175                         "enable the application backends.<nl/><nl/>Please note "
0176                         "that Arch Linux developers recommend using "
0177                         "<command>pacman</command> for managing software because "
0178                         "the PackageKit backend is not well-integrated on Arch "
0179                         "Linux.");
0180                     buttonIcon = QStringLiteral("help-about");
0181                     buttonText = i18n("Learn More");
0182                     buttonUrl = KOSRelease().supportUrl();
0183                 }
0184 
0185                 Q_EMIT openErrorPage(errorText, errorExplanation, buttonText, buttonIcon, buttonUrl);
0186             }
0187         },
0188         this);
0189 
0190     if (ResourcesModel::global()->backends().isEmpty()) {
0191         connect(ResourcesModel::global(), &ResourcesModel::allInitialized, action, &OneTimeAction::trigger);
0192     } else {
0193         action->trigger();
0194     }
0195 }
0196 
0197 DiscoverObject::~DiscoverObject()
0198 {
0199     m_engine->deleteLater();
0200 }
0201 
0202 bool DiscoverObject::isRoot()
0203 {
0204     return ::getuid() == 0;
0205 }
0206 
0207 QStringList DiscoverObject::modes() const
0208 {
0209     QStringList ret;
0210     QObject *obj = rootObject();
0211     for (int i = obj->metaObject()->propertyOffset(); i < obj->metaObject()->propertyCount(); i++) {
0212         QMetaProperty p = obj->metaObject()->property(i);
0213         QByteArray compName = p.name();
0214         if (compName.startsWith("top") && compName.endsWith("Comp")) {
0215             ret += QString::fromLatin1(compName.mid(3, compName.length() - 7));
0216         }
0217     }
0218     return ret;
0219 }
0220 
0221 void DiscoverObject::openMode(const QString &_mode)
0222 {
0223     QObject *obj = rootObject();
0224     if (!obj) {
0225         qCWarning(DISCOVER_LOG) << "could not get the main object";
0226         return;
0227     }
0228 
0229     if (!modes().contains(_mode, Qt::CaseInsensitive))
0230         qCWarning(DISCOVER_LOG) << "unknown mode" << _mode << modes();
0231 
0232     QString mode = _mode;
0233     mode[0] = mode[0].toUpper();
0234 
0235     const QByteArray propertyName = "top" + mode.toLatin1() + "Comp";
0236     const QVariant modeComp = obj->property(propertyName.constData());
0237     if (!modeComp.isValid())
0238         qCWarning(DISCOVER_LOG) << "couldn't open mode" << _mode;
0239     else
0240         obj->setProperty("currentTopLevel", modeComp);
0241 }
0242 
0243 void DiscoverObject::openMimeType(const QString &mime)
0244 {
0245     Q_EMIT listMimeInternal(mime);
0246 }
0247 
0248 void DiscoverObject::showLoadingPage()
0249 {
0250     QObject *obj = rootObject();
0251     if (!obj) {
0252         qCWarning(DISCOVER_LOG) << "could not get the main object";
0253         return;
0254     }
0255 
0256     obj->setProperty("currentTopLevel", QStringLiteral("qrc:/qml/LoadingPage.qml"));
0257 }
0258 
0259 void DiscoverObject::openCategory(const QString &category)
0260 {
0261     showLoadingPage();
0262     auto action = new OneTimeAction(
0263         [this, category]() {
0264             Category *cat = CategoryModel::global()->findCategoryByName(category);
0265             if (cat) {
0266                 Q_EMIT listCategoryInternal(cat);
0267             } else {
0268                 openMode(QStringLiteral("Browsing"));
0269                 showError(i18n("Could not find category '%1'", category));
0270             }
0271         },
0272         this);
0273 
0274     if (CategoryModel::global()->rootCategories().isEmpty()) {
0275         connect(CategoryModel::global(), &CategoryModel::rootCategoriesChanged, action, &OneTimeAction::trigger);
0276     } else {
0277         action->trigger();
0278     }
0279 }
0280 
0281 void DiscoverObject::openLocalPackage(const QUrl &localfile)
0282 {
0283     if (!QFile::exists(localfile.toLocalFile())) {
0284         showError(i18n("Trying to open inexisting file '%1'", localfile.toString()));
0285         openMode(QStringLiteral("Browsing"));
0286         return;
0287     }
0288     showLoadingPage();
0289     auto action = new OneTimeAction(
0290         [this, localfile]() {
0291             AbstractResourcesBackend::Filters f;
0292             f.resourceUrl = localfile;
0293             auto stream = new StoredResultsStream({ResourcesModel::global()->search(f)});
0294             connect(stream, &StoredResultsStream::finishedResources, this, [this, localfile](const QVector<AbstractResource *> &res) {
0295                 if (res.count() == 1) {
0296                     Q_EMIT openApplicationInternal(res.first());
0297                 } else {
0298                     QMimeDatabase db;
0299                     auto mime = db.mimeTypeForUrl(localfile);
0300                     auto fIsFlatpakBackend = [](AbstractResourcesBackend *backend) {
0301                         return backend->metaObject()->className() == QByteArray("FlatpakBackend");
0302                     };
0303                     if (mime.name().startsWith(QLatin1String("application/vnd.flatpak"))
0304                         && !kContains(ResourcesModel::global()->backends(), fIsFlatpakBackend)) {
0305                         openApplication(QUrl(QStringLiteral("appstream://org.kde.discover.flatpak")));
0306                         showError(i18n("Cannot interact with flatpak resources without the flatpak backend %1. Please install it first.",
0307                                        localfile.toDisplayString()));
0308                     } else {
0309                         openMode(QStringLiteral("Browsing"));
0310                         showError(i18n("Could not open %1", localfile.toDisplayString()));
0311                     }
0312                 }
0313             });
0314         },
0315         this);
0316 
0317     if (ResourcesModel::global()->backends().isEmpty()) {
0318         connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger);
0319     } else {
0320         action->trigger();
0321     }
0322 }
0323 
0324 void DiscoverObject::openApplication(const QUrl &url)
0325 {
0326     Q_ASSERT(!url.isEmpty());
0327     showLoadingPage();
0328     auto action = new OneTimeAction(
0329         [this, url]() {
0330             AbstractResourcesBackend::Filters f;
0331             f.resourceUrl = url;
0332             auto stream = new StoredResultsStream({ResourcesModel::global()->search(f)});
0333             connect(stream, &StoredResultsStream::finishedResources, this, [this, url](const QVector<AbstractResource *> &res) {
0334                 if (res.count() >= 1) {
0335                     QPointer<QTimer> timeout = new QTimer(this);
0336                     timeout->setSingleShot(true);
0337                     timeout->setInterval(20000);
0338                     connect(timeout, &QTimer::timeout, timeout, &QTimer::deleteLater);
0339 
0340                     auto openResourceOrWait = [this, res, timeout] {
0341                         auto idx = kIndexOf(res, [](auto res) {
0342                             return res->isInstalled();
0343                         });
0344                         if (idx < 0) {
0345                             bool oneBroken = kContains(res, [](auto res) {
0346                                 return res->state() == AbstractResource::Broken;
0347                             });
0348                             if (oneBroken && timeout) {
0349                                 return false;
0350                             }
0351 
0352                             idx = 0;
0353                         }
0354                         Q_EMIT openApplicationInternal(res[idx]);
0355                         return true;
0356                     };
0357 
0358                     if (!openResourceOrWait()) {
0359                         auto f = new OneTimeAction(0, openResourceOrWait, this);
0360                         for (auto r : res) {
0361                             if (r->state() == AbstractResource::Broken) {
0362                                 connect(r, &AbstractResource::stateChanged, f, &OneTimeAction::trigger);
0363                             }
0364                         }
0365                         timeout->start();
0366                         connect(timeout, &QTimer::destroyed, f, &OneTimeAction::trigger);
0367                     } else {
0368                         delete timeout;
0369                     }
0370                 } else if (url.scheme() == QLatin1String("snap")) {
0371                     openApplication(QUrl(QStringLiteral("appstream://org.kde.discover.snap")));
0372                     showError(i18n("Please make sure Snap support is installed"));
0373                 } else {
0374                     const QString errorText = i18n("Could not open %1 because it "
0375                     "was not found in any available software repositories.",
0376                     url.toDisplayString());
0377                     const QString errorExplanation = i18n("Please report this "
0378                     "issue to the packagers of your distribution.");
0379                     QString buttonIcon = QStringLiteral("tools-report-bug");
0380                     QString buttonText = i18n("Report This Issue");
0381                     QString buttonUrl = KOSRelease().bugReportUrl();
0382                     Q_EMIT openErrorPage(errorText, errorExplanation, buttonText, buttonIcon, buttonUrl);
0383                 }
0384             });
0385         },
0386         this);
0387 
0388     if (ResourcesModel::global()->backends().isEmpty()) {
0389         connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger);
0390     } else {
0391         action->trigger();
0392     }
0393 }
0394 
0395 class TransactionsJob : public KJob
0396 {
0397 public:
0398     void start() override
0399     {
0400         // no-op, this is just observing
0401 
0402         setTotalAmount(Items, TransactionModel::global()->rowCount());
0403         setPercent(TransactionModel::global()->progress());
0404         connect(TransactionModel::global(), &TransactionModel::lastTransactionFinished, this, &TransactionsJob::emitResult);
0405         connect(TransactionModel::global(), &TransactionModel::transactionRemoved, this, &TransactionsJob::refreshInfo);
0406         connect(TransactionModel::global(), &TransactionModel::progressChanged, this, [this] {
0407             setPercent(TransactionModel::global()->progress());
0408         });
0409         refreshInfo();
0410     }
0411 
0412     void refreshInfo()
0413     {
0414         if (TransactionModel::global()->rowCount() == 0) {
0415             return;
0416         }
0417 
0418         setProcessedAmount(Items, totalAmount(Items) - TransactionModel::global()->rowCount() + 1);
0419         auto firstTransaction = TransactionModel::global()->transactions().constFirst();
0420         Q_EMIT description(this, firstTransaction->name());
0421     }
0422 
0423     void cancel()
0424     {
0425         setError(KJob::KilledJobError /*KIO::ERR_USER_CANCELED*/);
0426         deleteLater();
0427     }
0428 };
0429 
0430 bool DiscoverObject::quitWhenIdle()
0431 {
0432     if (!ResourcesModel::global()->isBusy()) {
0433         return true;
0434     }
0435 
0436     if (!m_sni) {
0437         auto tracker = new KUiServerV2JobTracker(m_sni);
0438 
0439         m_sni = new KStatusNotifierItem(this);
0440         m_sni->setStatus(KStatusNotifierItem::Active);
0441         m_sni->setIconByName("plasmadiscover");
0442         m_sni->setTitle(i18n("Discover"));
0443         m_sni->setToolTip("process-working-symbolic", i18n("Discover"), i18n("Discover was closed before certain tasks were done, waiting for it to finish."));
0444         m_sni->setStandardActionsEnabled(false);
0445 
0446         connect(TransactionModel::global(), &TransactionModel::countChanged, this, &DiscoverObject::reconsiderQuit);
0447         connect(m_sni, &KStatusNotifierItem::activateRequested, this, &DiscoverObject::restore);
0448 
0449         auto job = new TransactionsJob;
0450         job->setParent(this);
0451         tracker->registerJob(job);
0452         job->start();
0453         connect(m_sni, &KStatusNotifierItem::activateRequested, job, &TransactionsJob::cancel);
0454 
0455         rootObject()->hide();
0456     }
0457     return false;
0458 }
0459 
0460 void DiscoverObject::restore()
0461 {
0462     if (!m_sni) {
0463         return;
0464     }
0465 
0466     disconnect(TransactionModel::global(), &TransactionModel::countChanged, this, &DiscoverObject::reconsiderQuit);
0467     disconnect(m_sni, &KStatusNotifierItem::activateRequested, this, &DiscoverObject::restore);
0468 
0469     rootObject()->show();
0470     m_sni->deleteLater();
0471     m_sni = nullptr;
0472 }
0473 
0474 void DiscoverObject::reconsiderQuit()
0475 {
0476     if (ResourcesModel::global()->isBusy()) {
0477         return;
0478     }
0479 
0480     m_sni->deleteLater();
0481     // Let the job UI to finalise properly
0482     QTimer::singleShot(20, qGuiApp, &QCoreApplication::quit);
0483 }
0484 
0485 void DiscoverObject::integrateObject(QObject *object)
0486 {
0487     if (!object) {
0488         qCWarning(DISCOVER_LOG) << "Errors when loading the GUI";
0489         QTimer::singleShot(0, QCoreApplication::instance(), []() {
0490             QCoreApplication::instance()->exit(1);
0491         });
0492         return;
0493     }
0494 
0495     Q_ASSERT(object == rootObject());
0496 
0497     KConfigGroup window(KSharedConfig::openConfig(), "Window");
0498     if (window.hasKey("geometry"))
0499         rootObject()->setGeometry(window.readEntry("geometry", QRect()));
0500     if (window.hasKey("visibility")) {
0501         QWindow::Visibility visibility(QWindow::Visibility(window.readEntry<int>("visibility", QWindow::Windowed)));
0502         rootObject()->setVisibility(qMax(visibility, QQuickView::AutomaticVisibility));
0503     }
0504     connect(rootObject(), &QQuickView::sceneGraphError, this, [](QQuickWindow::SceneGraphError /*error*/, const QString &message) {
0505         KCrash::setErrorMessage(message);
0506         qFatal("%s", qPrintable(message));
0507     });
0508 
0509     object->installEventFilter(this);
0510     connect(object, &QObject::destroyed, qGuiApp, &QCoreApplication::quit);
0511 
0512     object->setParent(m_engine);
0513     connect(qGuiApp, &QGuiApplication::commitDataRequest, this, [this](QSessionManager &sessionManager) {
0514         if (!quitWhenIdle()) {
0515             sessionManager.cancel();
0516         }
0517     });
0518 }
0519 
0520 bool DiscoverObject::eventFilter(QObject *object, QEvent *event)
0521 {
0522     if (object != rootObject())
0523         return false;
0524 
0525     if (event->type() == QEvent::Close) {
0526         if (!quitWhenIdle()) {
0527             return true;
0528         }
0529 
0530         KConfigGroup window(KSharedConfig::openConfig(), "Window");
0531         window.writeEntry("geometry", rootObject()->geometry());
0532         window.writeEntry<int>("visibility", rootObject()->visibility());
0533         // } else if (event->type() == QEvent::ShortcutOverride) {
0534         //     qCWarning(DISCOVER_LOG) << "Action conflict" << event;
0535     }
0536     return false;
0537 }
0538 
0539 QString DiscoverObject::iconName(const QIcon &icon)
0540 {
0541     return icon.name();
0542 }
0543 
0544 void DiscoverObject::switchApplicationLanguage()
0545 {
0546     // auto langDialog = new KSwitchLanguageDialog(nullptr);
0547     // connect(langDialog, SIGNAL(finished(int)), this, SLOT(dialogFinished()));
0548     // langDialog->show();
0549 }
0550 
0551 void DiscoverObject::setCompactMode(DiscoverObject::CompactMode mode)
0552 {
0553     if (m_mode != mode) {
0554         m_mode = mode;
0555         Q_EMIT compactModeChanged(m_mode);
0556     }
0557 }
0558 
0559 class DiscoverTestExecutor : public QObject
0560 {
0561 public:
0562     DiscoverTestExecutor(QObject *rootObject, QQmlEngine *engine, const QUrl &url)
0563         : QObject(engine)
0564     {
0565         connect(engine, &QQmlEngine::quit, this, &DiscoverTestExecutor::finish, Qt::QueuedConnection);
0566 
0567         QQmlComponent *component = new QQmlComponent(engine, url, engine);
0568         m_testObject = component->create(engine->rootContext());
0569 
0570         if (!m_testObject) {
0571             qCWarning(DISCOVER_LOG) << "error loading test" << url << m_testObject << component->errors();
0572             Q_ASSERT(false);
0573         }
0574 
0575         m_testObject->setProperty("appRoot", QVariant::fromValue<QObject *>(rootObject));
0576         connect(engine, &QQmlEngine::warnings, this, &DiscoverTestExecutor::processWarnings);
0577     }
0578 
0579     void processWarnings(const QList<QQmlError> &warnings)
0580     {
0581         for (const QQmlError &warning : warnings) {
0582             if (warning.url().path().endsWith(QLatin1String("DiscoverTest.qml"))) {
0583                 qCWarning(DISCOVER_LOG) << "Test failed!" << warnings;
0584                 qGuiApp->exit(1);
0585             }
0586         }
0587         m_warnings << warnings;
0588     }
0589 
0590     void finish()
0591     {
0592         if (m_warnings.isEmpty())
0593             qCDebug(DISCOVER_LOG) << "cool no warnings!";
0594         else
0595             qCDebug(DISCOVER_LOG) << "test finished successfully despite" << m_warnings;
0596         qGuiApp->exit(m_warnings.count());
0597     }
0598 
0599 private:
0600     QObject *m_testObject;
0601     QList<QQmlError> m_warnings;
0602 };
0603 
0604 void DiscoverObject::loadTest(const QUrl &url)
0605 {
0606     new DiscoverTestExecutor(rootObject(), engine(), url);
0607 }
0608 
0609 QQuickWindow *DiscoverObject::rootObject() const
0610 {
0611     return qobject_cast<QQuickWindow *>(m_engine->rootObjects().at(0));
0612 }
0613 
0614 void DiscoverObject::showError(const QString &msg)
0615 {
0616     QTimer::singleShot(100, this, [msg]() {
0617         Q_EMIT ResourcesModel::global()->passiveMessage(msg);
0618     });
0619 }
0620 
0621 void DiscoverObject::copyTextToClipboard(const QString text)
0622 {
0623     QClipboard *clipboard = QGuiApplication::clipboard();
0624     clipboard->setText(text);
0625 }
0626 
0627 void DiscoverObject::reboot()
0628 {
0629     auto method = QDBusMessage::createMethodCall(QStringLiteral("org.kde.LogoutPrompt"),
0630                                                  QStringLiteral("/LogoutPrompt"),
0631                                                  QStringLiteral("org.kde.LogoutPrompt"),
0632                                                  QStringLiteral("promptReboot"));
0633     QDBusConnection::sessionBus().asyncCall(method);
0634 }
0635 
0636 void DiscoverObject::rebootNow()
0637 {
0638     auto method = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.login1"),
0639                                                  QStringLiteral("/org/freedesktop/login1"),
0640                                                  QStringLiteral("org.freedesktop.login1.Manager"),
0641                                                  QStringLiteral("Reboot"));
0642     method.setArguments({true /*interactive*/});
0643     QDBusConnection::systemBus().asyncCall(method);
0644 }
0645 
0646 QRect DiscoverObject::initialGeometry() const
0647 {
0648     KConfigGroup window(KSharedConfig::openConfig(), "Window");
0649     return window.readEntry("geometry", QRect());
0650 }
0651 
0652 QString DiscoverObject::describeSources() const
0653 {
0654     return rootObject()->property("describeSources").toString();
0655 }
0656 
0657 #include "DiscoverObject.moc"