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 Q_EMIT q->queryFinished(); // NOLINT(readability-misleading-indentation)
239 }
240 if (!currentJobs.isEmpty()) {
241 qCDebug(KRUNNER) << "Current jobs are" << currentJobs;
242 }
243 }
244
245 void teardown()
246 {
247 pendingJobsAfterSuspend.clear(); // Do not start old jobs when the match session is over
248 if (allRunnersPrepped) {
249 for (AbstractRunner *runner : std::as_const(runners)) {
250 Q_EMIT runner->teardown();
251 }
252 allRunnersPrepped = false;
253 }
254
255 if (singleRunnerPrepped) {
256 if (currentSingleRunner) {
257 Q_EMIT currentSingleRunner->teardown();
258 }
259 singleRunnerPrepped = false;
260 }
261
262 prepped = false;
263 }
264
265 void runnerMatchingResumed(AbstractRunner *runner)
266 {
267 Q_ASSERT(runner);
268 const QString jobId = pendingJobsAfterSuspend.value(runner);
269 if (jobId.isEmpty()) {
270 qCDebug(KRUNNER) << runner << "was not scheduled for current query";
271 return;
272 }
273 // Ignore this runner
274 if (singleMode && runner->id() != singleModeRunnerId) {
275 qCDebug(KRUNNER) << runner << "did not match requested singlerunnermode ID";
276 return;
277 }
278
279 const QString query = context.query();
280 bool matchesCount = singleMode || runner->minLetterCount() <= query.size();
281 bool matchesRegex = singleMode || !runner->hasMatchRegex() || runner->matchRegex().match(query).hasMatch();
282
283 if (matchesCount && matchesRegex) {
284 startJob(runner);
285 } else {
286 onRunnerJobFinished(jobId);
287 }
288 }
289
290 void startJob(AbstractRunner *runner)
291 {
292 QMetaObject::invokeMethod(runner, "matchInternal", Qt::QueuedConnection, Q_ARG(KRunner::RunnerContext, context));
293 }
294
295 // Must only be called once
296 void initializeKNotifyPluginWatcher()
297 {
298 Q_ASSERT(!watcher);
300 q->connect(watcher.data(), &KConfigWatcher::configChanged, q, [this](const KConfigGroup &group, const QByteArrayList &changedNames) {
301 const QString groupName = group.name();
302 if (groupName == QLatin1String("Plugins")) {
303 q->reloadConfiguration();
304 } else if (groupName == QLatin1String("Runners")) {
305 for (auto *runner : std::as_const(runners)) {
306 // Signals from the KCM contain the component name, which is the KRunner plugin's id
307 if (changedNames.contains(runner->metadata().pluginId().toUtf8())) {
308 QMetaObject::invokeMethod(runner, "reloadConfigurationInternal");
309 }
310 }
311 } else if (group.parent().isValid() && group.parent().name() == QLatin1String("Runners")) {
312 for (auto *runner : std::as_const(runners)) {
313 // If the same config group has been modified which gets created in AbstractRunner::config()
314 if (groupName == runner->id()) {
315 QMetaObject::invokeMethod(runner, "reloadConfigurationInternal");
316 }
317 }
318 }
319 });
320 }
321
322 void addToHistory()
323 {
324 const QString term = context.query();
325 // We want to imitate the shall behavior
326 if (!historyEnabled || term.isEmpty() || untrimmedTerm.startsWith(QLatin1Char(' '))) {
327 return;
328 }
329 QStringList historyEntries = readHistoryForCurrentEnv();
330 // Avoid removing the same item from the front and prepending it again
331 if (!historyEntries.isEmpty() && historyEntries.constFirst() == term) {
332 return;
333 }
334
335 historyEntries.removeOne(term);
336 historyEntries.prepend(term);
337
338 while (historyEntries.count() > 50) { // we don't want to store more than 50 entries
339 historyEntries.removeLast();
340 }
341 writeHistory(historyEntries);
342 }
343
344 void writeHistory(const QStringList &historyEntries)
345 {
346 stateData.group(QStringLiteral("History")).writeEntry(historyEnvironmentIdentifier, historyEntries, KConfig::Notify);
347 stateData.sync();
348 }
349
350 inline QStringList readHistoryForCurrentEnv()
351 {
352 return stateData.group(QStringLiteral("History")).readEntry(historyEnvironmentIdentifier, QStringList());
353 }
354
355 QString historyEnvironmentIdentifier = QStringLiteral("default");
356 RunnerManager *const q;
357 RunnerContext context;
358 QTimer matchChangeTimer;
359 QElapsedTimer lastMatchChangeSignalled;
361 QHash<AbstractRunner *, QString> pendingJobsAfterSuspend;
362 AbstractRunner *currentSingleRunner = nullptr;
363 QSet<QString> currentJobs;
364 QString singleModeRunnerId;
365 bool prepped = false;
366 bool allRunnersPrepped = false;
367 bool singleRunnerPrepped = false;
368 bool singleMode = false;
369 bool historyEnabled = true;
370 QStringList whiteList;
371 KConfigWatcher::Ptr watcher;
372 QString untrimmedTerm;
373 KConfigGroup pluginConf;
374 KConfigGroup stateData;
375 QSet<QString> disabledRunnerIds; // Runners that are disabled but were loaded as single runners
376};
377
378RunnerManager::RunnerManager(const KConfigGroup &pluginConfigGroup, const KConfigGroup &stateConfigGroup, QObject *parent)
379 : QObject(parent)
380 , d(new RunnerManagerPrivate(pluginConfigGroup, stateConfigGroup, this))
381{
382 Q_ASSERT(pluginConfigGroup.isValid());
383 Q_ASSERT(stateConfigGroup.isValid());
384}
385
387 : QObject(parent)
388{
389 auto defaultStatePtr = KSharedConfig::openConfig(QStringLiteral("krunnerstaterc"), KConfig::NoGlobals, QStandardPaths::GenericDataLocation);
390 auto configPtr = KSharedConfig::openConfig(QStringLiteral("krunnerrc"), KConfig::NoGlobals);
391 d = std::make_unique<RunnerManagerPrivate>(configPtr->group(QStringLiteral("Plugins")),
392 defaultStatePtr->group(QStringLiteral("PlasmaRunnerManager")),
393 this);
394}
395
396RunnerManager::~RunnerManager()
397{
398 d->context.reset();
399 d->deleteRunners(d->runners.values());
400}
401
403{
404 d->pluginConf.config()->reparseConfiguration();
405 d->stateData.config()->reparseConfiguration();
406 d->loadConfiguration();
407 d->loadRunners();
408}
409
411{
412 d->whiteList = runners;
413 if (!d->runners.isEmpty()) {
414 // this has been called with runners already created. so let's do an instant reload
415 d->loadRunners();
416 }
417}
418
420{
421 const QString runnerId = pluginMetaData.pluginId();
422 if (auto loadedRunner = d->runners.value(runnerId)) {
423 return loadedRunner;
424 }
425 if (!runnerId.isEmpty()) {
426 if (AbstractRunner *runner = d->loadInstalledRunner(pluginMetaData)) {
427 d->runners.insert(runnerId, runner);
428 return runner;
429 }
430 }
431 return nullptr;
432}
433
435{
436 if (d->runners.isEmpty()) {
437 d->loadRunners();
438 }
439
440 return d->runners.value(pluginId, nullptr);
441}
442
444{
445 if (d->runners.isEmpty()) {
446 d->loadRunners();
447 }
448 return d->runners.values();
449}
450
452{
453 return &d->context;
454}
455
457{
458 return d->context.matches();
459}
460
461bool RunnerManager::run(const QueryMatch &match, const KRunner::Action &selectedAction)
462{
463 if (!match.isValid() || !match.isEnabled()) { // The model should prevent this
464 return false;
465 }
466
467 // Modify the match and run it
468 QueryMatch m = match;
469 m.setSelectedAction(selectedAction);
470 m.runner()->run(d->context, m);
471 // To allow the RunnerContext to increase the relevance of often launched apps
472 d->context.increaseLaunchCount(m);
473
474 if (!d->context.shouldIgnoreCurrentMatchForHistory()) {
475 d->addToHistory();
476 }
477 if (d->context.requestedQueryString().isEmpty()) {
478 return true;
479 } else {
480 Q_EMIT requestUpdateQueryString(d->context.requestedQueryString(), d->context.requestedCursorPosition());
481 return false;
482 }
483}
484
486{
487 return match.isValid() ? match.runner()->mimeDataForMatch(match) : nullptr;
488}
489
491{
492 QList<KPluginMetaData> pluginMetaDatas = KPluginMetaData::findPlugins(QStringLiteral("kf6/krunner"));
493 QSet<QString> knownRunnerIds;
494 knownRunnerIds.reserve(pluginMetaDatas.size());
495 for (const KPluginMetaData &pluginMetaData : std::as_const(pluginMetaDatas)) {
496 knownRunnerIds.insert(pluginMetaData.pluginId());
497 }
498
499 const QStringList dBusPlugindirs =
501 const QStringList dbusRunnerFiles = KFileUtils::findAllUniqueFiles(dBusPlugindirs, QStringList(QStringLiteral("*.desktop")));
502 for (const QString &dbusRunnerFile : dbusRunnerFiles) {
503 KPluginMetaData pluginMetaData = parseMetaDataFromDesktopFile(dbusRunnerFile);
504 if (pluginMetaData.isValid() && !knownRunnerIds.contains(pluginMetaData.pluginId())) {
505 pluginMetaDatas.append(pluginMetaData);
506 knownRunnerIds.insert(pluginMetaData.pluginId());
507 }
508 }
509
510 return pluginMetaDatas;
511}
512
514{
515 if (d->prepped) {
516 return;
517 }
518
519 d->prepped = true;
520 if (d->singleMode) {
521 if (d->currentSingleRunner) {
522 Q_EMIT d->currentSingleRunner->prepare();
523 d->singleRunnerPrepped = true;
524 }
525 } else {
526 for (AbstractRunner *runner : std::as_const(d->runners)) {
527 if (!d->disabledRunnerIds.contains(runner->name())) {
529 }
530 }
531
532 d->allRunnersPrepped = true;
533 }
534}
535
537{
538 if (!d->prepped) {
539 return;
540 }
541
542 d->teardown();
543 // We save the context config after each session, just like the history entries
544 // BUG: 424505
545 d->context.save(d->stateData);
546}
547
548void RunnerManager::launchQuery(const QString &untrimmedTerm, const QString &runnerName)
549{
550 d->pendingJobsAfterSuspend.clear(); // Do not start old jobs when we got a new query
551 QString term = untrimmedTerm.trimmed();
552 const QString prevSingleRunner = d->singleModeRunnerId;
553 d->untrimmedTerm = untrimmedTerm;
554
555 // Set the required values and load the runner
556 d->singleModeRunnerId = runnerName;
557 d->singleMode = !runnerName.isEmpty();
558 d->loadSingleRunner();
559 // If we could not load the single runner we reset
560 if (!runnerName.isEmpty() && !d->currentSingleRunner) {
561 reset();
562 return;
563 }
564 if (term.isEmpty()) {
566 reset();
567 return;
568 }
569
570 if (d->context.query() == term && prevSingleRunner == runnerName) {
571 // we already are searching for this!
572 return;
573 }
574
575 if (!d->singleMode && d->runners.isEmpty()) {
576 d->loadRunners();
577 }
578
579 reset();
580 d->context.setQuery(term);
581
583
584 // if the name is not empty we will launch only the specified runner
585 if (d->singleMode) {
586 runnable.insert(QString(), d->currentSingleRunner);
587 d->context.setSingleRunnerQueryMode(true);
588 } else {
589 runnable = d->runners;
590 }
591
592 qint64 startTs = QDateTime::currentMSecsSinceEpoch();
593 d->context.setJobStartTs(startTs);
595 for (KRunner::AbstractRunner *r : std::as_const(runnable)) {
596 const QString &jobId = d->context.runnerJobId(r);
597 if (r->isMatchingSuspended()) {
598 d->pendingJobsAfterSuspend.insert(r, jobId);
599 d->currentJobs.insert(jobId);
600 continue;
601 }
602 // If this runner is loaded but disabled
603 if (!d->singleMode && d->disabledRunnerIds.contains(r->id())) {
604 continue;
605 }
606 // The runners can set the min letter count as a property, this way we don't
607 // have to spawn threads just for the runner to reject the query, because it is too short
608 if (!d->singleMode && term.length() < r->minLetterCount()) {
609 continue;
610 }
611 // If the runner has one ore more trigger words it can set the matchRegex to prevent
612 // thread spawning if the pattern does not match
613 if (!d->singleMode && r->hasMatchRegex() && !r->matchRegex().match(term).hasMatch()) {
614 continue;
615 }
616
617 d->currentJobs.insert(jobId);
618 d->startJob(r);
619 }
620 // In the unlikely case that no runner gets queried we have to emit the signals here
621 if (d->currentJobs.isEmpty()) {
622 QTimer::singleShot(0, this, [this]() {
623 d->currentJobs.clear();
626 });
627 }
628}
629
631{
632 return d->context.query();
633}
634
635QStringList RunnerManager::history() const
636{
637 return d->readHistoryForCurrentEnv();
638}
639
641{
642 QStringList changedHistory = history();
643 if (index < changedHistory.length()) {
644 changedHistory.removeAt(index);
645 d->writeHistory(changedHistory);
646 }
647}
648
650{
651 const QStringList historyList = history();
652 for (const QString &entry : historyList) {
653 if (entry.startsWith(typedQuery, Qt::CaseInsensitive)) {
654 return entry;
655 }
656 }
657 return QString();
658}
659
661{
662 if (!d->currentJobs.empty()) {
664 d->currentJobs.clear();
665 }
666 d->context.reset();
667}
668
669KPluginMetaData RunnerManager::convertDBusRunnerToJson(const QString &filename) const
670{
671 return parseMetaDataFromDesktopFile(filename);
672}
673
674bool RunnerManager::historyEnabled()
675{
676 return d->historyEnabled;
677}
678
680{
681 d->historyEnabled = enabled;
683}
684
685// Gets called by RunnerContext to inform that we got new matches
686void RunnerManager::onMatchesChanged()
687{
688 d->scheduleMatchesChanged();
689}
691{
692 Q_ASSERT(!identifier.isEmpty());
693 d->historyEnvironmentIdentifier = identifier;
694}
695
696} // KRunner namespace
697
698#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)
std::optional< QSqlQuery > query(const QString &queryStatement)
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
iterator insert(const_iterator before, parameter_type value)
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)
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 May 17 2024 11:55:56 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.