/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsettings.h" #include "qsettings_p.h" #include "qvector.h" #include "qmap.h" #include "qdebug.h" #include "qfunctions_winrt.h" #include #include #include #include using namespace ABI::Windows::ApplicationModel; using namespace ABI::Windows::Storage; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Foundation::Collections; using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; typedef ITypedEventHandler DataHandler; typedef Collections::IKeyValuePair ContainerItem; typedef Collections::IIterable ContainerIterable; typedef Collections::IIterator ContainerIterator; typedef Collections::IKeyValuePair ValueItem; typedef Collections::IIterable ValueIterable; typedef Collections::IIterator ValueIterator; QT_BEGIN_NAMESPACE static IApplicationDataContainer *subContainer(IApplicationDataContainer *parent, const QString &name) { ComPtr> childrenContainer; HRESULT hr = parent->get_Containers(&childrenContainer); if (FAILED(hr)) return 0; ComPtr< ContainerIterable > iterable; ComPtr< ContainerIterator > iterator; hr = childrenContainer.As(&iterable); if (FAILED(hr)) return 0; hr = iterable->First(&iterator); if (FAILED(hr)) return 0; boolean current; hr = iterator->get_HasCurrent(¤t); if (FAILED(hr)) return 0; while (SUCCEEDED(hr) && current) { ComPtr item; hr = iterator->get_Current(&item); if (FAILED(hr)) return 0; HString key; hr = item->get_Key(key.GetAddressOf()); if (FAILED(hr)) continue; QString subName = QString::fromWCharArray(key.GetRawBuffer(nullptr)); if (name == subName) { IApplicationDataContainer *container; hr = item->get_Value(&container); return SUCCEEDED(hr) ? container : 0; } hr = iterator->MoveNext(¤t); } return 0; } static QStringList subContainerNames(IApplicationDataContainer *container, bool recursive = false) { QStringList result; ComPtr> childrenContainer; HRESULT hr = container->get_Containers(&childrenContainer); if (FAILED(hr)) return result; ComPtr< ContainerIterable > iterable; ComPtr< ContainerIterator > iterator; hr = childrenContainer.As(&iterable); if (FAILED(hr)) return result; hr = iterable->First(&iterator); if (FAILED(hr)) return result; boolean current; hr = iterator->get_HasCurrent(¤t); if (FAILED(hr)) return result; while (SUCCEEDED(hr) && current) { ComPtr item; hr = iterator->get_Current(&item); if (FAILED(hr)) return result; HString key; hr = item->get_Key(key.GetAddressOf()); if (SUCCEEDED(hr)) { QString subName = QString::fromWCharArray(key.GetRawBuffer(nullptr)); result.append(subName); if (recursive) { ComPtr sub = subContainer(container, subName); QStringList subSubNames = subContainerNames(sub.Get(), recursive); for (int i = 0; i < subSubNames.size(); ++i) subSubNames[i] = subName + QLatin1Char('/') + subSubNames[i]; result.append(subSubNames); } hr = iterator->MoveNext(¤t); } } return result; } static QStringList keyNames(IApplicationDataContainer *container) { HRESULT hr; QStringList result; ComPtr values; hr = container->get_Values(&values); if (FAILED(hr)) return result; ComPtr> settingsMap; hr = values.As(&settingsMap); if (FAILED(hr)) return result; ComPtr> mapView; hr = settingsMap->GetView(&mapView); if (FAILED(hr)) return result; ComPtr< ValueIterable > iterable; ComPtr< ValueIterator > iterator; hr = mapView.As(&iterable); if (FAILED(hr)) return result; boolean current = false; hr = iterable->First(&iterator); if (FAILED(hr)) return result; hr = iterator->get_HasCurrent(¤t); if (FAILED(hr)) return result; while (SUCCEEDED(hr) && current){ ComPtr item; hr = iterator->get_Current(&item); if (FAILED(hr)) return result; HString key; hr = item->get_Key(key.GetAddressOf()); if (SUCCEEDED(hr)) { result += QString::fromWCharArray(key.GetRawBuffer(nullptr)); hr = iterator->MoveNext(¤t); } } return result; } static IApplicationDataContainer *createSubContainer(IApplicationDataContainer *parent, const QString &name) { HStringReference childGroupNativeName((const wchar_t*)name.utf16(), name.size()); IApplicationDataContainer *result = subContainer(parent, name); if (!result) parent->CreateContainer(childGroupNativeName.Get(), ApplicationDataCreateDisposition_Always, &result); return result; } #define PROP_CASE_TO_VARIANT(TYPE, VARTYPE, QTYPE) \ case PropertyType_##TYPE: { \ VARTYPE v; \ value->Get##TYPE(&v); \ result.setValue( QTYPE(v) ); \ break; \ } static QVariant propertyValueToQVariant(IPropertyValue *value) { QVariant result; PropertyType type; value->get_Type(&type); switch (type) { PROP_CASE_TO_VARIANT(Boolean, boolean, bool) PROP_CASE_TO_VARIANT(UInt8, UINT8, quint8) PROP_CASE_TO_VARIANT(Int16, INT16, qint16) PROP_CASE_TO_VARIANT(UInt16, UINT16, quint16) PROP_CASE_TO_VARIANT(Int32, INT32, qint32) PROP_CASE_TO_VARIANT(UInt32, UINT32, quint32) PROP_CASE_TO_VARIANT(Int64, INT64, qint64) PROP_CASE_TO_VARIANT(UInt64, UINT64, quint64) PROP_CASE_TO_VARIANT(Single, FLOAT, float) PROP_CASE_TO_VARIANT(Double, DOUBLE, double) case PropertyType_StringArray: { UINT32 size; HSTRING *content; value->GetStringArray(&size, &content); QStringList list; // The last item is assumed to be added by us for (UINT32 i = 0; i < size - 1; ++i) { QString s = QString::fromWCharArray(WindowsGetStringRawBuffer(content[i], nullptr)); list.append(s); } result = QSettingsPrivate::stringListToVariantList(list); break; } case PropertyType_String: { HString v; value->GetString(v.GetAddressOf()); result = QSettingsPrivate::stringToVariant(QString::fromWCharArray(v.GetRawBuffer(nullptr))); break; } default: { UINT32 size; BYTE *arr; value->GetUInt8Array(&size, &arr); QByteArray data = QByteArray::fromRawData((const char*)arr, size); QString s; if (size) { // We assume this is our qt stored data like on other platforms // as well. QList and others are converted to byte arrays s = QString::fromWCharArray((const wchar_t *)data.constData(), data.size() / 2); result = QSettingsPrivate::stringToVariant(s); } break; } } return result; } class QWinRTSettingsPrivate : public QSettingsPrivate { public: QWinRTSettingsPrivate(QSettings::Scope scope, const QString &organization, const QString &application); QWinRTSettingsPrivate(const QString &rKey); ~QWinRTSettingsPrivate(); void remove(const QString &uKey) override; void set(const QString &uKey, const QVariant &value) override; bool get(const QString &uKey, QVariant *value) const override; QStringList children(const QString &uKey, ChildSpec spec) const override; void clear() override; void sync() override; void flush() override; bool isWritable() const override; QString fileName() const override; private: void init(QSettings::Scope scope); IApplicationDataContainer *getContainer(IApplicationDataContainer *parent, const QString &group, bool create = false) const; void clearContainerMaps(); HRESULT onDataChanged(IApplicationData*, IInspectable*); ComPtr applicationData; QVector> readContainers; ComPtr writeContainer; EventRegistrationToken dataChangedToken; }; QWinRTSettingsPrivate::QWinRTSettingsPrivate(QSettings::Scope scope, const QString &organization, const QString &application) : QSettingsPrivate(QSettings::NativeFormat, scope, organization, application) , writeContainer(0) { init(scope); } QWinRTSettingsPrivate::QWinRTSettingsPrivate(const QString &rPath) : QSettingsPrivate(QSettings::NativeFormat, QSettings::UserScope, rPath, QString()) , writeContainer(0) { init(QSettings::UserScope); } QWinRTSettingsPrivate::~QWinRTSettingsPrivate() { clearContainerMaps(); } void QWinRTSettingsPrivate::remove(const QString &uKey) { int lastIndex = uKey.lastIndexOf(QLatin1Char('/')); QString groupName = (lastIndex > 0) ? uKey.left(lastIndex) : QString(); QString groupKey = uKey.mid(lastIndex + 1); ComPtr container = getContainer(writeContainer.Get(), groupName, false); if (!container) return; HRESULT hr; ComPtr values; hr = container->get_Values(&values); if (FAILED(hr)) return; ComPtr> settingsMap; hr = values.As(&settingsMap); if (FAILED(hr)) return; HStringReference ref((const wchar_t*)groupKey.utf16(), groupKey.size()); hr = settingsMap->Remove(ref.Get()); // groupKey can be a container as well hr = container->DeleteContainer(ref.Get()); init(scope); } void QWinRTSettingsPrivate::set(const QString &uKey, const QVariant &value) { int lastIndex = uKey.lastIndexOf(QLatin1Char('/')); QString groupName = (lastIndex > 0) ? uKey.left(lastIndex) : QString(); QString groupKey = uKey.mid(lastIndex + 1); ComPtr container = getContainer(writeContainer.Get(), groupName, true); ComPtr values; HRESULT hr = container->get_Values(&values); if (FAILED(hr)) { qErrnoWarning(hr, "Could not access Windows container values"); setStatus(QSettings::AccessError); return; } ComPtr> settingsMap; hr = values.As(&settingsMap); if (FAILED(hr)) { setStatus(QSettings::AccessError); return; } ComPtr valueStatics; hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_PropertyValue).Get(), &valueStatics); if (FAILED(hr)) { setStatus(QSettings::AccessError); return; } ComPtr val; switch (value.type()) { case QVariant::List: case QVariant::StringList: { QStringList l = variantListToStringList(value.toList()); QStringList::const_iterator it = l.constBegin(); bool containsNull = false; for (; it != l.constEnd(); ++it) { if ((*it).length() == 0 || it->contains(QChar::Null)) { // We can only store as binary containsNull = true; break; } } if (containsNull) { // Store binary const QString s = variantToString(value); hr = valueStatics->CreateUInt8Array(s.length() * 2, (BYTE*) s.utf16(), &val); } else { // Store as native string list int size = l.size(); HSTRING *nativeHandleList = new HSTRING[size+1]; for (int i = 0; i < size; ++i) hr = WindowsCreateString((const wchar_t*)l[i].utf16(), l[i].size(), &nativeHandleList[i]); // Add end marker hr = WindowsCreateString((const wchar_t*)L"\0\0@", 3, &nativeHandleList[size]); hr = valueStatics->CreateStringArray(size + 1 , nativeHandleList, &val); for (int i = 0; i < size; ++i) hr = WindowsDeleteString(nativeHandleList[i]); delete [] nativeHandleList; } break; } case QVariant::Bool: hr = valueStatics->CreateBoolean(boolean(value.toBool()), &val); break; case QVariant::Int: hr = valueStatics->CreateInt32(INT32(value.toInt()), &val); break; case QVariant::UInt: hr = valueStatics->CreateUInt32(UINT32(value.toUInt()), &val); break; case QVariant::LongLong: hr = valueStatics->CreateInt64(INT64(value.toLongLong()), &val); break; case QVariant::ULongLong: hr = valueStatics->CreateUInt64(UINT64(value.toULongLong()), &val); break; default: { const QString s = variantToString(value); if (s.contains(QChar::Null)) { hr = valueStatics->CreateUInt8Array(s.length() * 2, (BYTE*) s.utf16(), &val); } else { HStringReference ref((const wchar_t*)s.utf16(), s.size()); hr = valueStatics->CreateString(ref.Get(), &val); } break; } } RETURN_VOID_IF_FAILED("QSettings: Could not save QVariant value into IInspectable"); HStringReference key((const wchar_t*)groupKey.utf16(), groupKey.size()); boolean rep; hr = settingsMap->Insert(key.Get(), val.Get(), &rep); RETURN_VOID_IF_FAILED("QSettings: Could not store value"); } bool QWinRTSettingsPrivate::get(const QString &uKey, QVariant *value) const { int lastIndex = uKey.lastIndexOf(QLatin1Char('/')); QString groupName = (lastIndex > 0) ? uKey.left(lastIndex) : QString(); QString groupKey = uKey.mid(lastIndex + 1); HRESULT hr; for (int i = 0; i < readContainers.size(); ++i) { ComPtr container = const_cast(this)->getContainer(readContainers.at(i).Get(), groupName); if (!container) continue; ComPtr values; hr = container->get_Values(&values); if (FAILED(hr)) continue; ComPtr> settingsMap; hr = values.As(&settingsMap); if (FAILED(hr)) continue; HStringReference key((const wchar_t*)groupKey.utf16(), groupKey.size()); boolean exists; hr = settingsMap.Get()->HasKey(key.Get(), &exists); if (FAILED(hr)) continue; if (!exists) { if (!fallbacks) break; else continue; } if (value) { ComPtr val; hr = settingsMap->Lookup(key.Get(), &val); if (FAILED(hr)) return false; ComPtr pVal; hr = val.As(&pVal); if (FAILED(hr)) return false; *value = propertyValueToQVariant(pVal.Get()); } return true; } setStatus(QSettings::AccessError); return false; } QStringList QWinRTSettingsPrivate::children(const QString &uKey, ChildSpec spec) const { QStringList result; for (int i = 0; i < readContainers.size(); ++i) { ComPtr container = getContainer(readContainers.at(i).Get(), uKey, false); if (!container.Get()) continue; // Get Keys in this container if (spec == AllKeys || spec == ChildKeys) result += keyNames(container.Get()); // Get Subcontainer(s) if (spec == AllKeys || spec == ChildGroups) { const QStringList subContainerList = subContainerNames(container.Get(), spec == AllKeys); if (spec == AllKeys) { for (const QString &item : subContainerList) { const QString subChildren = uKey.isEmpty() ? item : (uKey + QLatin1Char('/') + item); const QStringList subResult = children(subChildren, ChildKeys); for (const QString &subItem : subResult) result += item + QLatin1Char('/') + subItem; } } if (spec == ChildGroups) result += subContainerList; } } result.removeDuplicates(); return result; } void QWinRTSettingsPrivate::clear() { ComPtr container; HRESULT hr; if (scope == QSettings::UserScope) hr = applicationData->get_LocalSettings(&container); else hr = applicationData->get_RoamingSettings(&container); RETURN_VOID_IF_FAILED("Could not access settings container"); QString containerName = applicationName.isEmpty() ? organizationName : applicationName; HStringReference containerNativeName((const wchar_t*)containerName.utf16(), containerName.size()); hr = container->DeleteContainer(containerNativeName.Get()); RETURN_VOID_IF_FAILED("Could not delete Container"); init(scope); } void QWinRTSettingsPrivate::sync() { // No native sync available } void QWinRTSettingsPrivate::flush() { // No native flush available } QString QWinRTSettingsPrivate::fileName() const { Q_UNIMPLEMENTED(); return QString(); } HRESULT QWinRTSettingsPrivate::onDataChanged(IApplicationData *, IInspectable *) { // This only happens, if roaming data is changed by the OS. // To ensure sanity we clean up the map and start from scratch init(scope); return S_OK; } void QWinRTSettingsPrivate::init(QSettings::Scope scope) { clearContainerMaps(); ComPtr applicationDataStatics; HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_ApplicationData).Get(), &applicationDataStatics); if (FAILED(hr)) { qErrnoWarning(hr, "Could not access Storage Factory"); setStatus(QSettings::AccessError); return; } hr = applicationDataStatics->get_Current(&applicationData); if (FAILED(hr)) { qErrnoWarning(hr, "Could not access application data statics"); setStatus(QSettings::AccessError); return; } const QString organizationString = organizationName.isEmpty() ? QLatin1String("OrganizationDefaults") : organizationName; ComPtr localContainer; if (scope == QSettings::UserScope && SUCCEEDED(applicationData->get_LocalSettings(&localContainer))) { if (!applicationName.isEmpty()) readContainers.append(createSubContainer(localContainer.Get(), applicationName)); readContainers.append(createSubContainer(localContainer.Get(), organizationString)); } ComPtr roamingContainer; if (SUCCEEDED(applicationData->get_RoamingSettings(&roamingContainer))) { if (!applicationName.isEmpty()) readContainers.append(createSubContainer(roamingContainer.Get(), applicationName)); readContainers.append(createSubContainer(roamingContainer.Get(), organizationString)); } ComPtr writeRootContainer = (scope == QSettings::UserScope) ? localContainer : roamingContainer; if (!applicationName.isEmpty()) writeContainer = createSubContainer(writeRootContainer.Get(), applicationName); else writeContainer = createSubContainer(writeRootContainer.Get(), organizationString); hr = applicationData->add_DataChanged(Callback(this, &QWinRTSettingsPrivate::onDataChanged).Get(), &dataChangedToken); } IApplicationDataContainer *QWinRTSettingsPrivate::getContainer(IApplicationDataContainer *parent, const QString &group, bool create) const { IApplicationDataContainer *current = parent; if (group.isEmpty()) return current; const QStringList groupPath = group.split(QLatin1Char('/'), QString::SkipEmptyParts); for (const QString &subGroup : groupPath) { ComPtr sub = subContainer(current, subGroup); if (!sub && create) sub = createSubContainer(current, subGroup); if (!sub) return 0; // Something seriously went wrong current = sub.Detach(); } return current; } void QWinRTSettingsPrivate::clearContainerMaps() { readContainers.clear(); writeContainer.Reset(); } bool QWinRTSettingsPrivate::isWritable() const { return true; } QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application) { if (format == QSettings::NativeFormat) return new QWinRTSettingsPrivate(scope, organization, application); else return new QConfFileSettingsPrivate(format, scope, organization, application); } QSettingsPrivate *QSettingsPrivate::create(const QString &fileName, QSettings::Format format) { if (format == QSettings::NativeFormat) return new QWinRTSettingsPrivate(fileName); else return new QConfFileSettingsPrivate(fileName, format); } QT_END_NAMESPACE