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 {
136
137 const bool loadAll = stateData.readEntry("loadAll", false);
138 const bool noWhiteList = whiteList.isEmpty();
139
140 QList<AbstractRunner *> deadRunners;
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);
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;
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
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
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())) {
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"
KConfigGroup group(const QString &group)
QString name() const
bool isValid() const
KConfig * config()
QString readEntry(const char *key, const char *aDefault=nullptr) const
KConfigGroup parent() const
static Ptr create(const KSharedConfig::Ptr &config)
void configChanged(const KConfigGroup &group, const QByteArrayList &names)
QString name() const
QString pluginId() const
bool value(const QString &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.
QString name() const
Returns the translated name from the runner's metadata.
void teardown()
This signal is emitted when a session of matches is complete, giving runners the opportunity to tear ...
virtual void run(const KRunner::RunnerContext &context, const KRunner::QueryMatch &match)
Called whenever an exact or possible match associated with this runner is triggered.
void prepare()
This signal is emitted when matching is about to commence, giving runners an opportunity to prepare t...
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...
QList< QueryMatch > matches() const
Retrieves all available matches for the current search term.
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)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KCOREADDONS_EXPORT QStringList findAllUniqueFiles(const QStringList &dirs, const QStringList &nameFilters={})
qint64 currentMSecsSinceEpoch()
qint64 elapsed() const const
bool hasExpired(qint64 timeout) const const
qint64 restart()
iterator insert(const Key &key, const T &value)
void append(QList< T > &&value)
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)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QThread * thread() const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
bool isEmpty() const const
bool remove(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
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
CaseInsensitive
QueuedConnection
void finished()
bool isActive() const const
void setSingleShot(bool singleShot)
void start()
void stop()
void timeout()
void setTimerType(Qt::TimerType atype)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:20:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.