// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #define UMDF_USING_NTSTATUS // Avoid ntstatus redefinitions #include "qwinregistry_p.h" #include #include #include #include #include #include #include // User mode version of ZwQueryKey, as per: // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwquerykey extern "C" NTSTATUS NTSYSCALLAPI NTAPI NtQueryKey(HANDLE KeyHandle, int KeyInformationClass, PVOID KeyInformation, ULONG Length, PULONG ResultLength); QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; QWinRegistryKey::QWinRegistryKey(QObject *parent) : QObject(parent) { } // Open a key with the specified permissions (KEY_READ/KEY_WRITE). // "access" is to explicitly use the 32- or 64-bit branch. QWinRegistryKey::QWinRegistryKey(HKEY parentHandle, QStringView subKey, REGSAM permissions, REGSAM access, QObject *parent) : QObject(parent) { if (RegOpenKeyExW(parentHandle, reinterpret_cast(subKey.utf16()), 0, permissions | access, &m_key) != ERROR_SUCCESS) { m_key = nullptr; } } QWinRegistryKey::~QWinRegistryKey() { close(); } void QWinRegistryKey::close() { if (isValid()) { RegCloseKey(m_key); m_key = nullptr; } } QString QWinRegistryKey::name() const { if (!isValid()) return {}; // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_key_name_information constexpr int kKeyNameInformation = 3; struct KeyNameInformation { ULONG length = 0; WCHAR name[1] = {}; }; // Resolve name of key iteratively by first computing the needed size, // and then querying the name, accounting for the possibility that the // name length changes behind our back a few times. DWORD keyInformationSize = 0; for (int i = 0; i < 5; ++i) { QByteArray buffer(keyInformationSize, 0u); auto *keyNameInformation = reinterpret_cast(buffer.data()); switch (NtQueryKey(m_key, kKeyNameInformation, keyNameInformation, keyInformationSize, &keyInformationSize)) { case STATUS_BUFFER_TOO_SMALL: case STATUS_BUFFER_OVERFLOW: continue; case STATUS_SUCCESS: { return QString::fromWCharArray(keyNameInformation->name, keyNameInformation->length / sizeof(wchar_t)); } default: return {}; } } return {}; } QVariant QWinRegistryKey::value(QStringView subKey) const { // NOTE: Empty value name is allowed in Windows registry, it means the default // or unnamed value of a key, you can read/write/delete such value normally. if (!isValid()) return {}; // Use nullptr when we need to access the default value. const auto subKeyC = subKey.isEmpty() ? nullptr : reinterpret_cast(subKey.utf16()); // Get the size and type of the value. DWORD dataType = REG_NONE; DWORD dataSize = 0; LONG ret = RegQueryValueExW(m_key, subKeyC, nullptr, &dataType, nullptr, &dataSize); if (ret != ERROR_SUCCESS) return {}; // Workaround for rare cases where the trailing '\0' is missing. if (dataType == REG_SZ || dataType == REG_EXPAND_SZ) dataSize += 2; else if (dataType == REG_MULTI_SZ) dataSize += 4; // Get the value. QVarLengthArray data(dataSize); std::fill(data.data(), data.data() + dataSize, 0u); ret = RegQueryValueExW(m_key, subKeyC, nullptr, nullptr, data.data(), &dataSize); if (ret != ERROR_SUCCESS) return {}; switch (dataType) { case REG_SZ: case REG_EXPAND_SZ: { if (dataSize > 0) { return QString::fromWCharArray( reinterpret_cast(data.constData())); } return QString(); } case REG_MULTI_SZ: { if (dataSize > 0) { QStringList list = {}; int i = 0; while (true) { const QString str = QString::fromWCharArray( reinterpret_cast(data.constData()) + i); i += str.length() + 1; if (str.isEmpty()) break; list.append(str); } return list; } return QStringList(); } case REG_NONE: // No specific type, treat as binary data. case REG_BINARY: { if (dataSize > 0) { return QString::fromWCharArray( reinterpret_cast(data.constData()), data.size() / 2); } return QString(); } case REG_DWORD: // Same as REG_DWORD_LITTLE_ENDIAN return qFromLittleEndian(data.constData()); case REG_DWORD_BIG_ENDIAN: return qFromBigEndian(data.constData()); case REG_QWORD: // Same as REG_QWORD_LITTLE_ENDIAN return qFromLittleEndian(data.constData()); default: break; } return {}; } // Returns the value of the specified subKey as a string, obtained using // qvariant_cast from the underlying QVariant. If that value is not a string, // or the subKey has no value, a string whose isNull() is true is returned. // Otherwise, the resulting string (which may be empty) is returned. QString QWinRegistryKey::stringValue(QStringView subKey) const { return value(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 QDebug operator<<(QDebug debug, const QWinRegistryKey &key) { QDebugStateSaver saver(debug); debug.nospace(); debug << "QWinRegistryKey("; if (key.isValid()) debug << key.name(); debug << ')'; return debug; } #endif QT_END_NAMESPACE