KDED

kded.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7*/
8
9#include "kded.h"
10#include "kded_debug.h"
11#include "kded_version.h"
12#include "kdedadaptor.h"
13
14#include <KCrash>
15
16#include <qplatformdefs.h>
17
18#include <QApplication>
19#include <QCommandLineParser>
20#include <QDir>
21#include <QLoggingCategory>
22#include <QProcess>
23
24#include <QDBusConnection>
25#include <QDBusConnectionInterface>
26#include <QDBusServiceWatcher>
27
28#include <KAboutData>
29#include <KConfigGroup>
30#include <KDBusService>
31#include <KDirWatch>
32#include <KPluginFactory>
33#include <KPluginMetaData>
34#include <KSharedConfig>
35
36#include <memory>
37
38Kded *Kded::_self = nullptr;
39
40static bool delayedCheck;
41static bool bCheckSycoca;
42static bool bCheckUpdates;
43
44#ifdef Q_DBUS_EXPORT
45extern Q_DBUS_EXPORT void qDBusAddSpyHook(void (*)(const QDBusMessage &));
46#else
47extern QDBUS_EXPORT void qDBusAddSpyHook(void (*)(const QDBusMessage &));
48#endif
49
50static void runKonfUpdate()
51{
52 int ret = QProcess::execute(QStringLiteral(KCONF_UPDATE_EXE), QStringList());
53 if (ret != 0) {
54 qCWarning(KDED) << KCONF_UPDATE_EXE << "returned" << ret;
55 }
56}
57
58Kded::Kded()
59 : m_pDirWatch(new KDirWatch(this))
60 , m_pTimer(new QTimer(this))
61 , m_needDelayedCheck(false)
62{
63 _self = this;
64
65 m_serviceWatcher = new QDBusServiceWatcher(this);
66 m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
67 m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
68 QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &Kded::slotApplicationRemoved);
69
70 new KdedAdaptor(this);
71
73 session.registerObject(QStringLiteral("/kbuildsycoca"), this);
74 session.registerObject(QStringLiteral("/kded"), this);
75
76 m_pTimer->setSingleShot(true);
77 connect(m_pTimer, &QTimer::timeout, this, static_cast<void (Kded::*)()>(&Kded::recreate));
78}
79
80Kded::~Kded()
81{
82 _self = nullptr;
83 m_pTimer->stop();
84
85 for (auto it = m_modules.cbegin(); it != m_modules.cend(); ++it) {
86 delete *it;
87 }
88 m_modules.clear();
89}
90
91// on-demand module loading
92// this function is called by the D-Bus message processing function before
93// calls are delivered to objects
94void Kded::messageFilter(const QDBusMessage &message)
95{
96 // This happens when kded goes down and some modules try to clean up.
97 if (!self()) {
98 return;
99 }
100
102 if (obj.isEmpty() || obj == QLatin1String("ksycoca")) {
103 return;
104 }
105
106 if (self()->m_dontLoad.value(obj, nullptr)) {
107 return;
108 }
109
110 self()->loadModule(obj, true);
111}
112
113static int phaseForModule(const KPluginMetaData &module)
114{
115 return module.value(QStringLiteral("X-KDE-Kded-phase"), 2);
116}
117
118QList<KPluginMetaData> Kded::availableModules() const
119{
120 QList<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/kded"));
121 QSet<QString> moduleIds;
122 for (const KPluginMetaData &md : std::as_const(plugins)) {
123 moduleIds.insert(md.pluginId());
124 }
125 return plugins;
126}
127
128static KPluginMetaData findModule(const QString &id)
129{
130 KPluginMetaData module(QStringLiteral("kf6/kded/") + id);
131 if (module.isValid()) {
132 return module;
133 }
134 qCWarning(KDED) << "could not find kded module with id" << id;
135 return KPluginMetaData();
136}
137
138void Kded::initModules()
139{
140 m_dontLoad.clear();
141
142 bool kde_running = !qEnvironmentVariableIsEmpty("KDE_FULL_SESSION");
143 if (kde_running) {
144 // not the same user like the one running the session (most likely we're run via sudo or something)
145 const QByteArray sessionUID = qgetenv("KDE_SESSION_UID");
146 if (!sessionUID.isEmpty() && uid_t(sessionUID.toInt()) != getuid()) {
147 kde_running = false;
148 }
149 // not the same kde version as the current desktop
150 const QByteArray kdeSession = qgetenv("KDE_SESSION_VERSION");
151 if (kdeSession.toInt() != 6) {
152 kde_running = false;
153 }
154 }
155
156 // Preload kded modules.
157 const QList<KPluginMetaData> kdedModules = availableModules();
158 for (const KPluginMetaData &module : kdedModules) {
159 // Should the service load on startup?
160 const bool autoload = isModuleAutoloaded(module);
161 if (!platformSupportsModule(module)) {
162 continue;
163 }
164
165 // see ksmserver's README for description of the phases
166 bool prevent_autoload = false;
167 switch (phaseForModule(module)) {
168 case 0: // always autoload
169 break;
170 case 1: // autoload only in KDE
171 if (!kde_running) {
172 prevent_autoload = true;
173 }
174 break;
175 case 2: // autoload delayed, only in KDE
176 default:
177 if (!kde_running) {
178 prevent_autoload = true;
179 }
180 break;
181 }
182
183 // Load the module if necessary and allowed
184 if (autoload && !prevent_autoload) {
185 if (!loadModule(module, false)) {
186 continue;
187 }
188 }
189
190 // Remember if the module is allowed to load on demand
191 bool loadOnDemand = isModuleLoadedOnDemand(module);
192 if (!loadOnDemand) {
193 noDemandLoad(module.pluginId());
194 }
195
196 // In case of reloading the configuration it is possible for a module
197 // to run even if it is now allowed to. Stop it then.
198 if (!loadOnDemand && !autoload) {
199 unloadModule(module.pluginId());
200 }
201 }
202}
203
204void Kded::noDemandLoad(const QString &obj)
205{
206 m_dontLoad.insert(obj, this);
207}
208
209void Kded::setModuleAutoloading(const QString &obj, bool autoload)
210{
211 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kded5rc"));
212 // Ensure the service exists.
213 KPluginMetaData module = findModule(obj);
214 if (!module.isValid()) {
215 return;
216 }
217 KConfigGroup cg(config, QStringLiteral("Module-").append(module.pluginId()));
218 cg.writeEntry("autoload", autoload);
219 cg.sync();
220}
221
222bool Kded::isModuleAutoloaded(const QString &obj) const
223{
224 return isModuleAutoloaded(findModule(obj));
225}
226
227bool Kded::isModuleAutoloaded(const KPluginMetaData &module) const
228{
229 if (!module.isValid()) {
230 return false;
231 }
232 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kded5rc"));
233 bool autoload = module.value(QStringLiteral("X-KDE-Kded-autoload"), false);
234 KConfigGroup cg(config, QStringLiteral("Module-").append(module.pluginId()));
235 autoload = cg.readEntry("autoload", autoload);
236 return autoload;
237}
238
239bool Kded::platformSupportsModule(const KPluginMetaData &module) const
240{
241 const QStringList supportedPlatforms = module.value(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"), QStringList());
242
243 return supportedPlatforms.isEmpty() || supportedPlatforms.contains(qApp->platformName());
244}
245
246bool Kded::isModuleLoadedOnDemand(const QString &obj) const
247{
248 return isModuleLoadedOnDemand(findModule(obj));
249}
250
251bool Kded::isModuleLoadedOnDemand(const KPluginMetaData &module) const
252{
253 if (!module.isValid()) {
254 return false;
255 }
256 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kded5rc"));
257 return module.value(QStringLiteral("X-KDE-Kded-load-on-demand"), true);
258}
259
260KDEDModule *Kded::loadModule(const QString &obj, bool onDemand)
261{
262 // Make sure this method is only called with valid module names.
263 if (obj.contains(QLatin1Char('/'))) {
264 qCWarning(KDED) << "attempting to load invalid kded module name:" << obj;
265 return nullptr;
266 }
267 KDEDModule *module = m_modules.value(obj, nullptr);
268 if (module) {
269 return module;
270 }
271 return loadModule(findModule(obj), onDemand);
272}
273
274KDEDModule *Kded::loadModule(const KPluginMetaData &module, bool onDemand)
275{
276 if (!module.isValid() || module.fileName().isEmpty()) {
277 qCWarning(KDED) << "attempted to load an invalid module.";
278 return nullptr;
279 }
280 const QString moduleId = module.pluginId();
281 KDEDModule *oldModule = m_modules.value(moduleId, nullptr);
282 if (oldModule) {
283 qCDebug(KDED) << "kded module" << moduleId << "is already loaded.";
284 return oldModule;
285 }
286
287 if (onDemand) {
288 if (!module.value(QStringLiteral("X-KDE-Kded-load-on-demand"), true)) {
289 noDemandLoad(moduleId);
290 return nullptr;
291 }
292 }
293
294 KDEDModule *kdedModule = nullptr;
295
296 auto factoryResult = KPluginFactory::loadFactory(module);
297 if (factoryResult) {
298 kdedModule = factoryResult.plugin->create<KDEDModule>(this);
299 } else {
300 qCWarning(KDED).nospace() << "Could not load kded module " << moduleId << ":" << factoryResult.errorText << " (library path was:" << module.fileName()
301 << ")";
302 }
303
304 if (kdedModule) {
305 kdedModule->setModuleName(moduleId);
306 m_modules.insert(moduleId, kdedModule);
307 // m_libs.insert(moduleId, lib);
308 qCDebug(KDED) << "Successfully loaded module" << moduleId;
309 return kdedModule;
310 }
311 return nullptr;
312}
313
314bool Kded::unloadModule(const QString &obj)
315{
316 KDEDModule *module = m_modules.value(obj, nullptr);
317 if (!module) {
318 return false;
319 }
320 qCDebug(KDED) << "Unloading module" << obj;
321 m_modules.remove(obj);
322 delete module;
323 return true;
324}
325
326QStringList Kded::loadedModules()
327{
328 return m_modules.keys();
329}
330
331void Kded::slotApplicationRemoved(const QString &name)
332{
333#if 0 // see kdedmodule.cpp (KDED_OBJECTS)
334 foreach (KDEDModule *module, m_modules) {
335 module->removeAll(appId);
336 }
337#endif
338 m_serviceWatcher->removeWatchedService(name);
339 const QList<qlonglong> windowIds = m_windowIdList.value(name);
340 for (const auto id : windowIds) {
341 m_globalWindowIdList.remove(id);
342 for (KDEDModule *module : std::as_const(m_modules)) {
343 Q_EMIT module->windowUnregistered(id);
344 }
345 }
346 m_windowIdList.remove(name);
347}
348
349void Kded::updateDirWatch()
350{
351 if (!bCheckUpdates) {
352 return;
353 }
354
355 delete m_pDirWatch;
356 m_pDirWatch = new KDirWatch(this);
357
358 QObject::connect(m_pDirWatch, &KDirWatch::dirty, this, &Kded::update);
359 QObject::connect(m_pDirWatch, &KDirWatch::created, this, &Kded::update);
360 QObject::connect(m_pDirWatch, &KDirWatch::deleted, this, &Kded::dirDeleted);
361
362 // For each resource
363 for (const QString &dir : std::as_const(m_allResourceDirs)) {
364 readDirectory(dir);
365 }
366
368 for (auto &dir : dataDirs) {
369 dir += QLatin1String("/icons");
370 if (!m_pDirWatch->contains(dir)) {
371 m_pDirWatch->addDir(dir, KDirWatch::WatchDirOnly);
372 }
373 }
374}
375
376void Kded::updateResourceList()
377{
378 KSycoca::clearCaches();
379
380 if (!bCheckUpdates) {
381 return;
382 }
383
384 if (delayedCheck) {
385 return;
386 }
387
388 const QStringList dirs = KSycoca::self()->allResourceDirs();
389 // For each resource
390 for (const auto &dir : dirs) {
391 if (!m_allResourceDirs.contains(dir)) {
392 m_allResourceDirs.append(dir);
393 readDirectory(dir);
394 }
395 }
396}
397
398void Kded::recreate()
399{
400 recreate(false);
401}
402
403void Kded::runDelayedCheck()
404{
405 if (m_needDelayedCheck) {
406 recreate(false);
407 }
408 m_needDelayedCheck = false;
409}
410
411void Kded::recreate(bool initial)
412{
413 // Using KLauncher here is difficult since we might not have a
414 // database
415
416 if (!initial) {
417 updateDirWatch(); // Update tree first, to be sure to miss nothing.
419 recreateDone();
420 } else {
421 if (!delayedCheck) {
422 updateDirWatch(); // this would search all the directories
423 }
424 if (bCheckSycoca) {
426 }
427 recreateDone();
428 if (delayedCheck) {
429 // do a proper ksycoca check after a delay
430 QTimer::singleShot(60000, this, &Kded::runDelayedCheck);
431 m_needDelayedCheck = true;
432 delayedCheck = false;
433 } else {
434 m_needDelayedCheck = false;
435 }
436 }
437}
438
439void Kded::recreateDone()
440{
441 updateResourceList();
442
443 initModules();
444}
445
446void Kded::dirDeleted(const QString &path)
447{
448 update(path);
449}
450
451void Kded::update(const QString &path)
452{
453 if (path.endsWith(QLatin1String("/icons")) && m_pDirWatch->contains(path)) {
454 // If the dir was created or updated there could be new folders to merge into the active theme(s)
455 QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"), QStringLiteral("iconChanged"));
456 message << 0;
458 } else {
459 m_pTimer->start(1000);
460 }
461}
462
463void Kded::readDirectory(const QString &_path)
464{
465 QString path(_path);
466 if (!path.endsWith(QLatin1Char('/'))) {
467 path += QLatin1Char('/');
468 }
469
470 if (m_pDirWatch->contains(path)) { // Already seen this one?
471 return;
472 }
473
474 Q_ASSERT(path != QDir::homePath());
475 m_pDirWatch->addDir(path, KDirWatch::WatchFiles | KDirWatch::WatchSubDirs); // add watch on this dir
476}
477
478void Kded::registerWindowId(qlonglong windowId, const QString &sender)
479{
480 if (!m_windowIdList.contains(sender)) {
481 m_serviceWatcher->addWatchedService(sender);
482 }
483
484 m_globalWindowIdList.insert(windowId);
485 QList<qlonglong> windowIds = m_windowIdList.value(sender);
486 windowIds.append(windowId);
487 m_windowIdList.insert(sender, windowIds);
488
489 for (KDEDModule *module : std::as_const(m_modules)) {
490 qCDebug(KDED) << module->moduleName();
491 Q_EMIT module->windowRegistered(windowId);
492 }
493}
494
495void Kded::unregisterWindowId(qlonglong windowId, const QString &sender)
496{
497 m_globalWindowIdList.remove(windowId);
498 QList<qlonglong> windowIds = m_windowIdList.value(sender);
499 if (!windowIds.isEmpty()) {
500 windowIds.removeAll(windowId);
501 if (windowIds.isEmpty()) {
502 m_serviceWatcher->removeWatchedService(sender);
503 m_windowIdList.remove(sender);
504 } else {
505 m_windowIdList.insert(sender, windowIds);
506 }
507 }
508
509 for (KDEDModule *module : std::as_const(m_modules)) {
510 qCDebug(KDED) << module->moduleName();
511 Q_EMIT module->windowUnregistered(windowId);
512 }
513}
514
515static void sighandler(int /*sig*/)
516{
517 if (qApp) {
518 qApp->quit();
519 }
520}
521
522KUpdateD::KUpdateD()
523{
524 m_pDirWatch = new KDirWatch(this);
525 m_pTimer = new QTimer(this);
526 m_pTimer->setSingleShot(true);
527 connect(m_pTimer, &QTimer::timeout, this, &KUpdateD::runKonfUpdate);
528 QObject::connect(m_pDirWatch, &KDirWatch::dirty, this, &KUpdateD::slotNewUpdateFile);
529
531 for (auto &path : dirs) {
532 Q_ASSERT(path != QDir::homePath());
533 if (!path.endsWith(QLatin1Char('/'))) {
534 path += QLatin1Char('/');
535 }
536
537 if (!m_pDirWatch->contains(path)) {
538 m_pDirWatch->addDir(path, KDirWatch::WatchFiles);
539 }
540 }
541}
542
543KUpdateD::~KUpdateD()
544{
545}
546
547void KUpdateD::runKonfUpdate()
548{
549 ::runKonfUpdate();
550}
551
552void KUpdateD::slotNewUpdateFile(const QString &dirty)
553{
554 Q_UNUSED(dirty);
555 qCDebug(KDED) << dirty;
556 m_pTimer->start(500);
557}
558
559static bool detectPlatform(int argc, char **argv)
560{
561 if (qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
562 return false;
563 }
564 for (int i = 0; i < argc; i++) {
565 /* clang-format off */
566 if (qstrcmp(argv[i], "-platform") == 0
567 || qstrcmp(argv[i], "--platform") == 0
568 || QByteArrayView(argv[i]).startsWith("-platform=")
569 || QByteArrayView(argv[i]).startsWith("--platform=")) { /* clang-format on */
570 return false;
571 }
572 }
573 const QByteArray sessionType = qgetenv("XDG_SESSION_TYPE");
574 if (sessionType.isEmpty()) {
575 return false;
576 }
577 if (qstrcmp(sessionType.data(), "wayland") == 0) {
578 qputenv("QT_QPA_PLATFORM", "wayland");
579 return true;
580 } else if (qstrcmp(sessionType.data(), "x11") == 0) {
581 qputenv("QT_QPA_PLATFORM", "xcb");
582 return true;
583 }
584 return false;
585}
586
587int main(int argc, char *argv[])
588{
589 // options.add("check", qi18n("Check Sycoca database only once"));
590
591 // WABA: Make sure not to enable session management.
592 qunsetenv("SESSION_MANAGER");
593
594 const bool unsetQpa = detectPlatform(argc, argv);
595
596 // In older versions, QApplication creation was postponed until after
597 // testing for --check, in which case, only a QCoreApplication was created.
598 // Since that option is no longer used at startup, we removed that speed
599 // optimization for code clarity and easier support of standard parameters.
600
601 QApplication app(argc, argv);
602 if (unsetQpa) {
603 qunsetenv("QT_QPA_PLATFORM");
604 }
605
606 app.setQuitOnLastWindowClosed(false);
607 app.setQuitLockEnabled(false);
608
609 KAboutData about(QStringLiteral("kded6"), QString(), QStringLiteral(KDED_VERSION_STRING));
611
613
614 QCommandLineParser parser;
615 parser.addHelpOption();
616 parser.addVersionOption();
617 parser.addOption(QCommandLineOption(QStringLiteral("check"), QStringLiteral("Check cache validity")));
618 QCommandLineOption replaceOption({QStringLiteral("replace")}, QStringLiteral("Replace an existing instance"));
619 parser.addOption(replaceOption);
620 parser.process(app);
621
622 // Parse command line before checking D-Bus
623 if (parser.isSet(QStringLiteral("check"))) {
624 // KDBusService not wanted here.
626 runKonfUpdate();
627 return 0;
628 }
629
630 // Qt now has DBus in another thread, so we need to handle any messages
631 // between the service registration and our paths existing
632 // This means adding the spy now, so any received message gets
633 // posted to the main thread
634 qDBusAddSpyHook(Kded::messageFilter);
635
637 // Also register as all the names we should respond to (org.kde.kcookiejar, org.kde.khotkeys etc.)
638 // so that the calling code is independent from the physical "location" of the service.
639 const QList<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/kded"));
640 for (const KPluginMetaData &metaData : plugins) {
641 const QString serviceName = metaData.value(QStringLiteral("X-KDE-DBus-ServiceName"));
642 if (serviceName.isEmpty()) {
643 continue;
644 }
645 // register them queued as an old kded could be running at this point
647 qCWarning(KDED) << "Couldn't register name" << serviceName << "with DBUS - another process owns it already!";
648 }
649 }
651
652 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kded5rc"));
653 KConfigGroup cg(config, QStringLiteral("General"));
654
655 bCheckSycoca = cg.readEntry("CheckSycoca", true);
656 bCheckUpdates = cg.readEntry("CheckUpdates", true);
657 delayedCheck = cg.readEntry("DelayedCheck", false);
658
659 signal(SIGTERM, sighandler);
660 signal(SIGHUP, sighandler);
661
663
664 std::unique_ptr<Kded> kded = std::make_unique<Kded>();
665
666 kded->recreate(true); // initial
667
668 if (bCheckUpdates) {
669 (void)new KUpdateD; // Watch for updates
670 }
671
672 runKonfUpdate(); // Run it once.
673
674 return app.exec(); // keep running
675}
676
677#include "moc_kded.cpp"
static void setApplicationData(const KAboutData &aboutData)
static QString moduleForMessage(const QDBusMessage &message)
void setModuleName(const QString &name)
void windowUnregistered(qlonglong windowId)
void windowRegistered(qlonglong windowId)
bool contains(const QString &path) const
void deleted(const QString &path)
void addDir(const QString &path, WatchModes watchModes=WatchDirOnly)
void dirty(const QString &path)
void created(const QString &path)
static Result< KPluginFactory > loadFactory(const KPluginMetaData &data)
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={})
bool isValid() const
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QStringList allResourceDirs()
static KSycoca * self()
void ensureCacheValid()
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags)
KCRASH_EXPORT void initialize()
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
char * data()
bool isEmpty() const const
int toInt(bool *ok, int base) const const
QCommandLineOption addHelpOption()
bool addOption(const QCommandLineOption &option)
QCommandLineOption addVersionOption()
bool isSet(const QCommandLineOption &option) const const
void process(const QCoreApplication &app)
QDBusConnectionInterface * interface() const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
QDBusReply< QDBusConnectionInterface::RegisterServiceReply > registerService(const QString &serviceName, ServiceQueueOptions qoption, ServiceReplacementOptions roption)
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
void addWatchedService(const QString &newService)
bool removeWatchedService(const QString &service)
void serviceUnregistered(const QString &serviceName)
QString homePath()
const_iterator cbegin() const const
const_iterator cend() const const
void clear()
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
QList< Key > keys() const const
bool remove(const Key &key)
T value(const Key &key) const const
void append(QList< T > &&value)
bool isEmpty() const const
qsizetype removeAll(const AT &t)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * sender() const const
int execute(const QString &program, const QStringList &arguments)
iterator insert(const T &value)
bool remove(const T &value)
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QStringList standardLocations(StandardLocation type)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void start()
void stop()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:03:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.