aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Edmundson <kde@davidedmundson.co.uk>2018-10-10 14:48:49 +0100
committerDavid Edmundson <kde@davidedmundson.co.uk>2018-10-10 14:51:04 +0100
commit8579ec54838b7188ed016f7adb4a69bbf2e39712 (patch)
treeb9614b014ba779dae40e96069fc2cadeeecea9de
parent8e56083463374fa6525b5feff4373b5ab58914bb (diff)
downloadkconfig-8579ec54838b7188ed016f7adb4a69bbf2e39712.tar.gz
kconfig-8579ec54838b7188ed016f7adb4a69bbf2e39712.tar.bz2
Add mechanism to notify other clients of config changes over DBus
Summary: Intention is not to create a registry like system, but to replace KDElibs4Support::KGlobalSettings and to replace other system settingss -> some app communication in a more generic way. writeEntry gains an additional flag Notify which if set, will notify clients of what has actually changed when we sync. Rationale to put this into KConfig was so that we could have everything batched and sychronised to the file sync and to get the fine detailed exposure of what has actually changed which we don't get with a file watcher. Default behaviour remains identical without any broadcast messages. As it is a new dependency it is purely optional and anything referencing DBus is not in the public API. Our deployment on platforms without DBus tend to be standalone applications anyway. Test Plan: Attached unit test Reviewers: broulik, dfaure Reviewed By: broulik, dfaure Subscribers: dfaure, broulik, zzag, kde-frameworks-devel Tags: #frameworks Differential Revision: https://phabricator.kde.org/D13034
-rw-r--r--CMakeLists.txt7
-rw-r--r--autotests/kconfigtest.cpp79
-rw-r--r--autotests/kconfigtest.h1
-rw-r--r--src/core/CMakeLists.txt10
-rw-r--r--src/core/config-kconfig.h.cmake1
-rw-r--r--src/core/kconfig.cpp55
-rw-r--r--src/core/kconfig_p.h2
-rw-r--r--src/core/kconfigbase.h14
-rw-r--r--src/core/kconfigdata.cpp7
-rw-r--r--src/core/kconfigdata.h7
-rw-r--r--src/core/kconfigwatcher.cpp107
-rw-r--r--src/core/kconfigwatcher.h68
12 files changed, 347 insertions, 11 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a2c900e9..216b7cae 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,6 +17,13 @@ option(KCONFIG_USE_GUI "Build components using Qt5Gui" ON)
if(KCONFIG_USE_GUI)
find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Gui)
endif()
+
+option(KCONFIG_USE_DBUS "Build components using Qt5DBus" ON)
+if(KCONFIG_USE_DBUS)
+ find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus)
+endif()
+
+
include(KDEInstallDirs)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings)
diff --git a/autotests/kconfigtest.cpp b/autotests/kconfigtest.cpp
index 35075d1a..01c08770 100644
--- a/autotests/kconfigtest.cpp
+++ b/autotests/kconfigtest.cpp
@@ -23,6 +23,8 @@
#include "kconfigtest.h"
#include "helper.h"
+#include "config-kconfig.h"
+
#include <QtTest>
#include <qtemporarydir.h>
#include <QStandardPaths>
@@ -30,6 +32,7 @@
#include <ksharedconfig.h>
#include <kconfiggroup.h>
+#include <kconfigwatcher.h>
#ifdef Q_OS_UNIX
#include <utime.h>
@@ -43,6 +46,8 @@ KCONFIGGROUP_DECLARE_FLAGS_QOBJECT(KConfigTest, Flags)
QTEST_MAIN(KConfigTest)
+Q_DECLARE_METATYPE(KConfigGroup)
+
static QString homePath()
{
#ifdef Q_OS_WIN
@@ -101,6 +106,7 @@ void KConfigTest::initTestCase()
{
// ensure we don't use files in the real config directory
QStandardPaths::setTestModeEnabled(true);
+ qRegisterMetaType<KConfigGroup>();
// to make sure all files from a previous failed run are deleted
cleanupTestCase();
@@ -1785,3 +1791,76 @@ void KConfigTest::testThreads()
f.waitForFinished();
}
}
+
+void KConfigTest::testNotify()
+{
+#if !KCONFIG_USE_DBUS
+ QSKIP("KConfig notification requires DBus")
+#endif
+
+ KConfig config(TEST_SUBDIR "kconfigtest");
+ auto myConfigGroup = KConfigGroup(&config, "TopLevelGroup");
+
+ //mimics a config in another process, which is watching for events
+ auto remoteConfig = KSharedConfig::openConfig(TEST_SUBDIR "kconfigtest");
+ KConfigWatcher::Ptr watcher = KConfigWatcher::create(remoteConfig);
+
+ //some random config that shouldn't be changing when kconfigtest changes, only on kdeglobals
+ auto otherRemoteConfig = KSharedConfig::openConfig(TEST_SUBDIR "kconfigtest2");
+ KConfigWatcher::Ptr otherWatcher = KConfigWatcher::create(otherRemoteConfig);
+
+ QSignalSpy watcherSpy(watcher.data(), &KConfigWatcher::configChanged);
+ QSignalSpy otherWatcherSpy(otherWatcher.data(), &KConfigWatcher::configChanged);
+
+ //write entries in a group and subgroup
+ myConfigGroup.writeEntry("entryA", "foo", KConfig::Persistent | KConfig::Notify);
+ auto subGroup = myConfigGroup.group("aSubGroup");
+ subGroup.writeEntry("entry1", "foo", KConfig::Persistent | KConfig::Notify);
+ subGroup.writeEntry("entry2", "foo", KConfig::Persistent | KConfig::Notify);
+ config.sync();
+ watcherSpy.wait();
+ QCOMPARE(watcherSpy.count(), 2);
+
+ std::sort(watcherSpy.begin(), watcherSpy.end(), [] (QList<QVariant> a, QList<QVariant> b) {
+ return a[0].value<KConfigGroup>().name() < b[0].value<KConfigGroup>().name();
+ });
+
+ QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), "TopLevelGroup");
+ QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entryA"}));
+
+ QCOMPARE(watcherSpy[1][0].value<KConfigGroup>().name(), "aSubGroup");
+ QCOMPARE(watcherSpy[1][0].value<KConfigGroup>().parent().name(), "TopLevelGroup");
+ QCOMPARE(watcherSpy[1][1].value<QByteArrayList>(), QByteArrayList({"entry1", "entry2"}));
+
+ //delete an entry
+ watcherSpy.clear();
+ myConfigGroup.deleteEntry("entryA", KConfig::Persistent | KConfig::Notify);
+ config.sync();
+ watcherSpy.wait();
+ QCOMPARE(watcherSpy.count(), 1);
+ QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), "TopLevelGroup");
+ QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entryA"}));
+
+ //deleting a group, should notify that every entry in that group has changed
+ watcherSpy.clear();
+ myConfigGroup.deleteGroup("aSubGroup", KConfig::Persistent | KConfig::Notify);
+ config.sync();
+ watcherSpy.wait();
+ QCOMPARE(watcherSpy.count(), 1);
+ QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), "aSubGroup");
+ QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"entry1", "entry2"}));
+
+ //global write still triggers our notification
+ watcherSpy.clear();
+ myConfigGroup.writeEntry("someGlobalEntry", "foo", KConfig::Persistent | KConfig::Notify | KConfig::Global);
+ config.sync();
+ watcherSpy.wait();
+ QCOMPARE(watcherSpy.count(), 1);
+ QCOMPARE(watcherSpy[0][0].value<KConfigGroup>().name(), "TopLevelGroup");
+ QCOMPARE(watcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"someGlobalEntry"}));
+
+ //watching another file should have only triggered from the kdeglobals change
+ QCOMPARE(otherWatcherSpy.count(), 1);
+ QCOMPARE(otherWatcherSpy[0][0].value<KConfigGroup>().name(), "TopLevelGroup");
+ QCOMPARE(otherWatcherSpy[0][1].value<QByteArrayList>(), QByteArrayList({"someGlobalEntry"}));
+}
diff --git a/autotests/kconfigtest.h b/autotests/kconfigtest.h
index 367e85de..708da042 100644
--- a/autotests/kconfigtest.h
+++ b/autotests/kconfigtest.h
@@ -78,6 +78,7 @@ private Q_SLOTS:
void testKdeGlobals();
void testNewlines();
void testXdgListEntry();
+ void testNotify();
void testThreads();
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index f06c803f..28aad4f8 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -10,8 +10,11 @@ set(libkconfigcore_SRCS
kcoreconfigskeleton.cpp
kauthorized.cpp
kemailsettings.cpp
+ kconfigwatcher.cpp
)
+configure_file(config-kconfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kconfig.h )
+
add_library(KF5ConfigCore ${libkconfigcore_SRCS})
generate_export_header(KF5ConfigCore BASE_NAME KConfigCore)
add_library(KF5::ConfigCore ALIAS KF5ConfigCore)
@@ -24,6 +27,11 @@ target_compile_definitions(KF5ConfigCore
target_include_directories(KF5ConfigCore INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/KConfigCore>")
target_link_libraries(KF5ConfigCore PUBLIC Qt5::Core)
+
+if(KCONFIG_USE_DBUS)
+ target_link_libraries(KF5ConfigCore PRIVATE Qt5::DBus)
+endif()
+
if(WIN32)
target_link_libraries(KF5ConfigCore PRIVATE ${KDEWIN_LIBRARIES})
endif()
@@ -44,6 +52,7 @@ ecm_generate_headers(KConfigCore_HEADERS
KCoreConfigSkeleton
KEMailSettings
ConversionCheck
+ KConfigWatcher
REQUIRED_HEADERS KConfigCore_HEADERS
)
@@ -68,6 +77,7 @@ if (PythonModuleGeneration_FOUND)
kcoreconfigskeleton.h
kemailsettings.h
conversioncheck.h
+ kconfigwatcher.h
)
endif()
diff --git a/src/core/config-kconfig.h.cmake b/src/core/config-kconfig.h.cmake
new file mode 100644
index 00000000..a4a7519d
--- /dev/null
+++ b/src/core/config-kconfig.h.cmake
@@ -0,0 +1 @@
+#cmakedefine01 KCONFIG_USE_DBUS
diff --git a/src/core/kconfig.cpp b/src/core/kconfig.cpp
index e4c9935b..df3ad471 100644
--- a/src/core/kconfig.cpp
+++ b/src/core/kconfig.cpp
@@ -23,6 +23,8 @@
#include "kconfig.h"
#include "kconfig_p.h"
+#include "config-kconfig.h"
+
#include <cstdlib>
#include <fcntl.h>
@@ -54,6 +56,12 @@ static inline int pclose(FILE *stream)
#include <QBasicMutex>
#include <QMutexLocker>
+#if KCONFIG_USE_DBUS
+#include <QDBusMessage>
+#include <QDBusConnection>
+#include <QDBusMetaType>
+#endif
+
bool KConfigPrivate::mappingsRegistered = false;
Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes.
@@ -424,6 +432,9 @@ bool KConfig::sync()
return false;
}
+ QHash<QString, QByteArrayList> notifyGroupsLocal;
+ QHash<QString, QByteArrayList> notifyGroupsGlobal;
+
if (d->bDirty && d->mBackend) {
const QByteArray utf8Locale(locale().toUtf8());
@@ -439,16 +450,20 @@ bool KConfig::sync()
// Rewrite global/local config only if there is a dirty entry in it.
bool writeGlobals = false;
bool writeLocals = false;
- Q_FOREACH (const KEntry &e, d->entryMap) {
+
+ for (auto it = d->entryMap.constBegin(); it != d->entryMap.constEnd(); ++it) {
+ auto e = it.value();
if (e.bDirty) {
if (e.bGlobal) {
writeGlobals = true;
+ if (e.bNotify) {
+ notifyGroupsGlobal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey;
+ }
} else {
writeLocals = true;
- }
-
- if (writeGlobals && writeLocals) {
- break;
+ if (e.bNotify) {
+ notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey;
+ }
}
}
}
@@ -485,9 +500,35 @@ bool KConfig::sync()
d->mBackend->unlock();
}
}
+
+ if (!notifyGroupsLocal.isEmpty()) {
+ d->notifyClients(notifyGroupsLocal, QStringLiteral("/") + name());
+ }
+ if (!notifyGroupsGlobal.isEmpty()) {
+ d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals"));
+ }
+
return !d->bDirty;
}
+void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path)
+{
+#if KCONFIG_USE_DBUS
+ qDBusRegisterMetaType<QByteArrayList>();
+
+ qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
+
+ QDBusMessage message = QDBusMessage::createSignal(path,
+ QStringLiteral("org.kde.kconfig.notify"),
+ QStringLiteral("ConfigChanged"));
+ message.setArguments({QVariant::fromValue(changes)});
+ QDBusConnection::sessionBus().send(message);
+#else
+ Q_UNUSED(changes)
+ Q_UNUSED(path)
+#endif
+}
+
void KConfig::markAsClean()
{
Q_D(KConfig);
@@ -497,6 +538,7 @@ void KConfig::markAsClean()
const KEntryMapIterator theEnd = d->entryMap.end();
for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) {
it->bDirty = false;
+ it->bNotify = false;
}
}
@@ -874,6 +916,9 @@ KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
if (flags & KConfig::Localized) {
options |= KEntryMap::EntryLocalized;
}
+ if (flags & KConfig::Notify) {
+ options |= KEntryMap::EntryNotify;
+ }
return options;
}
diff --git a/src/core/kconfig_p.h b/src/core/kconfig_p.h
index dfb3875a..7433ab2f 100644
--- a/src/core/kconfig_p.h
+++ b/src/core/kconfig_p.h
@@ -60,6 +60,8 @@ public:
QSet<QByteArray> allSubGroups(const QByteArray &parentGroup) const;
bool hasNonDeletedEntries(const QByteArray &group) const;
+ void notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path);
+
static QString expandString(const QString &value);
protected:
diff --git a/src/core/kconfigbase.h b/src/core/kconfigbase.h
index b7403de1..62a0cc31 100644
--- a/src/core/kconfigbase.h
+++ b/src/core/kconfigbase.h
@@ -58,13 +58,19 @@ public:
*/
Localized = 0x04,
/**<
+ * Notify remote KConfigWatchers of changes (requires DBus support)
+ * Implied persistent
+ * @since 5.51
+ */
+ Notify = 0x08 | Persistent,
+ /**<
* Add the locale tag to the key when writing it.
*/
Normal = Persistent
- /**<
- * Save the entry to the application specific config file without
- * a locale tag. This is the default.
- */
+ /**<
+ * Save the entry to the application specific config file without
+ * a locale tag. This is the default.
+ */
};
Q_DECLARE_FLAGS(WriteConfigFlags, WriteConfigFlag)
diff --git a/src/core/kconfigdata.cpp b/src/core/kconfigdata.cpp
index 6ef6af07..d80b7d07 100644
--- a/src/core/kconfigdata.cpp
+++ b/src/core/kconfigdata.cpp
@@ -134,6 +134,8 @@ bool KEntryMap::setEntry(const QByteArray &group, const QByteArray &key, const Q
e.mValue = value;
e.bDirty = e.bDirty || (options & EntryDirty);
+ e.bNotify = e.bNotify || (options & EntryNotify);
+
e.bGlobal = (options & EntryGlobal); //we can't use || here, because changes to entries in
//kdeglobals would be written to kdeglobals instead
//of the local config file, regardless of the globals flag
@@ -269,6 +271,8 @@ bool KEntryMap::getEntryOption(const QMap< KEntryKey, KEntry >::ConstIterator &i
return it->bDeleted;
case EntryExpansion:
return it->bExpand;
+ case EntryNotify:
+ return it->bNotify;
default:
break; // fall through
}
@@ -296,6 +300,9 @@ void KEntryMap::setEntryOption(QMap< KEntryKey, KEntry >::Iterator it, KEntryMap
case EntryExpansion:
it->bExpand = bf;
break;
+ case EntryNotify:
+ it->bNotify = bf;
+ break;
default:
break; // fall through
}
diff --git a/src/core/kconfigdata.h b/src/core/kconfigdata.h
index f84e51e3..2a5c643d 100644
--- a/src/core/kconfigdata.h
+++ b/src/core/kconfigdata.h
@@ -37,7 +37,7 @@ struct KEntry {
KEntry()
: mValue(), bDirty(false),
bGlobal(false), bImmutable(false), bDeleted(false), bExpand(false), bReverted(false),
- bLocalizedCountry(false) {}
+ bLocalizedCountry(false), bNotify(false) {}
/** @internal */
QByteArray mValue;
/**
@@ -69,11 +69,13 @@ struct KEntry {
* if @c true the value references language and country, e.g. "de_DE".
**/
bool bLocalizedCountry: 1;
+
+ bool bNotify: 1;
};
// These operators are used to check whether an entry which is about
// to be written equals the previous value. As such, this intentionally
-// omits the dirty flag from the comparison.
+// omits the dirty/notify flag from the comparison.
inline bool operator ==(const KEntry &k1, const KEntry &k2)
{
return k1.bGlobal == k2.bGlobal && k1.bImmutable == k2.bImmutable
@@ -172,6 +174,7 @@ public:
EntryExpansion = 16,
EntryRawKey = 32,
EntryLocalizedCountry = 64,
+ EntryNotify = 128,
EntryDefault = (SearchDefaults << 16),
EntryLocalized = (SearchLocalized << 16)
};
diff --git a/src/core/kconfigwatcher.cpp b/src/core/kconfigwatcher.cpp
new file mode 100644
index 00000000..96120c6a
--- /dev/null
+++ b/src/core/kconfigwatcher.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 David Edmundson <davidedmundson@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kconfigwatcher.h"
+
+#include "config-kconfig.h"
+
+#if KCONFIG_USE_DBUS
+#include <QDBusConnection>
+#include <QDBusMessage>
+#include <QDBusMetaType>
+#endif
+
+#include <QDebug>
+#include <QThreadStorage>
+#include <QHash>
+
+class KConfigWatcherPrivate {
+public:
+ KSharedConfig::Ptr m_config;
+};
+
+KConfigWatcher::Ptr KConfigWatcher::create(const KSharedConfig::Ptr &config)
+{
+ static QThreadStorage<QHash<KSharedConfig*, QWeakPointer<KConfigWatcher>>> watcherList;
+
+ auto c = config.data();
+ KConfigWatcher::Ptr watcher;
+
+ if (!watcherList.localData().contains(c)) {
+ watcher = KConfigWatcher::Ptr(new KConfigWatcher(config));
+
+ watcherList.localData().insert(c, watcher.toWeakRef());
+
+ QObject::connect(watcher.data(), &QObject::destroyed, [c]() {
+ watcherList.localData().remove(c);
+ });
+ }
+ return watcherList.localData().value(c).toStrongRef();
+}
+
+KConfigWatcher::KConfigWatcher(const KSharedConfig::Ptr &config):
+ QObject (nullptr),
+ d(new KConfigWatcherPrivate)
+{
+ Q_ASSERT(config);
+#if KCONFIG_USE_DBUS
+
+ qDBusRegisterMetaType<QByteArrayList>();
+ qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
+
+ d->m_config = config;
+
+ QStringList watchedPaths;
+ watchedPaths <<QStringLiteral("/") + d->m_config->name();
+ for (const QString file: d->m_config->additionalConfigSources()) {
+ watchedPaths << QStringLiteral("/") + file;
+ }
+ if (d->m_config->openFlags() & KConfig::IncludeGlobals) {
+ watchedPaths << QStringLiteral("/kdeglobals");
+ }
+
+ for(const QString &path: qAsConst(watchedPaths)) {
+ QDBusConnection::sessionBus().connect(QString(),
+ path,
+ QStringLiteral("org.kde.kconfig.notify"),
+ QStringLiteral("ConfigChanged"),
+ this,
+ SLOT(onConfigChangeNotification(QHash<QString, QByteArrayList>)));
+ }
+#else
+ qWarning() << "Use of KConfigWatcher without DBus support. You will not receive updates"
+#endif
+}
+
+void KConfigWatcher::onConfigChangeNotification(const QHash<QString, QByteArrayList> &changes)
+{
+ //should we ever need it we can determine the file changed with QDbusContext::message().path(), but it doesn't seem too useful
+
+ d->m_config->reparseConfiguration();
+
+ for(auto it = changes.constBegin(); it != changes.constEnd(); it++) {
+ KConfigGroup group = d->m_config->group(QString());//top level group
+ const auto parts = it.key().split(QLatin1Char('\x1d')); //magic char, see KConfig
+ for(const QString &groupName: parts) {
+ group = group.group(groupName);
+ }
+ emit configChanged(group, it.value());
+ }
+}
+
diff --git a/src/core/kconfigwatcher.h b/src/core/kconfigwatcher.h
new file mode 100644
index 00000000..b73d3fc2
--- /dev/null
+++ b/src/core/kconfigwatcher.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018 David Edmundson <davidedmundson@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KCONFIGWATCHER_H
+#define KCONFIGWATCHER_H
+
+#include <QObject>
+
+#include <KSharedConfig>
+#include <KConfigGroup>
+
+#include <kconfigcore_export.h>
+
+class KConfigWatcherPrivate;
+
+/*
+ * Notifies when another client has updated this config file with the Notify flag set.
+ * @since 5.51
+ */
+class KCONFIGCORE_EXPORT KConfigWatcher: public QObject
+{
+ Q_OBJECT
+public:
+ typedef QSharedPointer<KConfigWatcher> Ptr;
+
+ /*
+ * Instantiate a ConfigWatcher for a given config
+ *
+ * @note any additional config sources should be set before this point.
+ */
+ static Ptr create(const KSharedConfig::Ptr &config);
+
+Q_SIGNALS:
+ /**
+ * Emitted when a config group has changed
+ * The config will be reloaded before this signal is emitted
+ *
+ * @arg group the config group that has changed
+ * @arg names a list of entries that have changed within that group
+ */
+ void configChanged(const KConfigGroup &group, const QByteArrayList &names);
+
+private Q_SLOTS:
+ void onConfigChangeNotification(const QHash<QString, QByteArrayList> &changes);
+
+private:
+ KConfigWatcher(const KSharedConfig::Ptr &config);
+ Q_DISABLE_COPY(KConfigWatcher)
+ KConfigWatcherPrivate *const d;
+};
+
+#endif