File indexing completed on 2024-03-24 03:59:37
0001 /* 0002 SPDX-FileCopyrightText: 2006 Aaron Seigo <aseigo@kde.org> 0003 SPDX-FileCopyrightText: 2007, 2009 Ryan P. Bitanga <ryan.bitanga@gmail.com> 0004 SPDX-FileCopyrightText: 2008 Jordi Polo <mumismo@gmail.com> 0005 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnauŋmx.de> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "runnermanager.h" 0011 0012 #include <QCoreApplication> 0013 #include <QDir> 0014 #include <QElapsedTimer> 0015 #include <QMutableListIterator> 0016 #include <QPointer> 0017 #include <QRegularExpression> 0018 #include <QStandardPaths> 0019 #include <QThread> 0020 #include <QTimer> 0021 0022 #include <KConfigWatcher> 0023 #include <KFileUtils> 0024 #include <KPluginMetaData> 0025 #include <KSharedConfig> 0026 0027 #include "abstractrunner_p.h" 0028 #include "dbusrunner_p.h" 0029 #include "kpluginmetadata_utils_p.h" 0030 #include "krunner_debug.h" 0031 #include "querymatch.h" 0032 0033 namespace KRunner 0034 { 0035 class RunnerManagerPrivate 0036 { 0037 public: 0038 RunnerManagerPrivate(const KConfigGroup &configurationGroup, KConfigGroup stateConfigGroup, RunnerManager *parent) 0039 : q(parent) 0040 , context(parent) 0041 , pluginConf(configurationGroup) 0042 , stateData(stateConfigGroup) 0043 { 0044 initializeKNotifyPluginWatcher(); 0045 matchChangeTimer.setSingleShot(true); 0046 matchChangeTimer.setTimerType(Qt::TimerType::PreciseTimer); // Without this, autotest will fail due to imprecision of this timer 0047 0048 QObject::connect(&matchChangeTimer, &QTimer::timeout, q, [this]() { 0049 matchesChanged(); 0050 }); 0051 0052 // Set up tracking of the last time matchesChanged was signalled 0053 lastMatchChangeSignalled.start(); 0054 QObject::connect(q, &RunnerManager::matchesChanged, q, [&] { 0055 lastMatchChangeSignalled.restart(); 0056 }); 0057 loadConfiguration(); 0058 } 0059 0060 void scheduleMatchesChanged() 0061 { 0062 // We avoid over-refreshing the client. We only refresh every this much milliseconds 0063 constexpr int refreshPeriod = 250; 0064 // This will tell us if we are reseting the matches to start a new search. RunnerContext::reset() clears its query string for its emission 0065 if (context.query().isEmpty()) { 0066 matchChangeTimer.stop(); 0067 // This actually contains the query string for the new search that we're launching (if any): 0068 if (!this->untrimmedTerm.trimmed().isEmpty()) { 0069 // We are starting a new search, we shall stall for some time before deciding to show an empty matches list. 0070 // This stall should be enough for the engine to provide more meaningful result, so we avoid refreshing with 0071 // an empty results list if possible. 0072 matchChangeTimer.start(refreshPeriod); 0073 // We "pretend" that we have refreshed it so the next call will be forced to wait the timeout: 0074 lastMatchChangeSignalled.restart(); 0075 } else { 0076 // We have an empty input string, so it's not a real query. We don't expect any results to come, so no need to stall 0077 Q_EMIT q->matchesChanged(context.matches()); 0078 } 0079 } else if (lastMatchChangeSignalled.hasExpired(refreshPeriod)) { 0080 matchChangeTimer.stop(); 0081 Q_EMIT q->matchesChanged(context.matches()); 0082 } else { 0083 matchChangeTimer.start(refreshPeriod - lastMatchChangeSignalled.elapsed()); 0084 } 0085 } 0086 0087 void matchesChanged() 0088 { 0089 Q_EMIT q->matchesChanged(context.matches()); 0090 } 0091 0092 void loadConfiguration() 0093 { 0094 const KConfigGroup generalConfig = pluginConf.config()->group(QStringLiteral("General")); 0095 context.restore(stateData); 0096 } 0097 0098 void loadSingleRunner() 0099 { 0100 // In case we are not in the single runner mode of we do not have an id 0101 if (!singleMode || singleModeRunnerId.isEmpty()) { 0102 currentSingleRunner = nullptr; 0103 return; 0104 } 0105 0106 if (currentSingleRunner && currentSingleRunner->id() == singleModeRunnerId) { 0107 return; 0108 } 0109 currentSingleRunner = q->runner(singleModeRunnerId); 0110 // If there are no runners loaded or the single runner could no be loaded, 0111 // this is the case if it was disabled but gets queries using the singleRunnerMode, BUG: 435050 0112 if (runners.isEmpty() || !currentSingleRunner) { 0113 loadRunners(singleModeRunnerId); 0114 currentSingleRunner = q->runner(singleModeRunnerId); 0115 } 0116 } 0117 0118 void deleteRunners(const QList<AbstractRunner *> &runners) 0119 { 0120 for (const auto runner : runners) { 0121 if (qobject_cast<DBusRunner *>(runner)) { 0122 runner->deleteLater(); 0123 } else { 0124 Q_ASSERT(runner->thread() != q->thread()); 0125 runner->thread()->quit(); 0126 QObject::connect(runner->thread(), &QThread::finished, runner->thread(), &QObject::deleteLater); 0127 QObject::connect(runner->thread(), &QThread::finished, runner, &QObject::deleteLater); 0128 } 0129 } 0130 } 0131 0132 void loadRunners(const QString &singleRunnerId = QString()) 0133 { 0134 QList<KPluginMetaData> offers = RunnerManager::runnerMetaDataList(); 0135 0136 const bool loadAll = stateData.readEntry("loadAll", false); 0137 const bool noWhiteList = whiteList.isEmpty(); 0138 0139 QList<AbstractRunner *> deadRunners; 0140 QMutableListIterator<KPluginMetaData> it(offers); 0141 while (it.hasNext()) { 0142 const KPluginMetaData &description = it.next(); 0143 qCDebug(KRUNNER) << "Loading runner: " << description.pluginId(); 0144 0145 const QString runnerName = description.pluginId(); 0146 const bool isPluginEnabled = description.isEnabled(pluginConf); 0147 const bool loaded = runners.contains(runnerName); 0148 bool selected = loadAll || disabledRunnerIds.contains(runnerName) || (isPluginEnabled && (noWhiteList || whiteList.contains(runnerName))); 0149 if (!selected && runnerName == singleRunnerId) { 0150 selected = true; 0151 disabledRunnerIds << runnerName; 0152 } 0153 0154 if (selected) { 0155 if (!loaded) { 0156 if (auto runner = loadInstalledRunner(description)) { 0157 qCDebug(KRUNNER) << "Loaded:" << runnerName; 0158 runners.insert(runnerName, runner); 0159 } 0160 } 0161 } else if (loaded) { 0162 // Remove runner 0163 deadRunners.append(runners.take(runnerName)); 0164 qCDebug(KRUNNER) << "Plugin disabled. Removing runner: " << runnerName; 0165 } 0166 } 0167 0168 deleteRunners(deadRunners); 0169 // in case we deleted it up above, just to be sure we do not have a dangeling pointer 0170 currentSingleRunner = nullptr; 0171 qCDebug(KRUNNER) << "All runners loaded, total:" << runners.count(); 0172 } 0173 0174 AbstractRunner *loadInstalledRunner(const KPluginMetaData &pluginMetaData) 0175 { 0176 if (!pluginMetaData.isValid()) { 0177 return nullptr; 0178 } 0179 0180 AbstractRunner *runner = nullptr; 0181 0182 const QString api = pluginMetaData.value(QStringLiteral("X-Plasma-API")); 0183 const bool isCppPlugin = api.isEmpty(); 0184 0185 if (isCppPlugin) { 0186 if (auto res = KPluginFactory::instantiatePlugin<AbstractRunner>(pluginMetaData, q)) { 0187 runner = res.plugin; 0188 } else { 0189 qCWarning(KRUNNER).nospace() << "Could not load runner " << pluginMetaData.name() << ":" << res.errorString 0190 << " (library path was:" << pluginMetaData.fileName() << ")"; 0191 } 0192 } else if (api.startsWith(QLatin1String("DBus"))) { 0193 runner = new DBusRunner(q, pluginMetaData); 0194 } else { 0195 qCWarning(KRUNNER) << "Unknown X-Plasma-API requested for runner" << pluginMetaData.fileName(); 0196 return nullptr; 0197 } 0198 0199 if (runner) { 0200 QPointer<AbstractRunner> ptr(runner); 0201 q->connect(runner, &AbstractRunner::matchingResumed, q, [this, ptr]() { 0202 if (ptr) { 0203 runnerMatchingResumed(ptr.get()); 0204 } 0205 }); 0206 if (isCppPlugin) { 0207 auto thread = new QThread(); 0208 thread->setObjectName(pluginMetaData.pluginId()); 0209 thread->start(); 0210 runner->moveToThread(thread); 0211 } 0212 // The runner might outlive the manager due to us waiting for the thread to exit 0213 q->connect(runner, &AbstractRunner::matchInternalFinished, q, [this](const QString &jobId) { 0214 onRunnerJobFinished(jobId); 0215 }); 0216 0217 if (prepped) { 0218 Q_EMIT runner->prepare(); 0219 } 0220 } 0221 0222 return runner; 0223 } 0224 0225 void onRunnerJobFinished(const QString &jobId) 0226 { 0227 if (currentJobs.remove(jobId) && currentJobs.isEmpty()) { 0228 // If there are any new matches scheduled to be notified, we should anticipate it and just refresh right now 0229 if (matchChangeTimer.isActive()) { 0230 matchChangeTimer.stop(); 0231 matchesChanged(); 0232 } else if (context.matches().isEmpty()) { 0233 // we finished our run, and there are no valid matches, and so no 0234 // signal will have been sent out, so we need to emit the signal ourselves here 0235 matchesChanged(); 0236 } 0237 Q_EMIT q->queryFinished(); // NOLINT(readability-misleading-indentation) 0238 } 0239 if (!currentJobs.isEmpty()) { 0240 qCDebug(KRUNNER) << "Current jobs are" << currentJobs; 0241 } 0242 } 0243 0244 void teardown() 0245 { 0246 pendingJobsAfterSuspend.clear(); // Do not start old jobs when the match session is over 0247 if (allRunnersPrepped) { 0248 for (AbstractRunner *runner : std::as_const(runners)) { 0249 Q_EMIT runner->teardown(); 0250 } 0251 allRunnersPrepped = false; 0252 } 0253 0254 if (singleRunnerPrepped) { 0255 if (currentSingleRunner) { 0256 Q_EMIT currentSingleRunner->teardown(); 0257 } 0258 singleRunnerPrepped = false; 0259 } 0260 0261 prepped = false; 0262 } 0263 0264 void runnerMatchingResumed(AbstractRunner *runner) 0265 { 0266 Q_ASSERT(runner); 0267 const QString jobId = pendingJobsAfterSuspend.value(runner); 0268 if (jobId.isEmpty()) { 0269 qCDebug(KRUNNER) << runner << "was not scheduled for current query"; 0270 return; 0271 } 0272 // Ignore this runner 0273 if (singleMode && runner->id() != singleModeRunnerId) { 0274 qCDebug(KRUNNER) << runner << "did not match requested singlerunnermode ID"; 0275 return; 0276 } 0277 0278 const QString query = context.query(); 0279 bool matchesCount = singleMode || runner->minLetterCount() <= query.size(); 0280 bool matchesRegex = singleMode || !runner->hasMatchRegex() || runner->matchRegex().match(query).hasMatch(); 0281 0282 if (matchesCount && matchesRegex) { 0283 startJob(runner); 0284 } else { 0285 onRunnerJobFinished(jobId); 0286 } 0287 } 0288 0289 void startJob(AbstractRunner *runner) 0290 { 0291 QMetaObject::invokeMethod(runner, "matchInternal", Qt::QueuedConnection, Q_ARG(KRunner::RunnerContext, context)); 0292 } 0293 0294 // Must only be called once 0295 void initializeKNotifyPluginWatcher() 0296 { 0297 Q_ASSERT(!watcher); 0298 watcher = KConfigWatcher::create(KSharedConfig::openConfig(pluginConf.config()->name())); 0299 q->connect(watcher.data(), &KConfigWatcher::configChanged, q, [this](const KConfigGroup &group, const QByteArrayList &changedNames) { 0300 const QString groupName = group.name(); 0301 if (groupName == QLatin1String("Plugins")) { 0302 q->reloadConfiguration(); 0303 } else if (groupName == QLatin1String("Runners")) { 0304 for (auto *runner : std::as_const(runners)) { 0305 // Signals from the KCM contain the component name, which is the KRunner plugin's id 0306 if (changedNames.contains(runner->metadata().pluginId().toUtf8())) { 0307 QMetaObject::invokeMethod(runner, "reloadConfigurationInternal"); 0308 } 0309 } 0310 } else if (group.parent().isValid() && group.parent().name() == QLatin1String("Runners")) { 0311 for (auto *runner : std::as_const(runners)) { 0312 // If the same config group has been modified which gets created in AbstractRunner::config() 0313 if (groupName == runner->id()) { 0314 QMetaObject::invokeMethod(runner, "reloadConfigurationInternal"); 0315 } 0316 } 0317 } 0318 }); 0319 } 0320 0321 void addToHistory() 0322 { 0323 const QString term = context.query(); 0324 // We want to imitate the shall behavior 0325 if (!historyEnabled || term.isEmpty() || untrimmedTerm.startsWith(QLatin1Char(' '))) { 0326 return; 0327 } 0328 QStringList historyEntries = readHistoryForCurrentEnv(); 0329 // Avoid removing the same item from the front and prepending it again 0330 if (!historyEntries.isEmpty() && historyEntries.constFirst() == term) { 0331 return; 0332 } 0333 0334 historyEntries.removeOne(term); 0335 historyEntries.prepend(term); 0336 0337 while (historyEntries.count() > 50) { // we don't want to store more than 50 entries 0338 historyEntries.removeLast(); 0339 } 0340 writeHistory(historyEntries); 0341 } 0342 0343 void writeHistory(const QStringList &historyEntries) 0344 { 0345 stateData.group(QStringLiteral("History")).writeEntry(historyEnvironmentIdentifier, historyEntries, KConfig::Notify); 0346 stateData.sync(); 0347 } 0348 0349 inline QStringList readHistoryForCurrentEnv() 0350 { 0351 return stateData.group(QStringLiteral("History")).readEntry(historyEnvironmentIdentifier, QStringList()); 0352 } 0353 0354 QString historyEnvironmentIdentifier = QStringLiteral("default"); 0355 RunnerManager *const q; 0356 RunnerContext context; 0357 QTimer matchChangeTimer; 0358 QElapsedTimer lastMatchChangeSignalled; 0359 QHash<QString, AbstractRunner *> runners; 0360 QHash<AbstractRunner *, QString> pendingJobsAfterSuspend; 0361 AbstractRunner *currentSingleRunner = nullptr; 0362 QSet<QString> currentJobs; 0363 QString singleModeRunnerId; 0364 bool prepped = false; 0365 bool allRunnersPrepped = false; 0366 bool singleRunnerPrepped = false; 0367 bool singleMode = false; 0368 bool historyEnabled = true; 0369 QStringList whiteList; 0370 KConfigWatcher::Ptr watcher; 0371 QString untrimmedTerm; 0372 KConfigGroup pluginConf; 0373 KConfigGroup stateData; 0374 QSet<QString> disabledRunnerIds; // Runners that are disabled but were loaded as single runners 0375 }; 0376 0377 RunnerManager::RunnerManager(const KConfigGroup &pluginConfigGroup, KConfigGroup stateConfigGroup, QObject *parent) 0378 : QObject(parent) 0379 , d(new RunnerManagerPrivate(pluginConfigGroup, stateConfigGroup, this)) 0380 { 0381 Q_ASSERT(pluginConfigGroup.isValid()); 0382 Q_ASSERT(stateConfigGroup.isValid()); 0383 } 0384 0385 RunnerManager::RunnerManager(QObject *parent) 0386 : QObject(parent) 0387 { 0388 auto defaultStatePtr = KSharedConfig::openConfig(QStringLiteral("krunnerstaterc"), KConfig::NoGlobals, QStandardPaths::GenericDataLocation); 0389 auto configPtr = KSharedConfig::openConfig(QStringLiteral("krunnerrc"), KConfig::NoGlobals); 0390 d.reset(new RunnerManagerPrivate(configPtr->group(QStringLiteral("Plugins")), defaultStatePtr->group(QStringLiteral("PlasmaRunnerManager")), this)); 0391 } 0392 0393 RunnerManager::~RunnerManager() 0394 { 0395 d->context.reset(); 0396 d->deleteRunners(d->runners.values()); 0397 } 0398 0399 void RunnerManager::reloadConfiguration() 0400 { 0401 d->pluginConf.config()->reparseConfiguration(); 0402 d->stateData.config()->reparseConfiguration(); 0403 d->loadConfiguration(); 0404 d->loadRunners(); 0405 } 0406 0407 void RunnerManager::setAllowedRunners(const QStringList &runners) 0408 { 0409 d->whiteList = runners; 0410 if (!d->runners.isEmpty()) { 0411 // this has been called with runners already created. so let's do an instant reload 0412 d->loadRunners(); 0413 } 0414 } 0415 0416 AbstractRunner *RunnerManager::loadRunner(const KPluginMetaData &pluginMetaData) 0417 { 0418 const QString runnerId = pluginMetaData.pluginId(); 0419 if (auto loadedRunner = d->runners.value(runnerId)) { 0420 return loadedRunner; 0421 } 0422 if (!runnerId.isEmpty()) { 0423 if (AbstractRunner *runner = d->loadInstalledRunner(pluginMetaData)) { 0424 d->runners.insert(runnerId, runner); 0425 return runner; 0426 } 0427 } 0428 return nullptr; 0429 } 0430 0431 AbstractRunner *RunnerManager::runner(const QString &pluginId) const 0432 { 0433 if (d->runners.isEmpty()) { 0434 d->loadRunners(); 0435 } 0436 0437 return d->runners.value(pluginId, nullptr); 0438 } 0439 0440 QList<AbstractRunner *> RunnerManager::runners() const 0441 { 0442 if (d->runners.isEmpty()) { 0443 d->loadRunners(); 0444 } 0445 return d->runners.values(); 0446 } 0447 0448 RunnerContext *RunnerManager::searchContext() const 0449 { 0450 return &d->context; 0451 } 0452 0453 QList<QueryMatch> RunnerManager::matches() const 0454 { 0455 return d->context.matches(); 0456 } 0457 0458 bool RunnerManager::run(const QueryMatch &match, const KRunner::Action &selectedAction) 0459 { 0460 if (!match.isValid() || !match.isEnabled()) { // The model should prevent this 0461 return false; 0462 } 0463 0464 // Modify the match and run it 0465 QueryMatch m = match; 0466 m.setSelectedAction(selectedAction); 0467 m.runner()->run(d->context, m); 0468 // To allow the RunnerContext to increase the relevance of often launched apps 0469 d->context.increaseLaunchCount(m); 0470 0471 if (!d->context.shouldIgnoreCurrentMatchForHistory()) { 0472 d->addToHistory(); 0473 } 0474 if (d->context.requestedQueryString().isEmpty()) { 0475 return true; 0476 } else { 0477 Q_EMIT requestUpdateQueryString(d->context.requestedQueryString(), d->context.requestedCursorPosition()); 0478 return false; 0479 } 0480 } 0481 0482 QMimeData *RunnerManager::mimeDataForMatch(const QueryMatch &match) const 0483 { 0484 return match.isValid() ? match.runner()->mimeDataForMatch(match) : nullptr; 0485 } 0486 0487 QList<KPluginMetaData> RunnerManager::runnerMetaDataList() 0488 { 0489 QList<KPluginMetaData> pluginMetaDatas = KPluginMetaData::findPlugins(QStringLiteral("kf6/krunner")); 0490 QSet<QString> knownRunnerIds; 0491 knownRunnerIds.reserve(pluginMetaDatas.size()); 0492 for (const KPluginMetaData &pluginMetaData : std::as_const(pluginMetaDatas)) { 0493 knownRunnerIds.insert(pluginMetaData.pluginId()); 0494 } 0495 0496 const QStringList dBusPlugindirs = 0497 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("krunner/dbusplugins"), QStandardPaths::LocateDirectory); 0498 const QStringList dbusRunnerFiles = KFileUtils::findAllUniqueFiles(dBusPlugindirs, QStringList(QStringLiteral("*.desktop"))); 0499 for (const QString &dbusRunnerFile : dbusRunnerFiles) { 0500 KPluginMetaData pluginMetaData = parseMetaDataFromDesktopFile(dbusRunnerFile); 0501 if (pluginMetaData.isValid() && !knownRunnerIds.contains(pluginMetaData.pluginId())) { 0502 pluginMetaDatas.append(pluginMetaData); 0503 knownRunnerIds.insert(pluginMetaData.pluginId()); 0504 } 0505 } 0506 0507 return pluginMetaDatas; 0508 } 0509 0510 void RunnerManager::setupMatchSession() 0511 { 0512 if (d->prepped) { 0513 return; 0514 } 0515 0516 d->prepped = true; 0517 if (d->singleMode) { 0518 if (d->currentSingleRunner) { 0519 Q_EMIT d->currentSingleRunner->prepare(); 0520 d->singleRunnerPrepped = true; 0521 } 0522 } else { 0523 for (AbstractRunner *runner : std::as_const(d->runners)) { 0524 if (!d->disabledRunnerIds.contains(runner->name())) { 0525 Q_EMIT runner->prepare(); 0526 } 0527 } 0528 0529 d->allRunnersPrepped = true; 0530 } 0531 } 0532 0533 void RunnerManager::matchSessionComplete() 0534 { 0535 if (!d->prepped) { 0536 return; 0537 } 0538 0539 d->teardown(); 0540 // We save the context config after each session, just like the history entries 0541 // BUG: 424505 0542 d->context.save(d->stateData); 0543 } 0544 0545 void RunnerManager::launchQuery(const QString &untrimmedTerm, const QString &runnerName) 0546 { 0547 d->pendingJobsAfterSuspend.clear(); // Do not start old jobs when we got a new query 0548 QString term = untrimmedTerm.trimmed(); 0549 const QString prevSingleRunner = d->singleModeRunnerId; 0550 d->untrimmedTerm = untrimmedTerm; 0551 0552 // Set the required values and load the runner 0553 d->singleModeRunnerId = runnerName; 0554 d->singleMode = !runnerName.isEmpty(); 0555 d->loadSingleRunner(); 0556 // If we could not load the single runner we reset 0557 if (!runnerName.isEmpty() && !d->currentSingleRunner) { 0558 reset(); 0559 return; 0560 } 0561 if (term.isEmpty()) { 0562 QTimer::singleShot(0, this, &RunnerManager::queryFinished); 0563 reset(); 0564 return; 0565 } 0566 0567 if (d->context.query() == term && prevSingleRunner == runnerName) { 0568 // we already are searching for this! 0569 return; 0570 } 0571 0572 if (!d->singleMode && d->runners.isEmpty()) { 0573 d->loadRunners(); 0574 } 0575 0576 reset(); 0577 d->context.setQuery(term); 0578 0579 QHash<QString, AbstractRunner *> runnable; 0580 0581 // if the name is not empty we will launch only the specified runner 0582 if (d->singleMode) { 0583 runnable.insert(QString(), d->currentSingleRunner); 0584 d->context.setSingleRunnerQueryMode(true); 0585 } else { 0586 runnable = d->runners; 0587 } 0588 0589 qint64 startTs = QDateTime::currentMSecsSinceEpoch(); 0590 d->context.setJobStartTs(startTs); 0591 setupMatchSession(); 0592 for (KRunner::AbstractRunner *r : std::as_const(runnable)) { 0593 const QString &jobId = d->context.runnerJobId(r); 0594 if (r->isMatchingSuspended()) { 0595 d->pendingJobsAfterSuspend.insert(r, jobId); 0596 d->currentJobs.insert(jobId); 0597 continue; 0598 } 0599 // If this runner is loaded but disabled 0600 if (!d->singleMode && d->disabledRunnerIds.contains(r->id())) { 0601 continue; 0602 } 0603 // The runners can set the min letter count as a property, this way we don't 0604 // have to spawn threads just for the runner to reject the query, because it is too short 0605 if (!d->singleMode && term.length() < r->minLetterCount()) { 0606 continue; 0607 } 0608 // If the runner has one ore more trigger words it can set the matchRegex to prevent 0609 // thread spawning if the pattern does not match 0610 if (!d->singleMode && r->hasMatchRegex() && !r->matchRegex().match(term).hasMatch()) { 0611 continue; 0612 } 0613 0614 d->currentJobs.insert(jobId); 0615 d->startJob(r); 0616 } 0617 // In the unlikely case that no runner gets queried we have to emit the signals here 0618 if (d->currentJobs.isEmpty()) { 0619 QTimer::singleShot(0, this, [this]() { 0620 d->currentJobs.clear(); 0621 Q_EMIT matchesChanged({}); 0622 Q_EMIT queryFinished(); 0623 }); 0624 } 0625 } 0626 0627 QString RunnerManager::query() const 0628 { 0629 return d->context.query(); 0630 } 0631 0632 QStringList RunnerManager::history() const 0633 { 0634 return d->readHistoryForCurrentEnv(); 0635 } 0636 0637 void RunnerManager::removeFromHistory(int index) 0638 { 0639 QStringList changedHistory = history(); 0640 if (index < changedHistory.length()) { 0641 changedHistory.removeAt(index); 0642 d->writeHistory(changedHistory); 0643 } 0644 } 0645 0646 QString RunnerManager::getHistorySuggestion(const QString &typedQuery) const 0647 { 0648 const QStringList historyList = history(); 0649 for (const QString &entry : historyList) { 0650 if (entry.startsWith(typedQuery, Qt::CaseInsensitive)) { 0651 return entry; 0652 } 0653 } 0654 return QString(); 0655 } 0656 0657 void RunnerManager::reset() 0658 { 0659 if (!d->currentJobs.empty()) { 0660 Q_EMIT queryFinished(); 0661 d->currentJobs.clear(); 0662 } 0663 d->context.reset(); 0664 } 0665 0666 KPluginMetaData RunnerManager::convertDBusRunnerToJson(const QString &filename) const 0667 { 0668 return parseMetaDataFromDesktopFile(filename); 0669 } 0670 0671 bool RunnerManager::historyEnabled() 0672 { 0673 return d->historyEnabled; 0674 } 0675 0676 void RunnerManager::setHistoryEnabled(bool enabled) 0677 { 0678 d->historyEnabled = enabled; 0679 Q_EMIT historyEnabledChanged(); 0680 } 0681 0682 // Gets called by RunnerContext to inform that we got new matches 0683 void RunnerManager::onMatchesChanged() 0684 { 0685 d->scheduleMatchesChanged(); 0686 } 0687 void RunnerManager::setHistoryEnvironmentIdentifier(const QString &identifier) 0688 { 0689 Q_ASSERT(!identifier.isEmpty()); 0690 d->historyEnvironmentIdentifier = identifier; 0691 } 0692 0693 } // KRunner namespace 0694 0695 #include "moc_runnermanager.cpp"