Репозиторий Sisyphus
Последнее обновление: 1 октября 2023 | Пакетов: 18631 | Посещений: 37477750
en ru br
Репозитории ALT

Группа :: Система/Библиотеки
Пакет: kf5-kconfig

 Главная   Изменения   Спек   Патчи   Sources   Загрузить   Gear   Bugs and FR  Repocop 

Патч: alt-kconfig-notify-via-dbus.patch
Скачать


From a50d999d0741ec2ce57d513c949e39283b4561b7 Mon Sep 17 00:00:00 2001
From: Aleksei Nikiforov <darktemplar@basealt.ru>
Date: Fri, 14 Aug 2020 15:28:00 +0300
Subject: [PATCH] KConfig: notify on every configuration file change via DBUS
Also add utilities for listening to the broadcasted changes
and replaying changes.
---
 kconfig/src/CMakeLists.txt                  |   3 +
 kconfig/src/core/CMakeLists.txt             |   3 +
 kconfig/src/core/kconfig.cpp                |  26 +++
 kconfig/src/core/kconfig.h                  |   4 +
 kconfig/src/core/kconfig_p.h                |   1 +
 kconfig/src/core/kconfigbase.cpp            | 198 ++++++++++++++++
 kconfig/src/core/kconfigbase.h              |  23 ++
 kconfig/src/core/kconfiggroup.cpp           | 139 +++++++++++-
 kconfig/src/core/kconfiggroup.h             |   2 +
 kconfig/src/core/kconfigserialization.cpp   | 136 +++++++++++
 kconfig/src/core/kconfigserialization.h     |  24 ++
 kconfig/src/kconf_watcher/CMakeLists.txt    |  15 ++
 kconfig/src/kconf_watcher/kconf_apply.cpp   | 240 ++++++++++++++++++++
 kconfig/src/kconf_watcher/kconf_watcher.cpp | 135 +++++++++++
 kconfig/src/kconf_watcher/kconf_watcher_p.h |  18 ++
 15 files changed, 965 insertions(+), 2 deletions(-)
 create mode 100644 kconfig/src/core/kconfigserialization.cpp
 create mode 100644 kconfig/src/core/kconfigserialization.h
 create mode 100644 kconfig/src/kconf_watcher/CMakeLists.txt
 create mode 100644 kconfig/src/kconf_watcher/kconf_apply.cpp
 create mode 100644 kconfig/src/kconf_watcher/kconf_watcher.cpp
 create mode 100644 kconfig/src/kconf_watcher/kconf_watcher_p.h
diff --git a/kconfig/src/CMakeLists.txt b/kconfig/src/CMakeLists.txt
index 151c324..2f211df 100644
--- a/kconfig/src/CMakeLists.txt
+++ b/kconfig/src/CMakeLists.txt
@@ -4,6 +4,9 @@ if(TARGET Qt5::Gui)
 endif()
 add_subdirectory(kconfig_compiler)
 add_subdirectory(kconf_update)
+if(KCONFIG_USE_DBUS)
+    add_subdirectory(kconf_watcher)
+endif()
 add_subdirectory(kreadconfig)
 
 ecm_qt_install_logging_categories(
diff --git a/kconfig/src/core/CMakeLists.txt b/kconfig/src/core/CMakeLists.txt
index eb16d03..c80a400 100644
--- a/kconfig/src/core/CMakeLists.txt
+++ b/kconfig/src/core/CMakeLists.txt
@@ -11,6 +11,7 @@ set(libkconfigcore_SRCS
    kauthorized.cpp
    kemailsettings.cpp
    kconfigwatcher.cpp
+   kconfigserialization.cpp
 )
 
 ecm_qt_declare_logging_category(libkconfigcore_SRCS
@@ -65,6 +66,7 @@ ecm_generate_headers(KConfigCore_HEADERS
   KEMailSettings
   ConversionCheck
   KConfigWatcher
+  KConfigSerialization
 
   REQUIRED_HEADERS KConfigCore_HEADERS
 )
diff --git a/kconfig/src/core/kconfig.cpp b/kconfig/src/core/kconfig.cpp
index 7f53847..f4250e0 100644
--- a/kconfig/src/core/kconfig.cpp
+++ b/kconfig/src/core/kconfig.cpp
@@ -60,6 +60,7 @@ KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::Standar
     , bFileImmutable(false)
     , bForceGlobal(false)
     , bSuppressGlobal(false)
+    , bUpdateNotificationEnabled(true)
     , configState(KConfigBase::NoAccess)
 {
     const bool isTestMode = QStandardPaths::isTestModeEnabled();
@@ -886,6 +887,19 @@ KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
 }
 
 void KConfig::deleteGroupImpl(const QByteArray &aGroup, WriteConfigFlags flags)
+{
+    if (isUpdateNotificationEnabled())
+    {
+        sendNotification(nullptr, QByteArray(), QStringList(),
+            QStringList(QString::fromUtf8(aGroup)),
+            name(), locationType(), flags,
+            EntryNotificationType::DeleteGroup);
+    }
+
+    deleteGroupImplWithoutNotification(aGroup, flags);
+}
+
+void KConfig::deleteGroupImplWithoutNotification(const QByteArray &aGroup, WriteConfigFlags flags)
 {
     Q_D(KConfig);
     KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
@@ -1002,6 +1016,18 @@ QStandardPaths::StandardLocation KConfig::locationType() const
     return d->resourceType;
 }
 
+bool KConfig::isUpdateNotificationEnabledImpl() const
+{
+    Q_D(const KConfig);
+    return d->bUpdateNotificationEnabled;
+}
+
+void KConfig::setUpdateNotificationEnabledImpl(bool new_state)
+{
+    Q_D(KConfig);
+    d->bUpdateNotificationEnabled = new_state;
+}
+
 void KConfig::virtual_hook(int /*id*/, void * /*data*/)
 {
     /* nothing */
diff --git a/kconfig/src/core/kconfig.h b/kconfig/src/core/kconfig.h
index 792a9a6..7f6638d 100644
--- a/kconfig/src/core/kconfig.h
+++ b/kconfig/src/core/kconfig.h
@@ -370,6 +370,8 @@ protected:
     const KConfigGroup groupImpl(const QByteArray &b) const override;
     void deleteGroupImpl(const QByteArray &group, WriteConfigFlags flags = Normal) override;
     bool isGroupImmutableImpl(const QByteArray &aGroup) const override;
+    bool isUpdateNotificationEnabledImpl() const override;
+    void setUpdateNotificationEnabledImpl(bool new_state) override;
 
     friend class KConfigGroup;
     friend class KConfigGroupPrivate;
@@ -384,6 +386,8 @@ protected:
 
     KConfig(KConfigPrivate &d);
 
+    void deleteGroupImplWithoutNotification(const QByteArray &group, WriteConfigFlags flags = Normal);
+
 private:
     friend class KConfigTest;
 
diff --git a/kconfig/src/core/kconfig_p.h b/kconfig/src/core/kconfig_p.h
index b201df1..b013775 100644
--- a/kconfig/src/core/kconfig_p.h
+++ b/kconfig/src/core/kconfig_p.h
@@ -65,6 +65,7 @@ private:
     bool bFileImmutable : 1;
     bool bForceGlobal : 1;
     bool bSuppressGlobal : 1;
+    bool bUpdateNotificationEnabled : 1;
 
     static bool mappingsRegistered;
 
diff --git a/kconfig/src/core/kconfigbase.cpp b/kconfig/src/core/kconfigbase.cpp
index 9eee642..169c2a8 100644
--- a/kconfig/src/core/kconfigbase.cpp
+++ b/kconfig/src/core/kconfigbase.cpp
@@ -9,10 +9,27 @@
 
 #include "kconfigbase.h"
 
+#include "config-kconfig.h"
 #include "kconfiggroup.h"
 
 #include <QString>
 
+#if KCONFIG_USE_DBUS
+#include <QDBusConnection>
+#include <QDBusMessage>
+#include <QStringList>
+#include <QVector>
+#include <QDebug>
+#include <QMutex>
+#include <QMutexLocker>
+#include <unistd.h>
+#include <pwd.h>
+#include "kconfig.h"
+#include "kconfigserialization.h"
+#endif // KCONFIG_USE_DBUS
+
+bool KConfigBase::s_notification_warn_once = true;
+
 bool KConfigBase::hasGroup(const QString &group) const
 {
     return hasGroupImpl(group.toUtf8());
@@ -88,6 +105,16 @@ bool KConfigBase::isGroupImmutable(const char *aGroup) const
     return isGroupImmutableImpl(QByteArray(aGroup));
 }
 
+bool KConfigBase::isUpdateNotificationEnabled() const
+{
+    return isUpdateNotificationEnabledImpl();
+}
+
+void KConfigBase::setUpdateNotificationEnabled(bool new_state)
+{
+    setUpdateNotificationEnabledImpl(new_state);
+}
+
 KConfigBase::~KConfigBase()
 {
 }
@@ -99,3 +126,174 @@ KConfigBase::KConfigBase()
 void KConfigBase::virtual_hook(int, void *)
 {
 }
+
+void KConfigBase::sendNotification(const char *key, const QByteArray &value,
+    const QStringList &entry_paths, const QStringList &groups,
+    const QString &filename, QStandardPaths::StandardLocation location_type,
+    WriteConfigFlags flags, EntryNotificationType notification_type)
+{
+#if KCONFIG_USE_DBUS
+    {
+        static QMutex s_globalsInitMutex;
+        static bool s_globalsInitialized = false;
+        static bool s_notificationsGloballyEnabled;
+
+        { // lock
+            QMutexLocker globalsInitMutexLock(&s_globalsInitMutex);
+
+            if (!s_globalsInitialized)
+            {
+                KConfig globals_config(QStringLiteral("kdeglobals"), KConfig::NoGlobals);
+                KConfigGroup alt_group = globals_config.group(QStringLiteral("ALT"));
+                s_notificationsGloballyEnabled = alt_group.readEntry(QStringLiteral("EnableDbusNotifications"), false);
+                s_globalsInitialized = true;
+            }
+        } // unlock
+
+        if (!s_notificationsGloballyEnabled)
+        {
+            return;
+        }
+    }
+
+    if (!QDBusConnection::sessionBus().isConnected())
+    {
+        if (s_notification_warn_once)
+        {
+            qDebug() << QStringLiteral(
+                "Cannot connect to the D-Bus session bus.\n"
+                "Please check your system settings and try again.\n");
+            s_notification_warn_once = false;
+        }
+
+        return;
+    }
+
+    QStringList message_values_list;
+
+    message_values_list << QStringLiteral("version=1");
+
+    switch (notification_type)
+    {
+    case EntryNotificationType::Entry:
+        message_values_list << QStringLiteral("event=write_entry");
+        break;
+
+    case EntryNotificationType::PathEntry:
+        message_values_list << QStringLiteral("event=write_path_entry");
+        break;
+
+    case EntryNotificationType::PathArrayEntry:
+        message_values_list << QStringLiteral("event=write_path_array_entry");
+        break;
+
+    case EntryNotificationType::DeleteEntry:
+        message_values_list << QStringLiteral("event=delete_entry");
+        break;
+
+    case EntryNotificationType::DeleteGroup:
+        message_values_list << QStringLiteral("event=delete_group");
+        break;
+    }
+
+    {
+        qint64 initlen = sysconf(_SC_GETPW_R_SIZE_MAX);
+
+        struct passwd result;
+        struct passwd *resultp;
+
+        QVector<char> buffer;
+        buffer.resize((initlen == -1) ? 1024 : initlen);
+
+        uid_t uid = geteuid();
+
+        int e;
+
+        while ((e = getpwuid_r(uid, &result, buffer.data(), buffer.size(), &resultp)) == ERANGE)
+        {
+            buffer.resize(buffer.size() * 2);
+        }
+
+        if (e != 0)
+        {
+            qDebug() << QStringLiteral("Failed to serialize config entry: getpwuid_r function failed");
+            return;
+        }
+
+        if (resultp != nullptr)
+        {
+            message_values_list << QStringLiteral("username=%1").arg(QString::fromLocal8Bit(resultp->pw_name));
+        }
+
+        message_values_list << QStringLiteral("uid=%1").arg(uid);
+    }
+
+    message_values_list << QStringLiteral("filename=%1").arg(filename);
+
+    {
+        bool ok = false;
+        QString location_type_string = serializeStandardLocation(location_type, ok);
+        if (!ok)
+        {
+            qDebug() << QStringLiteral("Failed to serialize config entry: failed to serialize QStandardPaths::StandardLocation enum");
+            return;
+        }
+        message_values_list << QStringLiteral("location_type=%1").arg(location_type_string);
+    }
+
+    {
+        bool ok = false;
+        QString flags_string = serializeWriteConfigFlags(flags, ok);
+        if (!ok)
+        {
+            qDebug() << QStringLiteral("Failed to serialize config entry: failed to serialize KConfigBase::WriteConfigFlags");
+            return;
+        }
+
+        message_values_list << QStringLiteral("flags=%1").arg(flags_string);
+    }
+
+    message_values_list << QStringLiteral("group_name=%1").arg(groups.join(QLatin1Char('|')));
+
+    switch (notification_type)
+    {
+    case EntryNotificationType::Entry:
+    case EntryNotificationType::PathEntry:
+    case EntryNotificationType::PathArrayEntry:
+    case EntryNotificationType::DeleteEntry:
+        message_values_list << QStringLiteral("entry_name=%1").arg(QString::fromLocal8Bit(key));
+        break;
+
+    default:
+        break;
+    }
+
+    switch (notification_type)
+    {
+    case EntryNotificationType::Entry:
+    case EntryNotificationType::PathEntry:
+        message_values_list << QStringLiteral("entry_value=%1").arg(QString::fromLocal8Bit(value.isNull() ? QByteArray("") : value));
+        break;
+
+    default:
+        break;
+    }
+
+    if (notification_type == EntryNotificationType::PathArrayEntry)
+    {
+        int idx = 1;
+        auto iter = entry_paths.begin();
+        for (; iter != entry_paths.end(); ++iter, ++idx)
+        {
+            message_values_list << QStringLiteral("entry_path_%1=%2").arg(idx).arg(*iter);
+        }
+    }
+
+    QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/"),
+        QStringLiteral("org.alt.kde.kconfig.extra.notify"),
+        QStringLiteral("ConfigChanged"));
+
+    message << message_values_list;
+    QDBusConnection::sessionBus().send(message);
+#endif
+}
diff --git a/kconfig/src/core/kconfigbase.h b/kconfig/src/core/kconfigbase.h
index 79af885..54642d4 100644
--- a/kconfig/src/core/kconfigbase.h
+++ b/kconfig/src/core/kconfigbase.h
@@ -15,6 +15,7 @@
 
 #include <QStringList>
 #include <QtGlobal>
+#include <QStandardPaths>
 
 class KConfigGroup;
 class KConfigBasePrivate;
@@ -218,6 +219,9 @@ public:
      */
     bool isGroupImmutable(const char *group) const;
 
+    bool isUpdateNotificationEnabled() const;
+    void setUpdateNotificationEnabled(bool new_state);
+
 protected:
     KConfigBase();
 
@@ -232,10 +236,29 @@ protected:
     /// @param group name of group, encoded in UTF-8
     virtual bool isGroupImmutableImpl(const QByteArray &group) const = 0;
 
+    virtual bool isUpdateNotificationEnabledImpl() const = 0;
+    virtual void setUpdateNotificationEnabledImpl(bool new_state) = 0;
+
     /** Virtual hook, used to add new "virtual" functions while maintaining
      * binary compatibility. Unused in this class.
      */
     virtual void virtual_hook(int id, void *data);
+
+    enum class EntryNotificationType
+    {
+        Entry,
+        PathEntry,
+        PathArrayEntry,
+        DeleteEntry,
+        DeleteGroup,
+    };
+
+    void sendNotification(const char *pKey, const QByteArray &value,
+        const QStringList &entry_paths, const QStringList &groups,
+        const QString &filename, QStandardPaths::StandardLocation location_type,
+        WriteConfigFlags pFlags, EntryNotificationType notification_type);
+
+    static bool s_notification_warn_once;
 };
 
 Q_DECLARE_OPERATORS_FOR_FLAGS(KConfigBase::WriteConfigFlags)
diff --git a/kconfig/src/core/kconfiggroup.cpp b/kconfig/src/core/kconfiggroup.cpp
--- a/kconfig/src/core/kconfiggroup.cpp
+++ b/kconfig/src/core/kconfiggroup.cpp
@@ -16,6 +16,7 @@
 #include "kconfigdata_p.h"
 #include "ksharedconfig.h"
 
+#include <QStringList>
 #include <QDate>
 #include <QDir>
 #include <QFile>
@@ -40,6 +41,7 @@ public:
         , mName(name)
         , bImmutable(isImmutable)
         , bConst(isConst)
+        , bUpdateNotificationEnabled(owner->isUpdateNotificationEnabled())
     {
     }
 
@@ -49,6 +51,7 @@ public:
         , mName(name)
         , bImmutable(name.isEmpty() ? owner->isImmutable() : owner->isGroupImmutable(name))
         , bConst(false)
+        , bUpdateNotificationEnabled(owner->isUpdateNotificationEnabled())
     {
     }
 
@@ -58,6 +61,7 @@ public:
         , mName(name)
         , bImmutable(isImmutable)
         , bConst(isConst)
+        , bUpdateNotificationEnabled(parent->isUpdateNotificationEnabled())
     {
         if (!parent->d->mName.isEmpty()) {
             mParent = parent->d;
@@ -70,6 +74,7 @@ public:
         , mName(name)
         , bImmutable(isImmutable)
         , bConst(other->bConst)
+        , bUpdateNotificationEnabled(other->bUpdateNotificationEnabled)
     {
         if (!other->mName.isEmpty()) {
             mParent = const_cast<KConfigGroupPrivate *>(other);
@@ -84,6 +89,7 @@ public:
     /* bitfield */
     const bool bImmutable : 1; // is this group immutable?
     const bool bConst : 1; // is this group read-only?
+    bool bUpdateNotificationEnabled: 1;
 
     QByteArray fullName() const
     {
@@ -109,6 +115,24 @@ public:
         return fullName() + '\x1d' + aGroup;
     }
 
+    QStringList fullNameList() const
+    {
+        if (!mParent) {
+            return QStringList(QString::fromUtf8(name()));
+        }
+        return mParent->fullNameList(mName);
+    }
+
+    QStringList fullNameList(const QByteArray &aGroup) const
+    {
+        if (mName.isEmpty()) {
+            return QStringList(QString::fromUtf8(aGroup));
+        }
+        QStringList result = fullNameList();
+        result.append(QString::fromUtf8(aGroup));
+        return result;
+    }
+
     static QExplicitlySharedDataPointer<KConfigGroupPrivate> create(KConfigBase *master, const QByteArray &name, bool isImmutable, bool isConst)
     {
         QExplicitlySharedDataPointer<KConfigGroupPrivate> data;
@@ -470,6 +494,7 @@ static inline bool writeEntryGui(KConfig
 KConfigGroup::KConfigGroup(KConfigBase *master, const QString &_group)
     : d(KConfigGroupPrivate::create(master, _group.toUtf8(), master->isGroupImmutable(_group), false))
 {
+
 }
 
 KConfigGroup::KConfigGroup(KConfigBase *master, const char *_group)
@@ -559,7 +584,23 @@ void KConfigGroup::deleteGroup(WriteConf
     Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroup", "accessing an invalid group");
     Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroup", "deleting a read-only group");
 
-    config()->deleteGroup(d->fullName(), flags);
+    if (isUpdateNotificationEnabled())
+    {
+        KConfig *parent_config = config();
+        if (parent_config != nullptr)
+        {
+            sendNotification(nullptr, QByteArray(""), QStringList(),
+                d->fullNameList(),
+                parent_config->name(), parent_config->locationType(), flags,
+                EntryNotificationType::DeleteGroup);
+        }
+        else
+        {
+            qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found");
+        }
+    }
+
+    config()->deleteGroupImplWithoutNotification(d->fullName(), flags);
 }
 
 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(5, 0)
@@ -859,6 +900,22 @@ void KConfigGroup::writeEntry(const char
     Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group");
     Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group");
 
+    if (isUpdateNotificationEnabled())
+    {
+        KConfig *parent_config = config();
+        if (parent_config != nullptr)
+        {
+            sendNotification(key, value.isNull() ? QByteArray("") : value, QStringList(),
+                d->fullNameList(),
+                parent_config->name(), parent_config->locationType(), flags,
+                EntryNotificationType::Entry);
+        }
+        else
+        {
+            qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found");
+        }
+    }
+
     config()->d_func()->putData(d->fullName(), key, value.isNull() ? QByteArray("") : value, flags);
 }
 
@@ -1085,6 +1142,22 @@ void KConfigGroup::writePathEntry(const
     Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group");
     Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group");
 
+    if (isUpdateNotificationEnabled())
+    {
+        KConfig *parent_config = config();
+        if (parent_config != nullptr)
+        {
+            sendNotification(pKey, path.toLocal8Bit(), QStringList(),
+                d->fullNameList(),
+                parent_config->name(), parent_config->locationType(), pFlags,
+                EntryNotificationType::PathEntry);
+        }
+        else
+        {
+            qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found");
+        }
+    }
+
     config()->d_func()->putData(d->fullName(), pKey, translatePath(path).toUtf8(), pFlags, true);
 }
 
@@ -1104,6 +1177,22 @@ void KConfigGroup::writePathEntry(const
         list << translatePath(path).toUtf8();
     }
 
+    if (isUpdateNotificationEnabled())
+    {
+        KConfig *parent_config = config();
+        if (parent_config != nullptr)
+        {
+            sendNotification(pKey, QByteArray(), value,
+                d->fullNameList(),
+                parent_config->name(), parent_config->locationType(), pFlags,
+                EntryNotificationType::PathArrayEntry);
+        }
+        else
+        {
+            qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found");
+        }
+    }
+
     config()->d_func()->putData(d->fullName(), pKey, KConfigGroupPrivate::serializeList(list), pFlags, true);
 }
 
@@ -1112,6 +1201,22 @@ void KConfigGroup::deleteEntry(const cha
     Q_ASSERT_X(isValid(), "KConfigGroup::deleteEntry", "accessing an invalid group");
     Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteEntry", "deleting from a read-only group");
 
+    if (isUpdateNotificationEnabled())
+    {
+        KConfig *parent_config = config();
+        if (parent_config != nullptr)
+        {
+            sendNotification(key, QByteArray(), QStringList(),
+                d->fullNameList(),
+                parent_config->name(), parent_config->locationType(), flags,
+                EntryNotificationType::DeleteEntry);
+        }
+        else
+        {
+            qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found");
+        }
+    }
+
     config()->d_func()->putData(d->fullName(), key, QByteArray(), flags);
 }
 
@@ -1221,7 +1326,23 @@ void KConfigGroup::deleteGroupImpl(const
     Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroupImpl", "accessing an invalid group");
     Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroupImpl", "deleting from a read-only group");
 
-    config()->deleteGroup(d->fullName(b), flags);
+    if (isUpdateNotificationEnabled())
+    {
+        KConfig *parent_config = config();
+        if (parent_config != nullptr)
+        {
+            sendNotification(nullptr, QByteArray(), QStringList(),
+                d->fullNameList(b),
+                parent_config->name(), parent_config->locationType(), flags,
+                EntryNotificationType::DeleteGroup);
+        }
+        else
+        {
+            qDebug() << QStringLiteral("Failed to send notification: no parent KConfig is found");
+        }
+    }
+
+    config()->deleteGroupImplWithoutNotification(d->fullName(b), flags);
 }
 
 bool KConfigGroup::isGroupImmutableImpl(const QByteArray &b) const
@@ -1235,6 +1356,20 @@ bool KConfigGroup::isGroupImmutableImpl(
     return config()->isGroupImmutable(d->fullName(b));
 }
 
+bool KConfigGroup::isUpdateNotificationEnabledImpl() const
+{
+    Q_ASSERT_X(isValid(), "KConfigGroup::isUpdateNotificationEnabledImpl", "accessing an invalid group");
+
+    return d->bUpdateNotificationEnabled;
+}
+
+void KConfigGroup::setUpdateNotificationEnabledImpl(bool new_state)
+{
+    Q_ASSERT_X(isValid(), "KConfigGroup::setUpdateNotificationEnabledImpl", "accessing an invalid group");
+
+    d->bUpdateNotificationEnabled = new_state;
+}
+
 void KConfigGroup::copyTo(KConfigBase *other, WriteConfigFlags pFlags) const
 {
     Q_ASSERT_X(isValid(), "KConfigGroup::copyTo", "accessing an invalid group");
diff --git a/kconfig/src/core/kconfiggroup.h b/kconfig/src/core/kconfiggroup.h
index 5c125dd..000cbdb 100644
--- a/kconfig/src/core/kconfiggroup.h
+++ b/kconfig/src/core/kconfiggroup.h
@@ -705,6 +705,8 @@ protected:
     const KConfigGroup groupImpl(const QByteArray &b) const override;
     void deleteGroupImpl(const QByteArray &group, WriteConfigFlags flags) override;
     bool isGroupImmutableImpl(const QByteArray &aGroup) const override;
+    bool isUpdateNotificationEnabledImpl() const override;
+    void setUpdateNotificationEnabledImpl(bool new_state) override;
 
 private:
     QExplicitlySharedDataPointer<KConfigGroupPrivate> d;
diff --git a/kconfig/src/core/kconfigserialization.cpp b/kconfig/src/core/kconfigserialization.cpp
new file mode 100644
index 0000000..299ebf7
--- /dev/null
+++ b/kconfig/src/core/kconfigserialization.cpp
@@ -0,0 +1,136 @@
+/*
+    This file is part of the KDE libraries
+    SPDX-FileCopyrightText: 2020 Aleksei Nikiforov <darktemplar@basealt.ru>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "kconfigserialization.h"
+
+#include <QChar>
+#include <QStringList>
+#include <QMap>
+#include <QMutex>
+#include <QMutexLocker>
+
+static const QMap<QStandardPaths::StandardLocation, QString> s_standardLocationEnumValues =
+{
+    { QStandardPaths::DesktopLocation,       QStringLiteral("DesktopLocation") },
+    { QStandardPaths::DocumentsLocation,     QStringLiteral("DocumentsLocation") },
+    { QStandardPaths::FontsLocation,         QStringLiteral("FontsLocation") },
+    { QStandardPaths::ApplicationsLocation,  QStringLiteral("ApplicationsLocation") },
+    { QStandardPaths::MusicLocation,         QStringLiteral("MusicLocation") },
+    { QStandardPaths::MoviesLocation,        QStringLiteral("MoviesLocation") },
+    { QStandardPaths::PicturesLocation,      QStringLiteral("PicturesLocation") },
+    { QStandardPaths::TempLocation,          QStringLiteral("TempLocation") },
+    { QStandardPaths::HomeLocation,          QStringLiteral("HomeLocation") },
+    { QStandardPaths::AppLocalDataLocation,  QStringLiteral("AppLocalDataLocation") },
+    { QStandardPaths::CacheLocation,         QStringLiteral("CacheLocation") },
+    { QStandardPaths::GenericDataLocation,   QStringLiteral("GenericDataLocation") },
+    { QStandardPaths::RuntimeLocation,       QStringLiteral("RuntimeLocation") },
+    { QStandardPaths::ConfigLocation,        QStringLiteral("ConfigLocation") },
+    { QStandardPaths::DownloadLocation,      QStringLiteral("DownloadLocation") },
+    { QStandardPaths::GenericCacheLocation,  QStringLiteral("GenericCacheLocation") },
+    { QStandardPaths::GenericConfigLocation, QStringLiteral("GenericConfigLocation") },
+    { QStandardPaths::AppDataLocation,       QStringLiteral("AppDataLocation") },
+    { QStandardPaths::AppConfigLocation,     QStringLiteral("AppConfigLocation") },
+};
+
+static const QMap<QString, KConfigBase::WriteConfigFlag> s_writeConfigFlagsEnumValues =
+{
+    { QStringLiteral("Persistent"), KConfigBase::Persistent },
+    { QStringLiteral("Global"),     KConfigBase::Global },
+    { QStringLiteral("Localized"),  KConfigBase::Localized },
+    { QStringLiteral("Notify"),     KConfigBase::Notify },
+    { QStringLiteral("Normal"),     KConfigBase::Normal },
+};
+
+QString serializeStandardLocation(QStandardPaths::StandardLocation value, bool &ok)
+{
+    auto iter = s_standardLocationEnumValues.find(value);
+    if (iter != s_standardLocationEnumValues.end())
+    {
+        ok = true;
+        return iter.value();
+    }
+    else
+    {
+        ok = false;
+        return QString();
+    }
+}
+
+QStandardPaths::StandardLocation deserializeStandardLocation(const QString &value, bool &ok)
+{
+    static QMap<QString, QStandardPaths::StandardLocation> s_standardLocationEnumDeserializationMap;
+    static QMutex s_standardLocationEnumDeserializationMapMutex;
+
+    { // lock
+        QMutexLocker mapLock(&s_standardLocationEnumDeserializationMapMutex);
+
+        if (s_standardLocationEnumDeserializationMap.isEmpty())
+        {
+            for (auto iter = s_standardLocationEnumValues.begin(); iter != s_standardLocationEnumValues.end(); ++iter)
+            {
+                s_standardLocationEnumDeserializationMap[iter.value()] = iter.key();
+            }
+        }
+    } // unlock
+
+    auto iter = s_standardLocationEnumDeserializationMap.find(value);
+    if (iter != s_standardLocationEnumDeserializationMap.end())
+    {
+        ok = true;
+        return iter.value();
+    }
+    else
+    {
+        ok = false;
+        return QStandardPaths::StandardLocation();
+    }
+}
+
+QString serializeWriteConfigFlags(const KConfigBase::WriteConfigFlags &value, bool &ok)
+{
+    QStringList result;
+    KConfigBase::WriteConfigFlags reconstructed_value;
+
+    for (auto values_iter = s_writeConfigFlagsEnumValues.begin(); values_iter != s_writeConfigFlagsEnumValues.end(); ++values_iter)
+    {
+        if (value.testFlag(values_iter.value()))
+        {
+            result.push_back(values_iter.key());
+            reconstructed_value |= values_iter.value();
+        }
+    }
+
+    ok = (value == reconstructed_value);
+
+    return result.join(QChar::fromLatin1('|'));;
+}
+
+KConfigBase::WriteConfigFlags deserializeWriteConfigFlags(const QString &value, bool &ok)
+{
+    KConfigBase::WriteConfigFlags result;
+
+    QStringList values_list = value.split(QChar::fromLatin1('|'));
+
+    bool no_unknown_values = true;
+
+    for (const auto &value_iter: values_list)
+    {
+        auto map_iter = s_writeConfigFlagsEnumValues.find(value_iter);
+        if (map_iter != s_writeConfigFlagsEnumValues.end())
+        {
+            result |= map_iter.value();
+        }
+        else
+        {
+            no_unknown_values = false;
+        }
+    }
+
+    ok = no_unknown_values;
+
+    return result;
+}
diff --git a/kconfig/src/core/kconfigserialization.h b/kconfig/src/core/kconfigserialization.h
new file mode 100644
index 0000000..52a6da0
--- /dev/null
+++ b/kconfig/src/core/kconfigserialization.h
@@ -0,0 +1,24 @@
+/*
+    This file is part of the KDE libraries
+    SPDX-FileCopyrightText: 2020 Aleksei Nikiforov <darktemplar@basealt.ru>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KCONFIGSERIALIZATION_H
+#define KCONFIGSERIALIZATION_H
+
+#include "kconfigbase.h"
+
+#include <kconfigcore_export.h>
+
+#include <QStandardPaths>
+#include <QString>
+
+KCONFIGCORE_EXPORT QString serializeStandardLocation(QStandardPaths::StandardLocation value, bool &ok);
+KCONFIGCORE_EXPORT QStandardPaths::StandardLocation deserializeStandardLocation(const QString &value, bool &ok);
+
+KCONFIGCORE_EXPORT QString serializeWriteConfigFlags(const KConfigBase::WriteConfigFlags &value, bool &ok);
+KCONFIGCORE_EXPORT KConfigBase::WriteConfigFlags deserializeWriteConfigFlags(const QString &value, bool &ok);
+
+#endif // KCONFIGSERIALIZATION_H
diff --git a/kconfig/src/kconf_watcher/CMakeLists.txt b/kconfig/src/kconf_watcher/CMakeLists.txt
new file mode 100644
index 0000000..2d1e5fe
--- /dev/null
+++ b/kconfig/src/kconf_watcher/CMakeLists.txt
@@ -0,0 +1,15 @@
+include(ECMMarkNonGuiExecutable)
+
+add_executable(kconf_watcher kconf_watcher.cpp)
+target_link_libraries(kconf_watcher Qt5::Core Qt5::DBus)
+ecm_mark_nongui_executable(kconf_watcher)
+
+install(TARGETS kconf_watcher ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
+
+########### next target ###############
+
+add_executable(kconf_apply kconf_apply.cpp)
+target_link_libraries(kconf_apply Qt5::Core KF5::ConfigCore)
+ecm_mark_nongui_executable(kconf_apply)
+
+install(TARGETS kconf_apply ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/kconfig/src/kconf_watcher/kconf_apply.cpp b/kconfig/src/kconf_watcher/kconf_apply.cpp
new file mode 100644
index 0000000..0684640
--- /dev/null
+++ b/kconfig/src/kconf_watcher/kconf_apply.cpp
@@ -0,0 +1,240 @@
+/*
+    This file is part of the KDE libraries
+    SPDX-FileCopyrightText: 2020 Aleksei Nikiforov <darktemplar@basealt.ru>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include <QCoreApplication>
+#include <QTextStream>
+#include <QString>
+#include <QMap>
+#include <QLatin1Char>
+
+#include <KConfigSerialization>
+#include <KConfig>
+#include <KConfigGroup>
+
+enum class event_type
+{
+    write_entry = 0,
+    write_path_entry,
+    write_path_array_entry,
+    delete_entry,
+    delete_group,
+};
+
+static const QMap<QString, event_type> string_to_event_type_map =
+{
+    { QStringLiteral("write_entry"),            event_type::write_entry },
+    { QStringLiteral("write_path_entry"),       event_type::write_path_entry },
+    { QStringLiteral("write_path_array_entry"), event_type::write_path_array_entry },
+    { QStringLiteral("delete_entry"),           event_type::delete_entry },
+    { QStringLiteral("delete_group"),           event_type::delete_group },
+};
+
+int main(int argc, char **argv)
+{
+    QCoreApplication app(argc, argv);
+    app.setApplicationVersion(QStringLiteral("1.0"));
+
+    QTextStream stdin_stream(stdin, QIODevice::ReadOnly);
+
+    bool in_message = false;
+    QMap<QString, QString> message_data;
+
+    while (!stdin_stream.atEnd())
+    {
+        QString line = stdin_stream.readLine();
+
+        if (in_message)
+        {
+            if (line == QStringLiteral("message_end"))
+            {
+                do
+                {
+                    if (message_data[QStringLiteral("version")] != QStringLiteral("1"))
+                    {
+                        break;
+                    }
+
+                    event_type event_type_value;
+                    QString filename;
+                    QStandardPaths::StandardLocation standard_location;
+                    KConfigBase::WriteConfigFlags write_config_flags;
+                    QStringList group_names;
+                    QString entry_name;
+                    QString entry_value;
+
+                    // TODO: process uid/username, use it for optional filtering
+
+                    {
+                        auto iter = string_to_event_type_map.find(message_data[QStringLiteral("event")]);
+                        if (iter == string_to_event_type_map.end())
+                        {
+                            break;
+                        }
+
+                        event_type_value = iter.value();
+                    }
+
+                    {
+                        auto iter = message_data.find(QStringLiteral("filename"));
+                        if (iter == message_data.end())
+                        {
+                            break;
+                        }
+
+                        filename = iter.value();
+                    }
+
+                    {
+                        auto iter = message_data.find(QStringLiteral("location_type"));;
+                        if (iter == message_data.end())
+                        {
+                            break;
+                        }
+
+                        bool ok = false;
+                        standard_location = deserializeStandardLocation(iter.value(), ok);
+                        if (!ok)
+                        {
+                            break;
+                        }
+                    }
+
+                    {
+                        auto iter = message_data.find(QStringLiteral("flags"));;
+                        if (iter == message_data.end())
+                        {
+                            break;
+                        }
+
+                        bool ok = false;
+                        write_config_flags = deserializeWriteConfigFlags(iter.value(), ok);
+                        if (!ok)
+                        {
+                            break;
+                        }
+                    }
+
+                    {
+                        auto iter = message_data.find(QStringLiteral("group_name"));;
+                        if (iter == message_data.end())
+                        {
+                            break;
+                        }
+
+                        group_names = iter.value().split(QLatin1Char('|'));
+                        if (group_names.isEmpty())
+                        {
+                            break;
+                        }
+                    }
+
+                    {
+                        auto iter = message_data.find(QStringLiteral("entry_name"));;
+                        if ((iter == message_data.end())
+                            && ((event_type_value == event_type::write_entry)
+                                || (event_type_value == event_type::write_path_entry)
+                                || (event_type_value == event_type::write_path_array_entry)
+                                || (event_type_value == event_type::delete_entry)))
+                        {
+                            break;
+                        }
+
+                        if (iter != message_data.end())
+                        {
+                            entry_name = iter.value();
+                        }
+                    }
+
+                    {
+                        auto iter = message_data.find(QStringLiteral("entry_value"));;
+                        if ((iter == message_data.end())
+                            && ((event_type_value == event_type::write_entry)
+                                || (event_type_value == event_type::write_path_entry)))
+                        {
+                            break;
+                        }
+
+                        if (iter != message_data.end())
+                        {
+                            entry_value = iter.value();
+                        }
+                    }
+
+                    KConfig config(filename, KConfig::FullConfig, standard_location);
+                    config.setUpdateNotificationEnabled(false);
+
+                    auto group_name_iter = group_names.begin();
+                    KConfigGroup config_group = config.group(*group_name_iter);
+
+                    for (++group_name_iter; group_name_iter != group_names.end(); ++group_name_iter)
+                    {
+                        config_group = config_group.group(*group_name_iter);
+                    }
+
+                    switch (event_type_value)
+                    {
+                    case event_type::write_entry:
+                        config_group.writeEntry(entry_name, entry_value, write_config_flags);
+                        break;
+
+                    case event_type::write_path_entry:
+                        config_group.writePathEntry(entry_name, entry_value, write_config_flags);
+                        break;
+
+                    case event_type::write_path_array_entry:
+                        {
+                            QStringList entries_array;
+
+                            for (int idx = 1; ; ++idx)
+                            {
+                                auto iter = message_data.find(QStringLiteral("entry_path_%1").arg(idx));
+                                if (iter == message_data.end())
+                                {
+                                    break;
+                                }
+
+                                entries_array.push_back(iter.value());
+                            }
+
+                            config_group.writePathEntry(entry_name, entries_array, write_config_flags);
+                        }
+                        break;
+
+                    case event_type::delete_entry:
+                        config_group.deleteEntry(entry_name, write_config_flags);
+                        break;
+
+                    case event_type::delete_group:
+                        config_group.deleteGroup(write_config_flags);
+                        break;
+                    }
+                } while (false);
+
+                in_message = false;
+                message_data.clear();
+            }
+            else
+            {
+                // if '=' character is present and not first character in line
+                int index = line.indexOf(QLatin1Char('='));
+                if (index > 0)
+                {
+                    message_data[line.left(index)] = line.mid(index + 1);
+                }
+            }
+        }
+        else
+        {
+            if (line == QStringLiteral("message_begin"))
+            {
+                in_message = true;
+            }
+        }
+    }
+
+    return 0;
+}
diff --git a/kconfig/src/kconf_watcher/kconf_watcher.cpp b/kconfig/src/kconf_watcher/kconf_watcher.cpp
new file mode 100644
index 0000000..47103f8
--- /dev/null
+++ b/kconfig/src/kconf_watcher/kconf_watcher.cpp
@@ -0,0 +1,135 @@
+/*
+    This file is part of the KDE libraries
+    SPDX-FileCopyrightText: 2020 Aleksei Nikiforov <darktemplar@basealt.ru>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <inttypes.h>
+#include <signal.h>
+
+#include <QCoreApplication>
+#include <QSocketNotifier>
+#include <QDBusConnection>
+#include <QDBusMessage>
+#include <QStringList>
+#include <QString>
+
+#include "kconf_watcher_p.h"
+
+static int sigintFd[2];
+static int sigtermFd[2];
+
+void intSignalHandler(int)
+{
+    char a = 1;
+    ::write(sigintFd[0], &a, sizeof(a));
+}
+
+void termSignalHandler(int)
+{
+    char a = 1;
+    ::write(sigtermFd[0], &a, sizeof(a));
+}
+
+void qtHandleSigInt(QSocketNotifier &notifier)
+{
+    notifier.setEnabled(false);
+
+    char tmp;
+    ::read(sigintFd[1], &tmp, sizeof(tmp));
+
+    QCoreApplication::quit();
+
+    notifier.setEnabled(true);
+}
+
+void qtHandleSigTerm(QSocketNotifier &notifier)
+{
+    notifier.setEnabled(false);
+
+    char tmp;
+    ::read(sigtermFd[1], &tmp, sizeof(tmp));
+
+    QCoreApplication::quit();
+
+    notifier.setEnabled(true);
+}
+
+void DBusPrinter::messageSlot(const QStringList &values)
+{
+    static qint64 message_idx = 1;
+    fprintf(stdout, "message %" PRIi64 "\n", message_idx++);
+    fprintf(stdout, "message_begin\n");
+
+    for (const auto &item: values)
+    {
+        fprintf(stdout, "%s\n", item.toLocal8Bit().data());
+    }
+
+    fprintf(stdout, "message_end\n\n");
+    fflush(stdout);
+}
+
+int main(int argc, char **argv)
+{
+    QCoreApplication app(argc, argv);
+    app.setApplicationVersion(QStringLiteral("1.0"));
+
+    if (!QDBusConnection::sessionBus().isConnected())
+    {
+        fprintf(stderr,
+            "Cannot connect to the D-Bus session bus.\n"
+            "Please check your system settings and try again.\n");
+
+        return -1;
+    }
+
+    if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd))
+    {
+        fprintf(stderr, "Couldn't create INT socketpair\n");
+        return -1;
+    }
+
+    if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigtermFd))
+    {
+        fprintf(stderr, "Couldn't create TERM socketpair\n");
+        return -1;
+    }
+
+    QSocketNotifier sigIntNotifier(sigintFd[1], QSocketNotifier::Read);
+    QObject::connect(&sigIntNotifier, &QSocketNotifier::activated, [&sigIntNotifier] () { qtHandleSigInt(sigIntNotifier); });
+
+    QSocketNotifier sigTermNotifier(sigtermFd[1], QSocketNotifier::Read);
+    QObject::connect(&sigTermNotifier, &QSocketNotifier::activated, [&sigTermNotifier] () { qtHandleSigTerm(sigTermNotifier); });
+
+    {
+        struct sigaction sigaction_int, sigaction_term;
+
+        sigaction_int.sa_handler = &intSignalHandler;
+        sigemptyset(&sigaction_int.sa_mask);
+        sigaction_int.sa_flags = 0;
+        sigaction_int.sa_flags |= SA_RESTART;
+        sigaction(SIGINT, &sigaction_int, 0);
+
+        sigaction_term.sa_handler = &termSignalHandler;
+        sigemptyset(&sigaction_term.sa_mask);
+        sigaction_term.sa_flags = 0;
+        sigaction_term.sa_flags |= SA_RESTART;
+        sigaction(SIGTERM, &sigaction_term, 0);
+    }
+
+    DBusPrinter dbus_printer;
+    QDBusConnection::sessionBus().connect(
+        QString(),
+        QStringLiteral("/"),
+        QStringLiteral("org.alt.kde.kconfig.extra.notify"),
+        QStringLiteral("ConfigChanged"),
+        &dbus_printer,
+        SLOT(messageSlot(QStringList)));
+
+    return app.exec();
+}
diff --git a/kconfig/src/kconf_watcher/kconf_watcher_p.h b/kconfig/src/kconf_watcher/kconf_watcher_p.h
new file mode 100644
index 0000000..cb7d38d
--- /dev/null
+++ b/kconfig/src/kconf_watcher/kconf_watcher_p.h
@@ -0,0 +1,18 @@
+/*
+    This file is part of the KDE libraries
+    SPDX-FileCopyrightText: 2020 Aleksei Nikiforov <darktemplar@basealt.ru>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include <QObject>
+
+class QStringList;
+
+class DBusPrinter: public QObject
+{
+    Q_OBJECT
+
+public Q_SLOTS:
+    void messageSlot(const QStringList &values);
+};
-- 
2.29.3
 
дизайн и разработка: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
текущий майнтейнер: Michael Shigorin