Teach QWinRegistryKey to observe changes to the key

In some cases there are no explicit notification APIs for
changes to system settings, and the developer is expected
to observe changes to the registry keys directly.

For example this applies to color profile associations, as
documented in:

 https://learn.microsoft.com/en-us/windows/win32/wcs/wcs-registry-keys

Change-Id: I3507058d90b1d32b8b5256f40861126277242cd6
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Tor Arne Vestbø 2025-03-10 14:25:18 +01:00
parent e88940941f
commit 589bfddc7c
3 changed files with 83 additions and 5 deletions

View File

@ -8,6 +8,8 @@
#include <QtCore/qdebug.h> #include <QtCore/qdebug.h>
#include <QtCore/qendian.h> #include <QtCore/qendian.h>
#include <QtCore/qlist.h> #include <QtCore/qlist.h>
#include <QtCore/qwineventnotifier.h>
#include <QtCore/private/qsystemerror_p.h>
#include <ntstatus.h> #include <ntstatus.h>
@ -20,14 +22,17 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
QWinRegistryKey::QWinRegistryKey() QWinRegistryKey::QWinRegistryKey(QObject *parent)
: QObject(parent)
{ {
} }
// Open a key with the specified permissions (KEY_READ/KEY_WRITE). // Open a key with the specified permissions (KEY_READ/KEY_WRITE).
// "access" is to explicitly use the 32- or 64-bit branch. // "access" is to explicitly use the 32- or 64-bit branch.
QWinRegistryKey::QWinRegistryKey(HKEY parentHandle, QStringView subKey, QWinRegistryKey::QWinRegistryKey(HKEY parentHandle, QStringView subKey,
REGSAM permissions, REGSAM access) REGSAM permissions, REGSAM access,
QObject *parent)
: QObject(parent)
{ {
if (RegOpenKeyExW(parentHandle, reinterpret_cast<const wchar_t *>(subKey.utf16()), if (RegOpenKeyExW(parentHandle, reinterpret_cast<const wchar_t *>(subKey.utf16()),
0, permissions | access, &m_key) != ERROR_SUCCESS) { 0, permissions | access, &m_key) != ERROR_SUCCESS) {
@ -174,6 +179,45 @@ QString QWinRegistryKey::stringValue(QStringView subKey) const
return value<QString>(subKey).value_or(QString()); return value<QString>(subKey).value_or(QString());
} }
void QWinRegistryKey::connectNotify(const QMetaMethod &signal)
{
if (signal != QMetaMethod::fromSignal(&QWinRegistryKey::valueChanged))
return;
if (!isValid())
return;
if (m_keyChangedEvent)
return;
m_keyChangedEvent.reset(CreateEvent(nullptr, false, false, nullptr));
auto *notifier = new QWinEventNotifier(m_keyChangedEvent.get(), this);
auto registerForNotification = [this] {
constexpr auto changeFilter =
REG_NOTIFY_CHANGE_NAME
| REG_NOTIFY_CHANGE_ATTRIBUTES
| REG_NOTIFY_CHANGE_LAST_SET
| REG_NOTIFY_CHANGE_SECURITY;
if (auto status = RegNotifyChangeKeyValue(m_key, true, changeFilter,
m_keyChangedEvent.get(), true); status != ERROR_SUCCESS) {
qWarning() << "Failed to register notification for registry key"
<< this << "due to" << QSystemError::windowsString(status);
}
};
QObject::connect(notifier, &QWinEventNotifier::activated, this,
[this, registerForNotification] {
emit valueChanged();
registerForNotification();
});
registerForNotification();
return QObject::connectNotify(signal);
}
#ifndef QT_NO_DEBUG_STREAM #ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QWinRegistryKey &key) QDebug operator<<(QDebug debug, const QWinRegistryKey &key)
{ {

View File

@ -20,17 +20,21 @@
#include <QtCore/qstringview.h> #include <QtCore/qstringview.h>
#include <QtCore/qt_windows.h> #include <QtCore/qt_windows.h>
#include <QtCore/qvariant.h> #include <QtCore/qvariant.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/private/quniquehandle_types_p.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class Q_CORE_EXPORT QWinRegistryKey class Q_CORE_EXPORT QWinRegistryKey : public QObject
{ {
Q_DISABLE_COPY(QWinRegistryKey) Q_DISABLE_COPY(QWinRegistryKey)
Q_OBJECT
public: public:
QWinRegistryKey(); QWinRegistryKey(QObject *parent = nullptr);
explicit QWinRegistryKey(HKEY parentHandle, QStringView subKey, explicit QWinRegistryKey(HKEY parentHandle, QStringView subKey,
REGSAM permissions = KEY_READ, REGSAM access = 0); REGSAM permissions = KEY_READ, REGSAM access = 0,
QObject *parent = nullptr);
~QWinRegistryKey(); ~QWinRegistryKey();
QWinRegistryKey(QWinRegistryKey &&other) noexcept QWinRegistryKey(QWinRegistryKey &&other) noexcept
@ -64,8 +68,15 @@ public:
friend Q_CORE_EXPORT QDebug operator<<(QDebug dbg, const QWinRegistryKey &); friend Q_CORE_EXPORT QDebug operator<<(QDebug dbg, const QWinRegistryKey &);
#endif #endif
Q_SIGNALS:
void valueChanged();
protected:
void connectNotify(const QMetaMethod &signal) override;
private: private:
HKEY m_key = nullptr; HKEY m_key = nullptr;
QUniqueWin32NullHandle m_keyChangedEvent;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -99,6 +99,7 @@ private Q_SLOTS:
void cleanupTestCase(); void cleanupTestCase();
void qwinregistrykey(); void qwinregistrykey();
void name(); void name();
void valueChanged();
private: private:
bool m_available = false; bool m_available = false;
@ -266,6 +267,28 @@ void tst_qwinregistrykey::name()
} }
} }
void tst_qwinregistrykey::valueChanged()
{
if (!m_available)
QSKIP("The test data is not ready.");
QWinRegistryKey testKey(HKEY_CURRENT_USER, TEST_KEY, KEY_READ | KEY_WRITE);
QVERIFY(testKey.isValid());
QVERIFY(write(testKey, u"valueThatCanChange", -1));
bool valueChanged = false;
QObject::connect(&testKey, &QWinRegistryKey::valueChanged, [&] {
valueChanged = true;
});
for (int i = 0; i < 10; ++i) {
valueChanged = false;
QVERIFY(write(testKey, u"valueThatCanChange", i));
QTRY_VERIFY(valueChanged);
}
}
QTEST_MAIN(tst_qwinregistrykey) QTEST_MAIN(tst_qwinregistrykey)
#include "tst_qwinregistrykey.moc" #include "tst_qwinregistrykey.moc"