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