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"