From 63fc1ecca37e26c85e3a41d4e2f68af9bfa02532 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 13:10:48 +0100 Subject: [PATCH 01/14] Changes: document intention of this branch --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 720908ca4..2b0c3210a 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ This release contains contributions from (alphabetically by first name): ## Modules ## - A new QML-based *finishedq* module has been added. (Thanks Anke) + - The *users* module now can set a fixed username and prevent editing. # 3.2.37 (2021-02-23) # From d9f2f5e988ba8f0692106bab7f3e07a641376d38 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 13:16:36 +0100 Subject: [PATCH 02/14] [libcalamares] Start a 'presets' configuration datastructure --- src/libcalamares/CMakeLists.txt | 1 + src/libcalamares/modulesystem/Preset.cpp | 49 ++++++++++++++++++++ src/libcalamares/modulesystem/Preset.h | 57 ++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 src/libcalamares/modulesystem/Preset.cpp create mode 100644 src/libcalamares/modulesystem/Preset.h diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt index 700eff37b..bd24726bc 100644 --- a/src/libcalamares/CMakeLists.txt +++ b/src/libcalamares/CMakeLists.txt @@ -51,6 +51,7 @@ set( libSources modulesystem/Descriptor.cpp modulesystem/InstanceKey.cpp modulesystem/Module.cpp + modulesystem/Preset.cpp modulesystem/RequirementsChecker.cpp modulesystem/RequirementsModel.cpp diff --git a/src/libcalamares/modulesystem/Preset.cpp b/src/libcalamares/modulesystem/Preset.cpp new file mode 100644 index 000000000..8240e23e7 --- /dev/null +++ b/src/libcalamares/modulesystem/Preset.cpp @@ -0,0 +1,49 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#include "Preset.h" + +#include "utils/Logger.h" +#include "utils/Variant.h" + +static void +loadPresets( Calamares::ModuleSystem::Presets& preset, + const QVariantMap& configurationMap, + std::function< bool( const QString& ) > pred ) +{ + cDebug() << "Creating presets" << preset.capacity(); + for ( auto it = configurationMap.cbegin(); it != configurationMap.cend(); ++it ) + { + if ( !it.key().isEmpty() && pred( it.key() ) ) + { + QVariantMap m = it.value().toMap(); + QString value = CalamaresUtils::getString( m, "value" ); + bool editable = CalamaresUtils::getBool( m, "editable", true ); + + preset.append( Calamares::ModuleSystem::PresetField { it.key(), value, editable } ); + + cDebug() << Logger::SubEntry << "Preset for" << it.key() << "applied editable?" << editable; + } + } +} + +Calamares::ModuleSystem::Presets::Presets( const QVariantMap& configurationMap ) + + +{ + reserve( configurationMap.count() ); + loadPresets( *this, configurationMap, []( const QString& ) { return true; } ); +} + +Calamares::ModuleSystem::Presets::Presets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ) +{ + reserve( recognizedKeys.size() ); + loadPresets( + *this, configurationMap, [&recognizedKeys]( const QString& s ) { return recognizedKeys.contains( s ); } ); +} diff --git a/src/libcalamares/modulesystem/Preset.h b/src/libcalamares/modulesystem/Preset.h new file mode 100644 index 000000000..1744313ae --- /dev/null +++ b/src/libcalamares/modulesystem/Preset.h @@ -0,0 +1,57 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#ifndef CALAMARES_MODULESYSTEM_PRESET_H +#define CALAMARES_MODULESYSTEM_PRESET_H + +#include +#include +#include + +namespace Calamares +{ +namespace ModuleSystem +{ +/** @brief The settings for a single field + * + * The settings apply to a single field; **often** this will + * correspond to a single value or property of a Config + * object, but there is no guarantee of a correspondence + * between names here and names in the code. + * + * The value is stored as a string; consumers (e.g. the UI) + * will need to translate the value to whatever is actually + * used (e.g. in the case of an integer field). + * + * By default, presets are still editable. Set that to @c false + * to make the field unchangeable (again, the UI is responsible + * for setting that up). + */ +struct PresetField +{ + QString fieldName; + QString value; + bool editable = true; +}; + +/** @brief All the presets for one UI entity + * + * This is a collection of presets read from a module + * configuration file, one setting per field. + */ +class Presets : public QVector< PresetField > +{ +public: + explicit Presets( const QVariantMap& configurationMap ); + Presets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ); +}; +} // namespace ModuleSystem +} // namespace Calamares + +#endif From 381a4f9b531de97bdedeb1d8a7b0006006baadc0 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 13:25:16 +0100 Subject: [PATCH 03/14] [users] Add preset to users module Config --- src/modules/users/Config.cpp | 7 ++++++- src/modules/users/users.conf | 8 ++++++++ src/modules/users/users.schema.yaml | 12 ++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 165e21b9c..00aa257fa 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -16,6 +16,7 @@ #include "GlobalStorage.h" #include "JobQueue.h" +#include "modulesystem/Preset.h" #include "utils/Logger.h" #include "utils/String.h" #include "utils/Variant.h" @@ -105,7 +106,7 @@ Config::Config( QObject* parent ) connect( this, &Config::requireStrongPasswordsChanged, this, &Config::checkReady ); } -Config::~Config() { } +Config::~Config() {} void Config::setUserShell( const QString& shell ) @@ -836,6 +837,10 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) updateGSAutoLogin( doAutoLogin(), loginName() ); checkReady(); + + bool bogus = true; + Calamares::ModuleSystem::Presets p( CalamaresUtils::getSubMap( configurationMap, "presets", bogus ), + { "fullname", "loginname", } ); } void diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index 2e09ae123..b137a135a 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -159,3 +159,11 @@ setHostname: EtcFile # (also adds localhost and some ipv6 standard entries). # Defaults to *true*. writeHostsFile: true + +presets: + fullname: + value: "OEM User" + editable: false + loginname: + value: "oem" + editable: false diff --git a/src/modules/users/users.schema.yaml b/src/modules/users/users.schema.yaml index 81088032c..5870a7e0a 100644 --- a/src/modules/users/users.schema.yaml +++ b/src/modules/users/users.schema.yaml @@ -43,6 +43,18 @@ properties: setHostname: { type: string, enum: [ None, EtcFile, Hostnamed ] } writeHostsFile: { type: boolean, default: true } + # Presets + # + # TODO: lift up somewhere, since this will return in many modules; + # the type for each field (fullname, loginname) is a + # preset-description (value, editable). + presets: + type: object + additionalProperties: false + properties: + fullname: { type: object } + loginname: { type: object } + required: - defaultGroups - autologinGroup From 0be5e04c2e8655f2965f26f4892fa4f07516e4aa Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 13:49:37 +0100 Subject: [PATCH 04/14] [libcalamares] Add a base class for Config-objects This is an optional (until 3.3) base class, which can handle Presets consistently for configurations. --- src/libcalamares/CMakeLists.txt | 1 + src/libcalamares/modulesystem/Config.cpp | 64 ++++++++++++++++++++++++ src/libcalamares/modulesystem/Config.h | 60 ++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 src/libcalamares/modulesystem/Config.cpp create mode 100644 src/libcalamares/modulesystem/Config.h diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt index bd24726bc..826f0bc41 100644 --- a/src/libcalamares/CMakeLists.txt +++ b/src/libcalamares/CMakeLists.txt @@ -48,6 +48,7 @@ set( libSources locale/TranslatableString.cpp # Modules + modulesystem/Config.cpp modulesystem/Descriptor.cpp modulesystem/InstanceKey.cpp modulesystem/Module.cpp diff --git a/src/libcalamares/modulesystem/Config.cpp b/src/libcalamares/modulesystem/Config.cpp new file mode 100644 index 000000000..75c54badf --- /dev/null +++ b/src/libcalamares/modulesystem/Config.cpp @@ -0,0 +1,64 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#include "Config.h" + +#include "Preset.h" +#include "utils/Variant.h" + +namespace Calamares +{ +namespace ModuleSystem +{ + + +class Config::Private +{ +public: + std::unique_ptr< Presets > m_presets; +}; + + +Config::Config( QObject* parent ) + : QObject( parent ) + , d( std::make_unique< Private >() ) +{ +} + +Config::~Config() {} + +void +Config::loadPresets( const QVariantMap& configurationMap ) +{ + const QString key( "presets" ); + if ( !configurationMap.contains( key ) ) + { + d->m_presets.reset(); + return; + } + bool bogus = true; + d->m_presets = std::make_unique< Presets >( CalamaresUtils::getSubMap( configurationMap, key, bogus ) ); +} + +void +Config::loadPresets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ) +{ + const QString key( "presets" ); + if ( !configurationMap.contains( key ) ) + { + d->m_presets.reset(); + return; + } + bool bogus = true; + d->m_presets + = std::make_unique< Presets >( CalamaresUtils::getSubMap( configurationMap, key, bogus ), recognizedKeys ); +} + +} // namespace ModuleSystem +} // namespace Calamares diff --git a/src/libcalamares/modulesystem/Config.h b/src/libcalamares/modulesystem/Config.h new file mode 100644 index 000000000..9bc0705c6 --- /dev/null +++ b/src/libcalamares/modulesystem/Config.h @@ -0,0 +1,60 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#ifndef CALAMARES_MODULESYSTEM_CONFIG_H +#define CALAMARES_MODULESYSTEM_CONFIG_H + +#include "DllMacro.h" + +#include +#include +#include + +#include + +namespace Calamares +{ +namespace ModuleSystem +{ +/** @brief Base class for Config-objects + * + * This centralizes the things every Config-object should + * do and provides one source of preset-data. A Config-object + * for a module can **optionally** inherit from this class + * to get presets-support. + * + * TODO:3.3 This is not optional + * TODO:3.3 Put consistent i18n for Configurations in here too + */ +class DLLEXPORT Config : public QObject +{ +public: + Config( QObject* parent = nullptr ); + ~Config() override; + + /** @brief Set the configuration from the config file + * + * Subclasses must implement this to load configuration data; + * that subclass **should** also call loadPresets() with the + * same map, to pick up the "presets" key consistently. + */ + virtual void setConfigurationMap( const QVariantMap& ) = 0; + +protected: + void loadPresets( const QVariantMap& configurationMap ); + void loadPresets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ); + +private: + class Private; + std::unique_ptr< Private > d; +}; +} // namespace ModuleSystem +} // namespace Calamares + +#endif From 448e478b6d4afc50fbb040196483581a68875bb6 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 13:54:06 +0100 Subject: [PATCH 05/14] [users] Use base Config and its Preset-handling --- src/modules/users/Config.cpp | 11 ++++++----- src/modules/users/Config.h | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 00aa257fa..0fcf745fa 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -16,7 +16,6 @@ #include "GlobalStorage.h" #include "JobQueue.h" -#include "modulesystem/Preset.h" #include "utils/Logger.h" #include "utils/String.h" #include "utils/Variant.h" @@ -92,7 +91,7 @@ hostNameActionNames() } Config::Config( QObject* parent ) - : QObject( parent ) + : Calamares::ModuleSystem::Config( parent ) { emit readyChanged( m_isReady ); // false @@ -838,9 +837,11 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) updateGSAutoLogin( doAutoLogin(), loginName() ); checkReady(); - bool bogus = true; - Calamares::ModuleSystem::Presets p( CalamaresUtils::getSubMap( configurationMap, "presets", bogus ), - { "fullname", "loginname", } ); + loadPresets( configurationMap, + { + "fullname", + "loginname", + } ); } void diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index d4bfee4a4..28f0c73d7 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -13,6 +13,7 @@ #include "CheckPWQuality.h" #include "Job.h" +#include "modulesystem/Config.h" #include "utils/NamedEnum.h" #include @@ -85,7 +86,7 @@ private: }; -class PLUGINDLLEXPORT Config : public QObject +class PLUGINDLLEXPORT Config : public Calamares::ModuleSystem::Config { Q_OBJECT @@ -161,7 +162,7 @@ public: Config( QObject* parent = nullptr ); ~Config() override; - void setConfigurationMap( const QVariantMap& ); + void setConfigurationMap( const QVariantMap& ) override; /** @brief Fill Global Storage with some settings * From 8b10a9cfc2ba97cd3ee0ff39426113f030a3da4d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 14:49:46 +0100 Subject: [PATCH 06/14] [libcalamares] Add isEditable() check This adds support for checking whether a field is editable; Config objects should reject changes if the field is not editable. There is an "unlock" setting to override the check, although this is currently always locked. --- src/libcalamares/modulesystem/Config.cpp | 15 ++++++++++++++ src/libcalamares/modulesystem/Config.h | 12 ++++++++++++ src/libcalamares/modulesystem/Preset.cpp | 25 ++++++++++++++++++++++-- src/libcalamares/modulesystem/Preset.h | 18 +++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/libcalamares/modulesystem/Config.cpp b/src/libcalamares/modulesystem/Config.cpp index 75c54badf..701fbec74 100644 --- a/src/libcalamares/modulesystem/Config.cpp +++ b/src/libcalamares/modulesystem/Config.cpp @@ -60,5 +60,20 @@ Config::loadPresets( const QVariantMap& configurationMap, const QStringList& rec = std::make_unique< Presets >( CalamaresUtils::getSubMap( configurationMap, key, bogus ), recognizedKeys ); } +bool +Config::isEditable( const QString& fieldName ) const +{ + if ( m_unlocked ) + { + return true; + } + if ( d && d->m_presets ) + { + return d->m_presets->isEditable( fieldName ); + } + return true; +} + + } // namespace ModuleSystem } // namespace Calamares diff --git a/src/libcalamares/modulesystem/Config.h b/src/libcalamares/modulesystem/Config.h index 9bc0705c6..d0f5fa51f 100644 --- a/src/libcalamares/modulesystem/Config.h +++ b/src/libcalamares/modulesystem/Config.h @@ -34,6 +34,7 @@ namespace ModuleSystem */ class DLLEXPORT Config : public QObject { + Q_OBJECT public: Config( QObject* parent = nullptr ); ~Config() override; @@ -46,6 +47,16 @@ public: */ virtual void setConfigurationMap( const QVariantMap& ) = 0; +public Q_SLOTS: + /** @brief Checks if a @p fieldName is editable according to presets + * + * If the field is named as a preset, **and** the field is set + * to not-editable, returns @c false. Otherwise, return @c true. + * Calling this with an unknown field (one for which no presets + * are accepted) will print a warning and return @c true. + */ + bool isEditable( const QString& fieldName ) const; + protected: void loadPresets( const QVariantMap& configurationMap ); void loadPresets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ); @@ -53,6 +64,7 @@ protected: private: class Private; std::unique_ptr< Private > d; + bool m_unlocked = false; }; } // namespace ModuleSystem } // namespace Calamares diff --git a/src/libcalamares/modulesystem/Preset.cpp b/src/libcalamares/modulesystem/Preset.cpp index 8240e23e7..9685a6b68 100644 --- a/src/libcalamares/modulesystem/Preset.cpp +++ b/src/libcalamares/modulesystem/Preset.cpp @@ -33,7 +33,11 @@ loadPresets( Calamares::ModuleSystem::Presets& preset, } } -Calamares::ModuleSystem::Presets::Presets( const QVariantMap& configurationMap ) +namespace Calamares +{ +namespace ModuleSystem +{ +Presets::Presets( const QVariantMap& configurationMap ) { @@ -41,9 +45,26 @@ Calamares::ModuleSystem::Presets::Presets( const QVariantMap& configurationMap ) loadPresets( *this, configurationMap, []( const QString& ) { return true; } ); } -Calamares::ModuleSystem::Presets::Presets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ) +Presets::Presets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ) { reserve( recognizedKeys.size() ); loadPresets( *this, configurationMap, [&recognizedKeys]( const QString& s ) { return recognizedKeys.contains( s ); } ); } + +bool +Presets::isEditable( const QString& fieldName ) const +{ + for ( const auto& p : *this ) + { + if ( p.fieldName == fieldName ) + { + return p.editable; + } + } + return true; +} + + +} // namespace ModuleSystem +} // namespace Calamares diff --git a/src/libcalamares/modulesystem/Preset.h b/src/libcalamares/modulesystem/Preset.h index 1744313ae..59c308b92 100644 --- a/src/libcalamares/modulesystem/Preset.h +++ b/src/libcalamares/modulesystem/Preset.h @@ -48,8 +48,26 @@ struct PresetField class Presets : public QVector< PresetField > { public: + /** @brief Reads preset entries from the map + * + * The map's keys are used as field name, and each value entry + * should specify an initial value and whether the entry is editable. + * Fields are editable by default. + */ explicit Presets( const QVariantMap& configurationMap ); + /** @brief Reads preset entries from the @p configurationMap + * + * As above, but only field names that occur in @p recognizedKeys + * are kept; others are discarded. + */ Presets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ); + + /** @brief Is the given @p fieldName editable? + * + * Fields are editable by default, so if there is no explicit setting, + * returns @c true. + */ + bool isEditable( const QString& fieldName ) const; }; } // namespace ModuleSystem } // namespace Calamares From d8dff3dc6554b1fe00e3f743dc41f4f747a5088b Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 17:20:36 +0100 Subject: [PATCH 07/14] [libcalamares] Replace loadPresets() with an applicative style Build up the list of known presets by what the Config-object expects, not by what the Config file provides. This allows early detection of mis-matched configurations. Presets can only apply to Q_PROPERTY properties, and the preset must match the property name. --- src/libcalamares/modulesystem/Config.cpp | 67 +++++++++++++++--------- src/libcalamares/modulesystem/Config.h | 24 ++++++++- src/libcalamares/modulesystem/Preset.cpp | 1 + src/libcalamares/modulesystem/Preset.h | 12 ++++- src/modules/users/Config.cpp | 13 ++--- 5 files changed, 83 insertions(+), 34 deletions(-) diff --git a/src/libcalamares/modulesystem/Config.cpp b/src/libcalamares/modulesystem/Config.cpp index 701fbec74..af1536129 100644 --- a/src/libcalamares/modulesystem/Config.cpp +++ b/src/libcalamares/modulesystem/Config.cpp @@ -10,6 +10,7 @@ #include "Config.h" #include "Preset.h" +#include "utils/Logger.h" #include "utils/Variant.h" namespace Calamares @@ -33,47 +34,63 @@ Config::Config( QObject* parent ) Config::~Config() {} -void -Config::loadPresets( const QVariantMap& configurationMap ) +bool +Config::isEditable( const QString& fieldName ) const { - const QString key( "presets" ); - if ( !configurationMap.contains( key ) ) + if ( m_unlocked ) { - d->m_presets.reset(); - return; + return true; } - bool bogus = true; - d->m_presets = std::make_unique< Presets >( CalamaresUtils::getSubMap( configurationMap, key, bogus ) ); + if ( d && d->m_presets ) + { + return d->m_presets->isEditable( fieldName ); + } + else + { + cWarning() << "Checking isEditable, but no presets are configured."; + } + return true; } -void -Config::loadPresets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ) +Config::ApplyPresets::ApplyPresets( Calamares::ModuleSystem::Config& c, const QVariantMap& configurationMap ) + : m_c( c ) + , m_bogus( true ) + , m_map( CalamaresUtils::getSubMap( configurationMap, "presets", m_bogus ) ) { - const QString key( "presets" ); - if ( !configurationMap.contains( key ) ) + c.m_unlocked = true; + if ( !c.d->m_presets ) { - d->m_presets.reset(); - return; + c.d->m_presets = std::make_unique< Presets >(); } - bool bogus = true; - d->m_presets - = std::make_unique< Presets >( CalamaresUtils::getSubMap( configurationMap, key, bogus ), recognizedKeys ); } -bool -Config::isEditable( const QString& fieldName ) const + +Config::ApplyPresets& +Config::ApplyPresets::apply( const char* fieldName ) { - if ( m_unlocked ) + const auto prop = m_c.property( fieldName ); + if ( !prop.isValid() ) { - return true; + cWarning() << "Applying invalid property" << fieldName; } - if ( d && d->m_presets ) + else { - return d->m_presets->isEditable( fieldName ); + const QString key( fieldName ); + if ( !key.isEmpty() && m_map.contains( key ) ) + { + QVariantMap m = CalamaresUtils::getSubMap( m_map, key, m_bogus ); + QVariant value = m[ "value" ]; + bool editable = CalamaresUtils::getBool( m, "editable", true ); + + if ( value.isValid() ) + { + m_c.setProperty( fieldName, value ); + } + m_c.d->m_presets->append( PresetField { key, value, editable } ); + } } - return true; + return *this; } - } // namespace ModuleSystem } // namespace Calamares diff --git a/src/libcalamares/modulesystem/Config.h b/src/libcalamares/modulesystem/Config.h index d0f5fa51f..c38b0c11c 100644 --- a/src/libcalamares/modulesystem/Config.h +++ b/src/libcalamares/modulesystem/Config.h @@ -58,8 +58,28 @@ public Q_SLOTS: bool isEditable( const QString& fieldName ) const; protected: - void loadPresets( const QVariantMap& configurationMap ); - void loadPresets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ); + friend class ApplyPresets; + /** @brief "Builder" class for presets + * + * Derived classes should instantiate this (with themselves, + * and the whole configuration map that is passed to + * setConfigurationMap()) and then call .apply() to apply + * the presets specified in the configuration to the **named** + * QObject properties. + */ + class ApplyPresets + { + public: + ApplyPresets( Config& c, const QVariantMap& configurationMap ); + ~ApplyPresets() { m_c.m_unlocked = false; } + + ApplyPresets& apply( const char* fieldName ); + + private: + Config& m_c; + bool m_bogus = true; + const QVariantMap m_map; + }; private: class Private; diff --git a/src/libcalamares/modulesystem/Preset.cpp b/src/libcalamares/modulesystem/Preset.cpp index 9685a6b68..3a54c9e68 100644 --- a/src/libcalamares/modulesystem/Preset.cpp +++ b/src/libcalamares/modulesystem/Preset.cpp @@ -62,6 +62,7 @@ Presets::isEditable( const QString& fieldName ) const return p.editable; } } + cWarning() << "Checking isEditable for unknown field" << fieldName; return true; } diff --git a/src/libcalamares/modulesystem/Preset.h b/src/libcalamares/modulesystem/Preset.h index 59c308b92..f7b5023e5 100644 --- a/src/libcalamares/modulesystem/Preset.h +++ b/src/libcalamares/modulesystem/Preset.h @@ -36,8 +36,10 @@ namespace ModuleSystem struct PresetField { QString fieldName; - QString value; + QVariant value; bool editable = true; + + bool isValid() const { return !fieldName.isEmpty(); } }; /** @brief All the presets for one UI entity @@ -62,6 +64,14 @@ public: */ Presets( const QVariantMap& configurationMap, const QStringList& recognizedKeys ); + /** @brief Creates an empty presets map + * + * This constructor is primarily intended for use by the ApplyPresets + * helper class, which will reserve suitable space and load + * presets on-demand. + */ + Presets() = default; + /** @brief Is the given @p fieldName editable? * * Fields are editable by default, so if there is no explicit setting, diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 0fcf745fa..047b8d516 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -183,7 +183,7 @@ Config::setSudoersGroup( const QString& group ) void Config::setLoginName( const QString& login ) { - if ( login != m_loginName ) + if ( login != m_loginName && isEditable( QStringLiteral( "loginName" ) ) ) { m_customLoginName = !login.isEmpty(); m_loginName = login; @@ -393,6 +393,11 @@ makeHostnameSuggestion( const QStringList& parts ) void Config::setFullName( const QString& name ) { + if ( !isEditable( QStringLiteral( "fullName" ) ) ) + { + return; + } + if ( name.isEmpty() && !m_fullName.isEmpty() ) { if ( !m_customHostName ) @@ -837,11 +842,7 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) updateGSAutoLogin( doAutoLogin(), loginName() ); checkReady(); - loadPresets( configurationMap, - { - "fullname", - "loginname", - } ); + ApplyPresets( *this, configurationMap ).apply( "fullName" ).apply( "loginName" ); } void From 2e90a8d8298e6c902e10dc930945dbcdb8517c09 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 18:42:37 +0100 Subject: [PATCH 08/14] [libcalamares] Report preset mis-configurations - warn about fields applied twice (program error) - warn about fields not used (configuration error) - add operator<< for "clean" looking preset application --- src/libcalamares/modulesystem/Config.cpp | 28 +++++++++++++++++++++++- src/libcalamares/modulesystem/Config.h | 17 +++++++++++++- src/libcalamares/modulesystem/Preset.cpp | 15 +++++++++++-- src/libcalamares/modulesystem/Preset.h | 6 +++++ src/modules/users/Config.cpp | 3 ++- 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/libcalamares/modulesystem/Config.cpp b/src/libcalamares/modulesystem/Config.cpp index af1536129..71210a9cb 100644 --- a/src/libcalamares/modulesystem/Config.cpp +++ b/src/libcalamares/modulesystem/Config.cpp @@ -64,6 +64,28 @@ Config::ApplyPresets::ApplyPresets( Calamares::ModuleSystem::Config& c, const QV } } +Config::ApplyPresets::~ApplyPresets() +{ + m_c.m_unlocked = false; + + // Check that there's no **settings** (from the configuration map) + // that have not been consumed by apply() -- if they are there, + // that means the configuration map specifies things that the + // Config object does not expect. + bool haveWarned = false; + for ( const auto& k : m_map.keys() ) + { + if ( !m_c.d->m_presets->find( k ).isValid() ) + { + if ( !haveWarned ) + { + cWarning() << "Preset configuration contains unused keys"; + haveWarned = true; + } + cDebug() << Logger::SubEntry << "Unused key" << k; + } + } +} Config::ApplyPresets& Config::ApplyPresets::apply( const char* fieldName ) @@ -76,7 +98,11 @@ Config::ApplyPresets::apply( const char* fieldName ) else { const QString key( fieldName ); - if ( !key.isEmpty() && m_map.contains( key ) ) + if ( !key.isEmpty() && m_c.d->m_presets->find( key ).isValid() ) + { + cWarning() << "Applying duplicate property" << fieldName; + } + else if ( !key.isEmpty() && m_map.contains( key ) ) { QVariantMap m = CalamaresUtils::getSubMap( m_map, key, m_bogus ); QVariant value = m[ "value" ]; diff --git a/src/libcalamares/modulesystem/Config.h b/src/libcalamares/modulesystem/Config.h index c38b0c11c..c31433e1c 100644 --- a/src/libcalamares/modulesystem/Config.h +++ b/src/libcalamares/modulesystem/Config.h @@ -70,10 +70,25 @@ protected: class ApplyPresets { public: + /** @brief Create a preset-applier for this config + * + * The @p configurationMap should be the one passed in to + * setConfigurationMap() . Presets are extracted from the + * standard key *presets* and can be applied to the configuration + * with apply() or operator<<. + */ ApplyPresets( Config& c, const QVariantMap& configurationMap ); - ~ApplyPresets() { m_c.m_unlocked = false; } + ~ApplyPresets(); + /** @brief Add a preset for the given @p fieldName + * + * This checks for preset-entries in the configuration map that was + * passed in to the constructor. + */ ApplyPresets& apply( const char* fieldName ); + /** @brief Alternate way of writing apply() + */ + ApplyPresets& operator<<( const char* fieldName ) { return apply( fieldName ); } private: Config& m_c; diff --git a/src/libcalamares/modulesystem/Preset.cpp b/src/libcalamares/modulesystem/Preset.cpp index 3a54c9e68..a2e3f3264 100644 --- a/src/libcalamares/modulesystem/Preset.cpp +++ b/src/libcalamares/modulesystem/Preset.cpp @@ -38,8 +38,6 @@ namespace Calamares namespace ModuleSystem { Presets::Presets( const QVariantMap& configurationMap ) - - { reserve( configurationMap.count() ); loadPresets( *this, configurationMap, []( const QString& ) { return true; } ); @@ -66,6 +64,19 @@ Presets::isEditable( const QString& fieldName ) const return true; } +PresetField +Presets::find( const QString& fieldName ) const +{ + for ( const auto& p : *this ) + { + if ( p.fieldName == fieldName ) + { + return p; + } + } + + return PresetField(); +} } // namespace ModuleSystem } // namespace Calamares diff --git a/src/libcalamares/modulesystem/Preset.h b/src/libcalamares/modulesystem/Preset.h index f7b5023e5..b768a31c9 100644 --- a/src/libcalamares/modulesystem/Preset.h +++ b/src/libcalamares/modulesystem/Preset.h @@ -78,6 +78,12 @@ public: * returns @c true. */ bool isEditable( const QString& fieldName ) const; + + /** @brief Finds the settings for a field @p fieldName + * + * If there is no such field, returns an invalid PresetField. + */ + PresetField find( const QString& fieldName ) const; }; } // namespace ModuleSystem } // namespace Calamares diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 047b8d516..4e02cdcdf 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -842,7 +842,8 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) updateGSAutoLogin( doAutoLogin(), loginName() ); checkReady(); - ApplyPresets( *this, configurationMap ).apply( "fullName" ).apply( "loginName" ); + ApplyPresets( *this, configurationMap ) << "fullName" + << "loginName"; } void From 941cc9c48b7d2e4ec4a369a6e8230144c9a21638 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 18:44:14 +0100 Subject: [PATCH 09/14] [users] Match presets to the actual name of fields --- src/modules/users/users.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index b137a135a..87227898f 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -161,9 +161,9 @@ setHostname: EtcFile writeHostsFile: true presets: - fullname: + fullName: value: "OEM User" editable: false - loginname: + loginName: value: "oem" editable: false From 9fcf9b5fa85822cd2951bb6e46d3a6dadbc5ddad Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Mar 2021 22:27:08 +0100 Subject: [PATCH 10/14] [users] Pick up values from Config object on startup - Previously, we 'knew' that the values in Config were empty, so didn't have to set them from the Config when building the (widget) page --- src/modules/users/UsersPage.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 6ea03f8ef..9aac94779 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -101,6 +101,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) connect( config, &Config::rootPasswordSecondaryChanged, ui->textBoxVerifiedRootPassword, &QLineEdit::setText ); connect( config, &Config::rootPasswordStatusChanged, this, &UsersPage::reportRootPasswordStatus ); + ui->textBoxFullName->setText( config->fullName() ); connect( ui->textBoxFullName, &QLineEdit::textEdited, config, &Config::setFullName ); connect( config, &Config::fullNameChanged, this, &UsersPage::onFullNameTextEdited ); @@ -108,6 +109,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) connect( config, &Config::hostNameChanged, ui->textBoxHostName, &QLineEdit::setText ); connect( config, &Config::hostNameStatusChanged, this, &UsersPage::reportHostNameStatus ); + ui->textBoxLoginName->setText( config->loginName() ); connect( ui->textBoxLoginName, &QLineEdit::textEdited, config, &Config::setLoginName ); connect( config, &Config::loginNameChanged, ui->textBoxLoginName, &QLineEdit::setText ); connect( config, &Config::loginNameStatusChanged, this, &UsersPage::reportLoginNameStatus ); From 3ea796d0099e7d8c00ed097816cebf1f439e5648 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Mar 2021 12:27:53 +0100 Subject: [PATCH 11/14] [users] 'undo' changes to values if the UI is wonky - you can still call set*(), eg. from the UI, when the field is not editable. Although the code previously ignored the change, this would lead to a mismatch between what the UI is showing (the changed value) and what the Config has (old value). Emit a changed-signal (notify) with the old value so that the UI is changed *back* as soon as possible. --- src/modules/users/Config.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 4e02cdcdf..71bd48725 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef HAVE_ICU #include @@ -183,7 +184,13 @@ Config::setSudoersGroup( const QString& group ) void Config::setLoginName( const QString& login ) { - if ( login != m_loginName && isEditable( QStringLiteral( "loginName" ) ) ) + if ( !isEditable( QStringLiteral( "loginName" ) ) ) + { + // Should not have arrived here anyway + QTimer::singleShot( 0, this, [=]() { emit loginNameChanged( m_loginName ); } ); + return; + } + if ( login != m_loginName ) { m_customLoginName = !login.isEmpty(); m_loginName = login; @@ -395,6 +402,8 @@ Config::setFullName( const QString& name ) { if ( !isEditable( QStringLiteral( "fullName" ) ) ) { + // Should not have arrived here anyway + QTimer::singleShot( 0, this, [=]() { emit fullNameChanged( m_fullName ); } ); return; } From b4a21d7acacf1f0be2d42cdb41fb7ef6403579e7 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Mar 2021 13:30:26 +0100 Subject: [PATCH 12/14] [libcalamares] Add macro CONFIG_PREVENT_EDITING to handle uneditable fields Boilerplate code for avoiding accidental setting of an internal field when the UI is editable and the underlying data isn't. --- src/libcalamares/modulesystem/Config.h | 40 ++++++++++++++++++++++++++ src/modules/users/Config.cpp | 15 ++-------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/libcalamares/modulesystem/Config.h b/src/libcalamares/modulesystem/Config.h index c31433e1c..5facae3cd 100644 --- a/src/libcalamares/modulesystem/Config.h +++ b/src/libcalamares/modulesystem/Config.h @@ -54,6 +54,25 @@ public Q_SLOTS: * to not-editable, returns @c false. Otherwise, return @c true. * Calling this with an unknown field (one for which no presets * are accepted) will print a warning and return @c true. + * + * @see CONFIG_PREVENT_EDITING + * + * Most setters will call isEditable() to check if the field should + * be editable. Do not count on the setter not being called: the + * UI might not have set the field to fixed / constant / not-editable + * and then you can have the setter called by changes in the UI. + * + * To prevent the UI from changing **and** to make sure that the UI + * reflects the unchanged value (rather than the changed value it + * sent to the Config object), use CONFIG_PREVENT_EDITING, like so: + * + * CONFIG_PREVENT_EDITING( type, "propertyName" ); + * + * The ; is necessary. is the type of the property; for instance + * QString. The name of the property is a (constant) string. The + * macro will return (out of the setter it is used in) if the field + * is not editable, and will send a notification event with the old + * value as soon as the event loop resumes. */ bool isEditable( const QString& fieldName ) const; @@ -104,4 +123,25 @@ private: } // namespace ModuleSystem } // namespace Calamares +/// @see Config::isEditable() +// +// This needs to be a macro, because Q_ARG() is a macro that stringifies +// the type name. +#define CONFIG_PREVENT_EDITING( type, fieldName ) \ + do \ + { \ + if ( !isEditable( QStringLiteral( fieldName ) ) ) \ + { \ + auto prop = property( fieldName ); \ + const auto& metaobject = metaObject(); \ + auto metaprop = metaobject->property( metaobject->indexOfProperty( fieldName ) ); \ + if ( metaprop.hasNotifySignal() ) \ + { \ + metaprop.notifySignal().invoke( this, Qt::QueuedConnection, Q_ARG( type, prop.value< type >() ) ); \ + } \ + return; \ + } \ + } while ( 0 ) + + #endif diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 71bd48725..7aa1cb65c 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -184,12 +185,7 @@ Config::setSudoersGroup( const QString& group ) void Config::setLoginName( const QString& login ) { - if ( !isEditable( QStringLiteral( "loginName" ) ) ) - { - // Should not have arrived here anyway - QTimer::singleShot( 0, this, [=]() { emit loginNameChanged( m_loginName ); } ); - return; - } + CONFIG_PREVENT_EDITING( QString, "loginName" ); if ( login != m_loginName ) { m_customLoginName = !login.isEmpty(); @@ -400,12 +396,7 @@ makeHostnameSuggestion( const QStringList& parts ) void Config::setFullName( const QString& name ) { - if ( !isEditable( QStringLiteral( "fullName" ) ) ) - { - // Should not have arrived here anyway - QTimer::singleShot( 0, this, [=]() { emit fullNameChanged( m_fullName ); } ); - return; - } + CONFIG_PREVENT_EDITING( QString, "fullName" ); if ( name.isEmpty() && !m_fullName.isEmpty() ) { From 7bae625f4655721605fa8d16f4f5bf4d4f9975da Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Mar 2021 14:14:29 +0100 Subject: [PATCH 13/14] [users] Pick up UI changes based on the values from Config --- src/modules/users/UsersPage.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 9aac94779..1717f3211 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -142,6 +142,8 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) CALAMARES_RETRANSLATE_SLOT( &UsersPage::retranslate ) onReuseUserPasswordChanged( m_config->reuseUserPasswordForRoot() ); + onFullNameTextEdited( m_config->fullName() ); + reportLoginNameStatus( m_config->loginNameStatus() ); } UsersPage::~UsersPage() From caf18321df9e9c08abf3f8db12d23bd3871f022a Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Mar 2021 14:20:10 +0100 Subject: [PATCH 14/14] [users] Adjust UI to is-field-editable based on presets --- src/modules/users/UsersPage.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 1717f3211..be9e63498 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -144,6 +144,9 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) onReuseUserPasswordChanged( m_config->reuseUserPasswordForRoot() ); onFullNameTextEdited( m_config->fullName() ); reportLoginNameStatus( m_config->loginNameStatus() ); + + ui->textBoxLoginName->setEnabled( m_config->isEditable( "loginName" ) ); + ui->textBoxFullName->setEnabled( m_config->isEditable( "fullName" ) ); } UsersPage::~UsersPage()