KRunner

runnermanager.cpp
1/*
2 SPDX-FileCopyrightText: 2006 Aaron Seigo <aseigo@kde.org>
3 SPDX-FileCopyrightText: 2007, 2009 Ryan P. Bitanga <ryan.bitanga@gmail.com>
4 SPDX-FileCopyrightText: 2008 Jordi Polo <mumismo@gmail.com>
5 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnauŋmx.de>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "runnermanager.h"
11
12#include <QCoreApplication>
13#include <QDir>
14#include <QElapsedTimer>
15#include <QMutableListIterator>
16#include <QPointer>
17#include <QRegularExpression>
18#include <QStandardPaths>
19#include <QThread>
20#include <QTimer>
21
22#include <KConfigWatcher>
23#include <KFileUtils>
24#include <KPluginMetaData>
25#include <KSharedConfig>
26#include <memory>
27
28#include "abstractrunner_p.h"
29#include "dbusrunner_p.h"
30#include "kpluginmetadata_utils_p.h"
31#include "krunner_debug.h"
32#include "querymatch.h"
33
34namespace KRunner
35{
36class RunnerManagerPrivate
37{
38public:
39 RunnerManagerPrivate(const KConfigGroup &configurationGroup, const KConfigGroup &stateConfigGroup, RunnerManager *parent)
40 : q(parent)
41 , context(parent)
42 , pluginConf(configurationGroup)
43 , stateData(stateConfigGroup)
44 {
45 initializeKNotifyPluginWatcher();
46 matchChangeTimer.setSingleShot(true);
47 matchChangeTimer.setTimerType(Qt::TimerType::PreciseTimer); // Without this, autotest will fail due to imprecision of this timer
48
49 QObject::connect(&matchChangeTimer, &QTimer::timeout, q, [this]() {
50 matchesChanged();
51 });
52
53 // Set up tracking of the last time matchesChanged was signalled
54 lastMatchChangeSignalled.start();
56 lastMatchChangeSignalled.restart();
57 });
58 loadConfiguration();
59 }
60
61 void scheduleMatchesChanged()
62 {
63 // We avoid over-refreshing the client. We only refresh every this much milliseconds
64 constexpr int refreshPeriod = 250;
65 // This will tell us if we are reseting the matches to start a new search. RunnerContext::reset() clears its query string for its emission
66 if (context.query().isEmpty()) {
67 matchChangeTimer.stop();
68 // This actually contains the query string for the new search that we're launching (if any):
69 if (!this->untrimmedTerm.trimmed().isEmpty()) {
70 // We are starting a new search, we shall stall for some time before deciding to show an empty matches list.
71 // This stall should be enough for the engine to provide more meaningful result, so we avoid refreshing with
72 // an empty results list if possible.
73 matchChangeTimer.start(refreshPeriod);
74 // We "pretend" that we have refreshed it so the next call will be forced to wait the timeout:
75 lastMatchChangeSignalled.restart();
76 } else {
77 // 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
78 Q_EMIT q->matchesChanged(context.matches());
79 }
80 } else if (lastMatchChangeSignalled.hasExpired(refreshPeriod)) {
81 matchChangeTimer.stop();
82 Q_EMIT q->matchesChanged(context.matches());
83 } else {
84 matchChangeTimer.start(refreshPeriod - lastMatchChangeSignalled.elapsed());
85 }
86 }
87
88 void matchesChanged()
89 {
90 Q_EMIT q->matchesChanged(context.matches());
91 }
92
93 void loadConfiguration()
94 {
95 const KConfigGroup generalConfig = pluginConf.config()->group(QStringLiteral("General"));
96 context.restore(stateData);
97 }
98
99 void loadSingleRunner()
100 {
101 // In case we are not in the single runner mode of we do not have an id
102 if (!singleMode || singleModeRunnerId.isEmpty()) {
103 currentSingleRunner = nullptr;
104 return;
105 }
106
107 if (currentSingleRunner && currentSingleRunner->id() == singleModeRunnerId) {
108 return;
109 }
110 currentSingleRunner = q->runner(singleModeRunnerId);
111 // If there are no runners loaded or the single runner could no be loaded,
112 // this is the case if it was disabled but gets queries using the singleRunnerMode, BUG: 435050
113 if (runners.isEmpty() || !currentSingleRunner) {
114 loadRunners(singleModeRunnerId);
115 currentSingleRunner = q->runner(singleModeRunnerId);
116 }
117 }
118
119 void deleteRunners(const QList<AbstractRunner *> &runners)
120 {
121 for (const auto runner : runners) {
122 if (qobject_cast<DBusRunner *>(runner)) {
123 runner->deleteLater();
124 } else {
125 Q_ASSERT(runner->thread() != q->thread());
126 runner->thread()->quit();
127 QObject::connect(runner->thread(), &QThread::finished, runner->thread(), &QObject::deleteLater);
128 QObject::connect(runner->thread(), &QThread::finished, runner, &QObject::deleteLater);
129 }
130 }
131 }
132
133 void loadRunners(const QString &singleRunnerId = QString())
134 {
135 QList<KPluginMetaData> offers = RunnerManager::runnerMetaDataList();
136
137 const bool loadAll = stateData.readEntry("loadAll", false);
138 const bool noWhiteList = whiteList.isEmpty();
139
140 QList<AbstractRunner *> deadRunners;
141 QMutableListIterator<KPluginMetaData> it(offers);
142 while (it.hasNext()) {
143 const KPluginMetaData &description = it.next();
144 qCDebug(KRUNNER) << "Loading runner: " << description.pluginId();
145
146 const QString runnerName = description.pluginId();
147 const bool isPluginEnabled = description.isEnabled(pluginConf);
148 const bool loaded = runners.contains(runnerName);
149 bool selected = loadAll || disabledRunnerIds.contains(runnerName) || (isPluginEnabled && (noWhiteList || whiteList.contains(runnerName)));
150 if (!selected && runnerName == singleRunnerId) {
151 selected = true;
152 disabledRunnerIds << runnerName;
153 }
154
155 if (selected) {
156 if (!loaded) {
157 if (auto runner = loadInstalledRunner(description)) {
158 qCDebug(KRUNNER) << "Loaded:" << runnerName;
159 runners.insert(runnerName, runner);
160 }
161 }
162 } else if (loaded) {
163 // Remove runner
164 deadRunners.append(runners.take(runnerName));
165 qCDebug(KRUNNER) << "Plugin disabled. Removing runner: " << runnerName;
166 }
167 }
168
169 deleteRunners(deadRunners);
170 // in case we deleted it up above, just to be sure we do not have a dangeling pointer
171 currentSingleRunner = nullptr;
172 qCDebug(KRUNNER) << "All runners loaded, total:" << runners.count();
173 }
174
175 AbstractRunner *loadInstalledRunner(const KPluginMetaData &pluginMetaData)
176 {
177 if (!pluginMetaData.isValid()) {
178 return nullptr;
179 }
180
181 AbstractRunner *runner = nullptr;
182
183 const QString api = pluginMetaData.value(QStringLiteral("X-Plasma-API"));
184 const bool isCppPlugin = api.isEmpty();
185
186 if (isCppPlugin) {
187 if (auto res = KPluginFactory::instantiatePlugin<AbstractRunner>(pluginMetaData, q)) {
188 runner = res.plugin;
189 } else {
190 qCWarning(KRUNNER).nospace() << "Could not load runner " << pluginMetaData.name() << ":" << res.errorString
191 << " (library path was:" << pluginMetaData.fileName() << ")";
192 }
193 } else if (api.startsWith(QLatin1String("DBus"))) {
194 runner = new DBusRunner(q, pluginMetaData);
195 } else {
196 qCWarning(KRUNNER) << "Unknown X-Plasma-API requested for runner" << pluginMetaData.fileName();
197 return nullptr;
198 }
199
200 if (runner) {
201 QPointer<AbstractRunner> ptr(runner);
202 q->connect(runner, &AbstractRunner::matchingResumed, q, [this, ptr]() {
203 if (ptr) {
204 runnerMatchingResumed(ptr.get());
205 }
206 });
207 if (isCppPlugin) {
208 auto thread = new QThread();
209 thread->setObjectName(pluginMetaData.pluginId());
210 thread->start();
211 runner->moveToThread(thread);
212 }
213 // The runner might outlive the manager due to us waiting for the thread to exit
214 q->connect(runner, &AbstractRunner::matchInternalFinished, q, [this](const QString &jobId) {
215 onRunnerJobFinished(jobId);
216 });
217
218 if (prepped) {
219 Q_EMIT runner->prepare();
220 }
221 }
222
223 return runner;
224 }
225
226 void onRunnerJobFinished(const QString &jobId)
227 {
228 if (currentJobs.remove(jobId) && currentJobs.isEmpty()) {
229 // If there are any new matches scheduled to be notified, we should anticipate it and just refresh right now
230 if (matchChangeTimer.isActive()) {
231 matchChangeTimer.stop();
232 matchesChanged();
233 } else if (context.matches().isEmpty()) {
234 // we finished our run, and there are no valid matches, and so no
235 // signal will have been sent out, so we need to emit the signal ourselves here
236 matchesChanged();
237 }
238 setQuerying(false);
239 Q_EMIT q->queryFinished();
240 }
241 if (!currentJobs.isEmpty()) {
242 qCDebug(KRUNNER) << "Current jobs are" << currentJobs;
243 }
244 }
245
246 void teardown()
247 {
248 pendingJobsAfterSuspend.clear(); // Do not start old jobs when the match session is over
249 if (allRunnersPrepped) {
250 for (AbstractRunner *runner : std::as_const(runners)) {
251 Q_EMIT runner->teardown();
252 }
253 allRunnersPrepped = false;
254 }
255
256 if (singleRunnerPrepped) {
257 if (currentSingleRunner) {
258 Q_EMIT currentSingleRunner->teardown();
259 }
260 singleRunnerPrepped = false;
261 }
262
263 prepped = false;
264 }
265
266 void runnerMatchingResumed(AbstractRunner *runner)
267 {
268 Q_ASSERT(runner);
269 const QString jobId = pendingJobsAfterSuspend.value(runner);
270 if (jobId.isEmpty()) {
271 qCDebug(KRUNNER) << runner << "was not scheduled for current query";
272 return;
273 }
274 // Ignore this runner
275 if (singleMode && runner->id() != singleModeRunnerId) {
276 qCDebug(KRUNNER) << runner << "did not match requested singlerunnermode ID";
277 return;
278 }
279
280 const QString query = context.query();
281 bool matchesCount = singleMode || runner->minLetterCount() <= query.size();
282 bool matchesRegex = singleMode || !runner->hasMatchRegex() || runner->matchRegex().match(query).hasMatch();
283
284 if (matchesCount && matchesRegex) {
285 startJob(runner);
286 } else {
287 onRunnerJobFinished(jobId);
288 }
289 }
290
291 void startJob(AbstractRunner *runner)
292 {
293 QMetaObject::invokeMethod(runner, "matchInternal", Qt::QueuedConnection, Q_ARG(KRunner::RunnerContext, context));
294 }
295
296 // Must only be called once
297 void initializeKNotifyPluginWatcher()
298 {
299 Q_ASSERT(!watcher);
300 watcher = KConfigWatcher::create(KSharedConfig::openConfig(pluginConf.config()->name()));
301 q->connect(watcher.data(), &KConfigWatcher::configChanged, q, [this](const KConfigGroup &group, const QByteArrayList &changedNames) {
302 const QString groupName = group.name();
303 if (groupName == QLatin1String("Plugins")) {
304 q->reloadConfiguration();
305 } else if (groupName == QLatin1String("Runners")) {
306 for (auto *runner : std::as_const(runners)) {
307 // Signals from the KCM contain the component name, which is the KRunner plugin's id
308 if (changedNames.contains(runner->metadata().pluginId().toUtf8())) {
309 QMetaObject::invokeMethod(runner, "reloadConfigurationInternal");
310 }
311 }
312 } else if (group.parent().isValid() && group.parent().name() == QLatin1String("Runners")) {
313 for (auto *runner : std::as_const(runners)) {
314 // If the same config group has been modified which gets created in AbstractRunner::config()
315 if (groupName == runner->id()) {
316 QMetaObject::invokeMethod(runner, "reloadConfigurationInternal");
317 }
318 }
319 }
320 });
321 }
322
323 void setQuerying(bool querying)
324 {
325 if (m_querying != querying) {
326 m_querying = querying;
327 Q_EMIT q->queryingChanged();
328 }
329 }
330
331 void addToHistory()
332 {
333 const QString term = context.query();
334 // We want to imitate the shall behavior
335 if (!historyEnabled || term.isEmpty() || untrimmedTerm.startsWith(QLatin1Char(' '))) {
336 return;
337 }
338 QStringList historyEntries = readHistoryForCurrentEnv();
339 // Avoid removing the same item from the front and prepending it again
340 if (!historyEntries.isEmpty() && historyEntries.constFirst() == term) {
341 return;
342 }
343
344 historyEntries.removeOne(term);
345 historyEntries.prepend(term);
346
347 while (historyEntries.count() > 50) { // we don't want to store more than 50 entries
348 historyEntries.removeLast();
349 }
350 writeHistory(historyEntries);
351 }
352
353 void writeHistory(const QStringList &historyEntries)
354 {
355 stateData.group(QStringLiteral("History")).writeEntry(historyEnvironmentIdentifier, historyEntries, KConfig::Notify);
356 stateData.sync();
357 }
358
359 inline QStringList readHistoryForCurrentEnv()
360 {
361 return stateData.group(QStringLiteral("History")).readEntry(historyEnvironmentIdentifier, QStringList());
362 }
363
364 QString historyEnvironmentIdentifier = QStringLiteral("default");
365 RunnerManager *const q;
366 bool m_querying = false;
367 RunnerContext context;
368 QTimer matchChangeTimer;
369 QElapsedTimer lastMatchChangeSignalled;
370 QHash<QString, AbstractRunner *> runners;
371 QHash<AbstractRunner *, QString> pendingJobsAfterSuspend;
372 AbstractRunner *currentSingleRunner = nullptr;
373 QSet<QString> currentJobs;
374 QString singleModeRunnerId;
375 bool prepped = false;
376 bool allRunnersPrepped = false;
377 bool singleRunnerPrepped = false;
378 bool singleMode = false;
379 bool historyEnabled = true;
380 QStringList whiteList;
381 KConfigWatcher::Ptr watcher;
382 QString untrimmedTerm;
383 KConfigGroup pluginConf;
384 KConfigGroup stateData;
385 QSet<QString> disabledRunnerIds; // Runners that are disabled but were loaded as single runners
386};
387
388RunnerManager::RunnerManager(const KConfigGroup &pluginConfigGroup, const KConfigGroup &stateConfigGroup, QObject *parent)
389 : QObject(parent)
390 , d(new RunnerManagerPrivate(pluginConfigGroup, stateConfigGroup, this))
391{
392 Q_ASSERT(pluginConfigGroup.isValid());
393 Q_ASSERT(stateConfigGroup.isValid());
394}
395
397 : QObject(parent)
398{
399 auto defaultStatePtr = KSharedConfig::openConfig(QStringLiteral("krunnerstaterc"), KConfig::NoGlobals, QStandardPaths::GenericDataLocation);
400 auto configPtr = KSharedConfig::openConfig(QStringLiteral("krunnerrc"), KConfig::NoGlobals);
401 d = std::make_unique<RunnerManagerPrivate>(configPtr->group(QStringLiteral("Plugins")),
402 defaultStatePtr->group(QStringLiteral("PlasmaRunnerManager")),
403 this);
404}
405
406RunnerManager::~RunnerManager()
407{
408 d->context.reset();
409 d->deleteRunners(d->runners.values());
410}
411
413{
414 d->pluginConf.config()->reparseConfiguration();
415 d->stateData.config()->reparseConfiguration();
416 d->loadConfiguration();
417 d->loadRunners();
418}
419
421{
422 d->whiteList = runners;
423 if (!d->runners.isEmpty()) {
424 // this has been called with runners already created. so let's do an instant reload
425 d->loadRunners();
426 }
427}
428
429AbstractRunner *RunnerManager::loadRunner(const KPluginMetaData &pluginMetaData)
430{
431 const QString runnerId = pluginMetaData.pluginId();
432 if (auto loadedRunner = d->runners.value(runnerId)) {
433 return loadedRunner;
434 }
435 if (!runnerId.isEmpty()) {
436 if (AbstractRunner *runner = d->loadInstalledRunner(pluginMetaData)) {
437 d->runners.insert(runnerId, runner);
438 return runner;
439 }
440 }
441 return nullptr;
442}
443
444AbstractRunner *RunnerManager::runner(const QString &pluginId) const
445{
446 if (d->runners.isEmpty()) {
447 d->loadRunners();
448 }
449
450 return d->runners.value(pluginId, nullptr);
451}
452
454{
455 if (d->runners.isEmpty()) {
456 d->loadRunners();
457 }
458 return d->runners.values();
459}
460
462{
463 return &d->context;
464}
465
467{
468 return d->context.matches();
469}
470
471bool RunnerManager::run(const QueryMatch &match, const KRunner::Action &selectedAction)
472{
473 if (!match.isValid() || !match.isEnabled()) { // The model should prevent this
474 return false;
475 }
476
477 // Modify the match and run it
478 QueryMatch m = match;
479 m.setSelectedAction(selectedAction);
480 m.runner()->run(d->context, m);
481 // To allow the RunnerContext to increase the relevance of often launched apps
482 d->context.increaseLaunchCount(m);
483
484 if (!d->context.shouldIgnoreCurrentMatchForHistory()) {
485 d->addToHistory();
486 }
487 if (d->context.requestedQueryString().isEmpty()) {
488 return true;
489 } else {
490 Q_EMIT requestUpdateQueryString(d->context.requestedQueryString(), d->context.requestedCursorPosition());
491 return false;
492 }
493}
494
496{
497 return match.isValid() ? match.runner()->mimeDataForMatch(match) : nullptr;
498}
499
501{
502 QList<KPluginMetaData> pluginMetaDatas = KPluginMetaData::findPlugins(QStringLiteral("kf6/krunner"));
503 QSet<QString> knownRunnerIds;
504 knownRunnerIds.reserve(pluginMetaDatas.size());
505 for (const KPluginMetaData &pluginMetaData : std::as_const(pluginMetaDatas)) {
506 knownRunnerIds.insert(pluginMetaData.pluginId());
507 }
508
509 const QStringList dBusPlugindirs =
511 const QStringList dbusRunnerFiles = KFileUtils::findAllUniqueFiles(dBusPlugindirs, QStringList(QStringLiteral("*.desktop")));
512 for (const QString &dbusRunnerFile : dbusRunnerFiles) {
513 KPluginMetaData pluginMetaData = parseMetaDataFromDesktopFile(dbusRunnerFile);
514 if (pluginMetaData.isValid() && !knownRunnerIds.contains(pluginMetaData.pluginId())) {
515 pluginMetaDatas.append(pluginMetaData);
516 knownRunnerIds.insert(pluginMetaData.pluginId());
517 }
518 }
519
520 return pluginMetaDatas;
521}
522
524{
525 if (d->prepped) {
526 return;
527 }
528
529 d->prepped = true;
530 if (d->singleMode) {
531 if (d->currentSingleRunner) {
532 Q_EMIT d->currentSingleRunner->prepare();
533 d->singleRunnerPrepped = true;
534 }
535 } else {
536 for (AbstractRunner *runner : std::as_const(d->runners)) {
537 if (!d->disabledRunnerIds.contains(runner->name())) {
538 Q_EMIT runner->prepare();
539 }
540 }
541
542 d->allRunnersPrepped = true;
543 }
544}
545
547{
548 if (!d->prepped) {
549 return;
550 }
551
552 d->teardown();
553 // We save the context config after each session, just like the history entries
554 // BUG: 424505
555 d->context.save(d->stateData);
556}
557
558void RunnerManager::launchQuery(const QString &untrimmedTerm, const QString &runnerName)
559{
560 d->pendingJobsAfterSuspend.clear(); // Do not start old jobs when we got a new query
561 QString term = untrimmedTerm.trimmed();
562 const QString prevSingleRunner = d->singleModeRunnerId;
563 d->untrimmedTerm = untrimmedTerm;
564
565 // Set the required values and load the runner
566 d->singleModeRunnerId = runnerName;
567 d->singleMode = !runnerName.isEmpty();
568 d->loadSingleRunner();
569 // If we could not load the single runner we reset
570 if (!runnerName.isEmpty() && !d->currentSingleRunner) {
571 reset();
572 return;
573 }
574 if (term.isEmpty()) {
576 reset();
577 return;
578 }
579
580 if (d->context.query() == term && prevSingleRunner == runnerName) {
581 // we already are searching for this!
582 return;
583 }
584
585 if (!d->singleMode && d->runners.isEmpty()) {
586 d->loadRunners();
587 }
588
589 reset();
590 d->context.setQuery(term);
591
593
594 // if the name is not empty we will launch only the specified runner
595 if (d->singleMode) {
596 runnable.insert(QString(), d->currentSingleRunner);
597 d->context.setSingleRunnerQueryMode(true);
598 } else {
599 runnable = d->runners;
600 }
601
602 qint64 startTs = QDateTime::currentMSecsSinceEpoch();
603 d->context.setJobStartTs(startTs);
605 for (KRunner::AbstractRunner *r : std::as_const(runnable)) {
606 const QString &jobId = d->context.runnerJobId(r);
607 if (r->isMatchingSuspended()) {
608 d->pendingJobsAfterSuspend.insert(r, jobId);
609 d->currentJobs.insert(jobId);
610 continue;
611 }
612 // If this runner is loaded but disabled
613 if (!d->singleMode && d->disabledRunnerIds.contains(r->id())) {
614 continue;
615 }
616 // The runners can set the min letter count as a property, this way we don't
617 // have to spawn threads just for the runner to reject the query, because it is too short
618 if (!d->singleMode && term.length() < r->minLetterCount()) {
619 continue;
620 }
621 // If the runner has one ore more trigger words it can set the matchRegex to prevent
622 // thread spawning if the pattern does not match
623 if (!d->singleMode && r->hasMatchRegex() && !r->matchRegex().match(term).hasMatch()) {
624 continue;
625 }
626
627 d->currentJobs.insert(jobId);
628 d->startJob(r);
629 }
630 // In the unlikely case that no runner gets queried we have to emit the signals here
631 if (d->currentJobs.isEmpty()) {
632 QTimer::singleShot(0, this, [this]() {
633 d->currentJobs.clear();
636 });
637 d->setQuerying(false);
638 } else {
639 d->setQuerying(true);
640 }
641}
642
644{
645 return d->context.query();
646}
647
648QStringList RunnerManager::history() const
649{
650 return d->readHistoryForCurrentEnv();
651}
652
654{
655 QStringList changedHistory = history();
656 if (index < changedHistory.length()) {
657 changedHistory.removeAt(index);
658 d->writeHistory(changedHistory);
659 }
660}
661
663{
664 const QStringList historyList = history();
665 for (const QString &entry : historyList) {
666 if (entry.startsWith(typedQuery, Qt::CaseInsensitive)) {
667 return entry;
668 }
669 }
670 return QString();
671}
672
674{
675 if (!d->currentJobs.empty()) {
677 d->currentJobs.clear();
678 }
679 d->context.reset();
680}
681
682KPluginMetaData RunnerManager::convertDBusRunnerToJson(const QString &filename) const
683{
684 return parseMetaDataFromDesktopFile(filename);
685}
686
687bool RunnerManager::historyEnabled()
688{
689 return d->historyEnabled;
690}
691
692bool RunnerManager::querying() const
693{
694 return d->m_querying;
695}
696
698{
699 d->historyEnabled = enabled;
701}
702
703// Gets called by RunnerContext to inform that we got new matches
704void RunnerManager::onMatchesChanged()
705{
706 d->scheduleMatchesChanged();
707}
709{
710 Q_ASSERT(!identifier.isEmpty());
711 d->historyEnvironmentIdentifier = identifier;
712}
713
714} // KRunner namespace
715
716#include "moc_runnermanager.cpp"
QString name() const
bool isValid() const
KConfigGroup parent() const
static Ptr create(const KSharedConfig::Ptr &config)
void configChanged(const KConfigGroup &group, const QByteArrayList &names)
QString pluginId() const
bool value(QStringView key, bool defaultValue) const
QString fileName() const
static QList< KPluginMetaData > findPlugins(const QString &directory, std::function< bool(const KPluginMetaData &)> filter={}, KPluginMetaDataOptions options={})
QString name() const
bool isEnabled(const T &config) const
bool isValid() const
An abstract base class for Plasma Runner plugins.
virtual void run(const KRunner::RunnerContext &context, const KRunner::QueryMatch &match)
Called whenever an exact or possible match associated with this runner is triggered.
This class represents an action that will be shown next to a match.
Definition action.h:23
A match returned by an AbstractRunner in response to a given RunnerContext.
Definition querymatch.h:32
AbstractRunner * runner() const
The RunnerContext class provides information related to a search, including the search term and colle...
The RunnerManager class decides what installed runners are runnable, and their ratings.
void setHistoryEnabled(bool enabled)
Enables/disabled the history feature for the RunnerManager instance.
void setupMatchSession()
Call this method when the runners should be prepared for a query session.
RunnerContext * searchContext() const
Retrieves the current context.
bool run(const QueryMatch &match, const KRunner::Action &action={})
Runs a given match.
QList< QueryMatch > matches() const
Retrieves all available matches found so far for the previously launched query.
RunnerManager(const KConfigGroup &pluginConfigGroup, const KConfigGroup &stateGroup, QObject *parent)
Constructs a RunnerManager with the given parameters.
QMimeData * mimeDataForMatch(const QueryMatch &match) const
AbstractRunner * runner(const QString &pluginId) const
Finds and returns a loaded runner or a nullptr.
void reloadConfiguration()
Causes a reload of the current configuration This gets called automatically when the config in the KC...
Q_INVOKABLE void setHistoryEnvironmentIdentifier(const QString &identifier)
Set the environment identifier for recording history and launch counts.
static QList< KPluginMetaData > runnerMetaDataList()
void setAllowedRunners(const QStringList &runners)
Sets a whitelist for the plugins that can be loaded by this manager.
AbstractRunner * loadRunner(const KPluginMetaData &pluginMetaData)
Attempts to add the AbstractRunner plugin represented by the plugin info passed in.
Q_INVOKABLE void removeFromHistory(int index)
Delete the given index from the history.
void matchSessionComplete()
Call this method when the query session is finished for the time being.
void matchesChanged(const QList< KRunner::QueryMatch > &matches)
Emitted each time a new match is added to the list.
Q_INVOKABLE QString getHistorySuggestion(const QString &typedQuery) const
Get the suggested history entry for the typed query.
void requestUpdateQueryString(const QString &term, int cursorPosition)
Put the given search term in the KRunner search field.
void queryFinished()
Emitted when the launchQuery finish.
void launchQuery(const QString &term, const QString &runnerId=QString())
Launch a query, this will create threads and return immediately.
void reset()
Reset the current data and stops the query.
QList< AbstractRunner * > runners() const
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
std::optional< QSqlQuery > query(const QString &queryStatement)
KCOREADDONS_EXPORT QStringList findAllUniqueFiles(const QStringList &dirs, const QStringList &nameFilters={})
qint64 currentMSecsSinceEpoch()
iterator insert(const Key &key, const T &value)
void append(QList< T > &&value)
void clear()
const T & constFirst() const const
bool contains(const AT &value) const const
qsizetype count() const const
bool isEmpty() const const
qsizetype length() const const
void prepend(parameter_type value)
void removeAt(qsizetype i)
void removeLast()
bool removeOne(const AT &t)
qsizetype size() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QObject(QObject *parent)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QObject * parent() const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
void reserve(qsizetype size)
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
qsizetype length() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString trimmed() const const
CaseInsensitive
QueuedConnection
void finished()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 31 2025 12:10:47 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.