diff --git a/CHANGES-3.2 b/CHANGES-3.2 index 3171a939a..c590fe13f 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -8,6 +8,22 @@ contributors are listed. Note that Calamares does not have a historical changelog -- this log starts with version 3.2.0. The release notes on the website will have to do for older versions. +# 3.2.57 (unreleased) # + +This release contains contributions from (alphabetically by first name): + - Arjen Balfoort (new contributor! Welcome!) + - Victor Fuentes + +## Core ## + - No core changes yet + +## Modules ## + - *fstab* and *luksbootkeyfile* have better support for an **un**encrypted + `/boot` partition. #1931 (thanks Arjen) + - *packagechooser* and *packagechooserq* can now be given a custom name + in the side-panel. #1932 (thanks Victor) + + # 3.2.56 (2022-04-22) # As of this release, Calamares 3.2 development is winding down. The diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index dd4b41823..cc22746dd 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -798,8 +798,12 @@ def run(): fw_type = libcalamares.globalstorage.value("firmwareType") - if (libcalamares.globalstorage.value("bootLoader") is None and fw_type != "efi"): - libcalamares.utils.warning( "Non-EFI system, and no bootloader is set." ) + if libcalamares.globalstorage.value("bootLoader") is None: + # Don't want a bootloader, but do log that this has an effect: + if fw_type != "efi": + libcalamares.utils.warning( "Non-EFI system, and no bootloader is set." ) + else: + libcalamares.utils.warning( "EFI system, and no bootloader is set." ) return None partitions = libcalamares.globalstorage.value("partitions") diff --git a/src/modules/fstab/main.py b/src/modules/fstab/main.py old mode 100644 new mode 100755 index b76716749..fe94e5553 --- a/src/modules/fstab/main.py +++ b/src/modules/fstab/main.py @@ -156,11 +156,23 @@ class FstabGenerator(object): if not mapper_name or not luks_uuid: return None + password = "/crypto_keyfile.bin" + crypttab_options = self.crypttab_options + + # Set crypttab password for partition to none and remove crypttab options + # on root partition when /boot is unencrypted + if partition["mountPoint"] == "/": + if any([p["mountPoint"] == "/boot" + and "luksMapperName" not in p + for p in self.partitions]): + password = "none" + crypttab_options = "" + return dict( name=mapper_name, device="UUID=" + luks_uuid, - password="/crypto_keyfile.bin", - options=self.crypttab_options, + password=password, + options=crypttab_options, ) def print_crypttab_line(self, dct, file=None): @@ -218,7 +230,7 @@ class FstabGenerator(object): # Some "fs" names need special handling in /etc/fstab, so remap them. filesystem = partition["fs"].lower() filesystem = FS_MAP.get(filesystem, filesystem) - has_luks = "luksMapperName" in partition + luks_mapper_name = partition.get("luksMapperName", None) mount_point = partition["mountPoint"] disk_name = disk_name_for_partition(partition) is_ssd = disk_name in self.ssd_disks @@ -247,13 +259,23 @@ class FstabGenerator(object): if mount_point == "/": self.root_is_ssd = is_ssd - if has_luks: - device = "/dev/mapper/" + partition["luksMapperName"] + # If there's a set-and-not-empty subvolume set, add it + if filesystem == "btrfs" and partition.get("subvol",None): + options = "subvol={},".format(partition["subvol"]) + options + + device = None + if luks_mapper_name: + device = "/dev/mapper/" + luks_mapper_name elif partition["uuid"]: device = "UUID=" + partition["uuid"] else: device = partition["device"] + if not device: + # TODO: we get here when the user mounted a previously encrypted partition + # This should be catched early in the process + return None + return dict(device=device, mount_point=mount_point, fs=filesystem, diff --git a/src/modules/luksbootkeyfile/CMakeLists.txt b/src/modules/luksbootkeyfile/CMakeLists.txt index 6f5d45062..dff682521 100644 --- a/src/modules/luksbootkeyfile/CMakeLists.txt +++ b/src/modules/luksbootkeyfile/CMakeLists.txt @@ -11,3 +11,10 @@ calamares_add_plugin(luksbootkeyfile SHARED_LIB NO_CONFIG ) + +calamares_add_test( + luksbootkeyfiletest + SOURCES + Tests.cpp + LuksBootKeyFileJob.cpp +) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp index 137cb750d..e8dfd4724 100644 --- a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -150,26 +150,50 @@ setupLuks( const LuksDevice& d ) } static QVariantList -partitions() +partitionsFromGlobalStorage() { Calamares::GlobalStorage* globalStorage = Calamares::JobQueue::instance()->globalStorage(); return globalStorage->value( QStringLiteral( "partitions" ) ).toList(); } -static bool +/// Checks if the partition (represented by @p map) mounts to the given @p path +STATICTEST bool +hasMountPoint( const QVariantMap& map, const QString& path ) +{ + const auto v = map.value( QStringLiteral( "mountPoint" ) ); + return v.isValid() && QDir::cleanPath( v.toString() ) == path; +} + +STATICTEST bool +isEncrypted( const QVariantMap& map ) +{ + return map.contains( QStringLiteral( "luksMapperName" ) ); +} + +/// Checks for any partition satisfying @p pred +STATICTEST bool +anyPartition( bool ( *pred )( const QVariantMap& ) ) +{ + const auto partitions = partitionsFromGlobalStorage(); + return std::find_if( partitions.cbegin(), + partitions.cend(), + [ &pred ]( const QVariant& partitionVariant ) { return pred( partitionVariant.toMap() ); } ) + != partitions.cend(); +} + +STATICTEST bool hasUnencryptedSeparateBoot() { - const QVariantList partitions = ::partitions(); - for ( const QVariant& partition : partitions ) - { - QVariantMap partitionMap = partition.toMap(); - QString mountPoint = partitionMap.value( QStringLiteral( "mountPoint" ) ).toString(); - if ( QDir::cleanPath( mountPoint ) == QStringLiteral( "/boot" ) ) - { - return !partitionMap.contains( QStringLiteral( "luksMapperName" ) ); - } - } - return false; + return anyPartition( + []( const QVariantMap& partition ) + { return hasMountPoint( partition, QStringLiteral( "/boot" ) ) && !isEncrypted( partition ); } ); +} + +STATICTEST bool +hasEncryptedRoot() +{ + return anyPartition( []( const QVariantMap& partition ) + { return hasMountPoint( partition, QStringLiteral( "/" ) ) && isEncrypted( partition ); } ); } Calamares::JobResult @@ -218,7 +242,8 @@ LuksBootKeyFileJob::exec() } // /boot partition is not encrypted, keyfile must not be used - if ( hasUnencryptedSeparateBoot() ) + // But only if root partition is not encrypted + if ( hasUnencryptedSeparateBoot() && !hasEncryptedRoot() ) { cDebug() << Logger::SubEntry << "/boot partition is not encrypted, skipping keyfile creation."; return Calamares::JobResult::ok(); @@ -241,6 +266,12 @@ LuksBootKeyFileJob::exec() for ( const auto& d : s.devices ) { + // Skip setupLuks for root partition if system has an unencrypted /boot + if ( d.isRoot && hasUnencryptedSeparateBoot() ) + { + continue; + } + if ( !setupLuks( d ) ) return Calamares::JobResult::error( tr( "Encrypted rootfs setup error" ), diff --git a/src/modules/luksbootkeyfile/Tests.cpp b/src/modules/luksbootkeyfile/Tests.cpp new file mode 100644 index 000000000..6b6c7be81 --- /dev/null +++ b/src/modules/luksbootkeyfile/Tests.cpp @@ -0,0 +1,169 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2022 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "utils/Logger.h" + +#include + +#undef STATICTEST +#define STATICTEST extern + +// Implementation details +STATICTEST bool hasMountPoint( const QVariantMap& map, const QString& path ); + +STATICTEST bool isEncrypted( const QVariantMap& map ); + +STATICTEST bool anyPartition( bool ( *pred )( const QVariantMap& ) ); + +STATICTEST bool hasUnencryptedSeparateBoot(); + +STATICTEST bool hasEncryptedRoot(); + +class LuksBootKeyFileTests : public QObject +{ + Q_OBJECT +public: + LuksBootKeyFileTests() {} + ~LuksBootKeyFileTests() override {} + +private Q_SLOTS: + void initTestCase(); + + void testMountPoint(); + void testIsEncrypted(); + void testAnyPartition(); +}; + +void +LuksBootKeyFileTests::initTestCase() +{ + Logger::setupLogLevel( Logger::LOGDEBUG ); + cDebug() << "LuksBootKeyFile test started."; + + if ( !Calamares::JobQueue::instance() ) + { + (void)new Calamares::JobQueue(); + } +} + +void +LuksBootKeyFileTests::testMountPoint() +{ + QVariantMap m; // As if this is a partition data + const QString key = QStringLiteral( "mountPoint" ); + const QString boot = QStringLiteral( "/boot" ); + const QString root = QStringLiteral( "/" ); + + QVERIFY( !hasMountPoint( m, QString() ) ); + QVERIFY( !hasMountPoint( m, boot ) ); + + m.insert( key, boot ); + QVERIFY( hasMountPoint( m, boot ) ); + QVERIFY( !hasMountPoint( m, QString() ) ); + QVERIFY( !hasMountPoint( m, root ) ); + + m.insert( key, root ); + QVERIFY( !hasMountPoint( m, boot ) ); + QVERIFY( !hasMountPoint( m, QString() ) ); + QVERIFY( hasMountPoint( m, root ) ); + + m.remove( key ); + QVERIFY( !hasMountPoint( m, root ) ); +} + +void +LuksBootKeyFileTests::testIsEncrypted() +{ + QVariantMap m; // As if this is a partition data + const QString key = QStringLiteral( "luksMapperName" ); + const QString name = QStringLiteral( "any-name" ); + + QVERIFY( !isEncrypted( m ) ); + + // Even an empty string is considered encrypted + m.insert( key, QString() ); + QVERIFY( isEncrypted( m ) ); + + m.insert( key, name ); + QVERIFY( isEncrypted( m ) ); + + m.insert( key, QString() ); + QVERIFY( isEncrypted( m ) ); + + m.remove( key ); + QVERIFY( !isEncrypted( m ) ); +} + + +void +LuksBootKeyFileTests::testAnyPartition() +{ + // This is kind of annoying: we need to build up + // partition data in GS because the functions we're testing + // go straight to GS. + auto* gs = Calamares::JobQueue::instanceGlobalStorage(); + QVERIFY( gs ); + + const QString partitionsKey = QStringLiteral( "partitions" ); + const QString mountPointKey = QStringLiteral( "mountPoint" ); + const QString boot = QStringLiteral( "/boot" ); + const QString root = QStringLiteral( "/" ); + + QVariantList partitions; + QVariantMap p; + QVERIFY( !gs->contains( partitionsKey ) ); + + // Empty list! + QVERIFY( !anyPartition( []( const QVariantMap& ) { return true; } ) ); + + gs->insert( partitionsKey, partitions ); + QVERIFY( !anyPartition( []( const QVariantMap& ) { return true; } ) ); // Still an empty list + + partitions.append( p ); + QCOMPARE( partitions.count(), 1 ); + gs->insert( partitionsKey, partitions ); + QVERIFY( anyPartition( []( const QVariantMap& ) { return true; } ) ); // Now a one-element list + QVERIFY( !anyPartition( []( const QVariantMap& ) { return false; } ) ); // Now a one-element list + + p.insert( mountPointKey, boot ); + QVERIFY( hasMountPoint( p, boot ) ); + partitions.append( p ); + QCOMPARE( partitions.count(), 2 ); + + // Note that GS is not updated yet, so we expect this to fail + QEXPECT_FAIL( "", "GS not updated", Continue ); + QVERIFY( anyPartition( + []( const QVariantMap& partdata ) + { + cDebug() << partdata; + return hasMountPoint( partdata, QStringLiteral( "/boot" ) ); + } ) ); + + gs->insert( partitionsKey, partitions ); // Update GS + QVERIFY( anyPartition( + []( const QVariantMap& partdata ) + { + cDebug() << partdata; + return hasMountPoint( partdata, QStringLiteral( "/boot" ) ); + } ) ); + QVERIFY( !anyPartition( []( const QVariantMap& partdata ) + { return hasMountPoint( partdata, QStringLiteral( "/" ) ); } ) ); + QVERIFY( !anyPartition( []( const QVariantMap& partdata ) { return hasMountPoint( partdata, QString() ); } ) ); + + QVERIFY( !hasEncryptedRoot() ); + QVERIFY( hasUnencryptedSeparateBoot() ); +} + +QTEST_GUILESS_MAIN( LuksBootKeyFileTests ) + +#include "utils/moc-warnings.h" + +#include "Tests.moc" diff --git a/src/modules/packagechooser/Config.cpp b/src/modules/packagechooser/Config.cpp index 491fe5c25..667621597 100644 --- a/src/modules/packagechooser/Config.cpp +++ b/src/modules/packagechooser/Config.cpp @@ -237,6 +237,12 @@ Config::setPackageChoice( const QString& packageChoice ) emit packageChoiceChanged( m_packageChoice.value_or( QString() ) ); } +QString +Config::prettyName() const +{ + return m_stepName ? m_stepName->get() : tr( "Packages" ); +} + QString Config::prettyStatus() const { @@ -343,4 +349,14 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) cWarning() << "Single-selection QML module must use 'Legacy' method."; } } + + bool labels_ok = false; + auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok ); + if ( labels_ok ) + { + if ( labels.contains( "step" ) ) + { + m_stepName = new CalamaresUtils::Locale::TranslatedString( labels, "step" ); + } + } } diff --git a/src/modules/packagechooser/Config.h b/src/modules/packagechooser/Config.h index b04b1c30b..d1b783a8d 100644 --- a/src/modules/packagechooser/Config.h +++ b/src/modules/packagechooser/Config.h @@ -98,6 +98,7 @@ public: QString packageChoice() const { return m_packageChoice.value_or( QString() ); } void setPackageChoice( const QString& packageChoice ); + QString prettyName() const; QString prettyStatus() const; signals: @@ -120,6 +121,7 @@ private: * Reading the property will return an empty QString. */ std::optional< QString > m_packageChoice; + CalamaresUtils::Locale::TranslatedString* m_stepName; // As it appears in the sidebar }; diff --git a/src/modules/packagechooser/PackageChooserViewStep.cpp b/src/modules/packagechooser/PackageChooserViewStep.cpp index 9057004de..c3f2ce6e2 100644 --- a/src/modules/packagechooser/PackageChooserViewStep.cpp +++ b/src/modules/packagechooser/PackageChooserViewStep.cpp @@ -29,7 +29,6 @@ PackageChooserViewStep::PackageChooserViewStep( QObject* parent ) : Calamares::ViewStep( parent ) , m_config( new Config( this ) ) , m_widget( nullptr ) - , m_stepName( nullptr ) { emit nextStatusChanged( false ); } @@ -41,14 +40,13 @@ PackageChooserViewStep::~PackageChooserViewStep() { m_widget->deleteLater(); } - delete m_stepName; } QString PackageChooserViewStep::prettyName() const { - return m_stepName ? m_stepName->get() : tr( "Packages" ); + return m_config->prettyName(); } @@ -139,16 +137,6 @@ PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap m_config->setDefaultId( moduleInstanceKey() ); m_config->setConfigurationMap( configurationMap ); - bool labels_ok = false; - auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok ); - if ( labels_ok ) - { - if ( labels.contains( "step" ) ) - { - m_stepName = new CalamaresUtils::Locale::TranslatedString( labels, "step" ); - } - } - if ( m_widget ) { hookupModel(); diff --git a/src/modules/packagechooser/PackageChooserViewStep.h b/src/modules/packagechooser/PackageChooserViewStep.h index 7561f2bd7..76b35aed8 100644 --- a/src/modules/packagechooser/PackageChooserViewStep.h +++ b/src/modules/packagechooser/PackageChooserViewStep.h @@ -50,7 +50,6 @@ private: Config* m_config; PackageChooserPage* m_widget; - CalamaresUtils::Locale::TranslatedString* m_stepName; // As it appears in the sidebar }; CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory ) diff --git a/src/modules/packagechooserq/PackageChooserQmlViewStep.cpp b/src/modules/packagechooserq/PackageChooserQmlViewStep.cpp index 543c9771d..ae4aa3c48 100644 --- a/src/modules/packagechooserq/PackageChooserQmlViewStep.cpp +++ b/src/modules/packagechooserq/PackageChooserQmlViewStep.cpp @@ -29,7 +29,7 @@ PackageChooserQmlViewStep::PackageChooserQmlViewStep( QObject* parent ) QString PackageChooserQmlViewStep::prettyName() const { - return tr( "Packages" ); + return m_config->prettyName(); } QString diff --git a/src/modules/packagechooserq/packagechooserq.conf b/src/modules/packagechooserq/packagechooserq.conf index 803c6f670..9c1878f34 100644 --- a/src/modules/packagechooserq/packagechooserq.conf +++ b/src/modules/packagechooserq/packagechooserq.conf @@ -42,6 +42,19 @@ # method: legacy +# Human-visible strings in this module. These are all optional. +# The following translated keys are used: +# - *step*, used in the overall progress view (left-hand pane) +# +# Each key can have a [locale] added to it, which is used as +# the translated string for that locale. For the strings +# associated with the "no-selection" item, see *items*, below +# with the explicit item-*id* "". +# +labels: + step: "Packages" + step[nl]: "Pakketten" + # The *packageChoice* value is used for setting the default selection # in the QML view; this should match one of the keys used in the QML # module for package names. diff --git a/src/modules/partition/core/BootLoaderModel.cpp b/src/modules/partition/core/BootLoaderModel.cpp index fd66c8514..f659fc593 100644 --- a/src/modules/partition/core/BootLoaderModel.cpp +++ b/src/modules/partition/core/BootLoaderModel.cpp @@ -3,6 +3,7 @@ * SPDX-FileCopyrightText: 2014 Aurélien Gâteau * SPDX-FileCopyrightText: 2015 Teo Mrnjavac * SPDX-FileCopyrightText: 2019 Adriaan de Groot + * SPDX-FileCopyrightText: 2021 Anubhav Choudhary * SPDX-License-Identifier: GPL-3.0-or-later * * Calamares is Free Software: see the License-Identifier above. @@ -79,7 +80,7 @@ BootLoaderModel::updateInternal() clear(); createMbrItems(); - // An empty model is possible if you don't havee permissions: don't crash though. + // An empty model is possible if you don't have permissions: don't crash though. if ( rowCount() < 1 ) { return; @@ -124,10 +125,10 @@ BootLoaderModel::updateInternal() { appendRow( createBootLoaderItem( partitionText, PartitionInfo::mountPoint( partition ), true ) ); } - - // Create "don't install bootloader" item - appendRow( createBootLoaderItem( tr( "Do not install a boot loader" ), QString(), false ) ); } + // Create "don't install bootloader" item. This is always available, + // also if there was no /boot or / partition found. + appendRow( createBootLoaderItem( tr( "Do not install a boot loader" ), QString(), false ) ); } diff --git a/src/modules/partition/gui/ChoicePage.cpp b/src/modules/partition/gui/ChoicePage.cpp index 2d395764d..dd15fee7e 100644 --- a/src/modules/partition/gui/ChoicePage.cpp +++ b/src/modules/partition/gui/ChoicePage.cpp @@ -3,6 +3,7 @@ * SPDX-FileCopyrightText: 2014-2017 Teo Mrnjavac * SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot * SPDX-FileCopyrightText: 2019 Collabora Ltd + * SPDX-FileCopyrightText: 2021 Anubhav Choudhary * SPDX-License-Identifier: GPL-3.0-or-later * * Calamares is Free Software: see the License-Identifier above. @@ -1021,6 +1022,12 @@ ChoicePage::updateActionChoicePreview( InstallChoice choice ) QLabel* sizeLabel = new QLabel( m_previewAfterFrame ); layout->addWidget( sizeLabel ); sizeLabel->setWordWrap( true ); + + if ( !m_isEfi ) + { + layout->addWidget( createBootloaderPanel() ); + } + connect( m_afterPartitionSplitterWidget, &PartitionSplitterWidget::partitionResized, this, @@ -1079,51 +1086,7 @@ ChoicePage::updateActionChoicePreview( InstallChoice choice ) if ( !m_isEfi ) { - QWidget* eraseWidget = new QWidget; - - QHBoxLayout* eraseLayout = new QHBoxLayout; - eraseWidget->setLayout( eraseLayout ); - eraseLayout->setContentsMargins( 0, 0, 0, 0 ); - QLabel* eraseBootloaderLabel = new QLabel( eraseWidget ); - eraseLayout->addWidget( eraseBootloaderLabel ); - eraseBootloaderLabel->setText( tr( "Boot loader location:" ) ); - - m_bootloaderComboBox = createBootloaderComboBox( eraseWidget ); - connect( m_core->bootLoaderModel(), - &QAbstractItemModel::modelReset, - [ this ]() - { - if ( !m_bootloaderComboBox.isNull() ) - { - Calamares::restoreSelectedBootLoader( *m_bootloaderComboBox, - m_core->bootLoaderInstallPath() ); - } - } ); - connect( - m_core, - &PartitionCoreModule::deviceReverted, - this, - [ this ]( Device* dev ) - { - Q_UNUSED( dev ) - if ( !m_bootloaderComboBox.isNull() ) - { - if ( m_bootloaderComboBox->model() != m_core->bootLoaderModel() ) - { - m_bootloaderComboBox->setModel( m_core->bootLoaderModel() ); - } - - m_bootloaderComboBox->setCurrentIndex( m_lastSelectedDeviceIndex ); - } - }, - Qt::QueuedConnection ); - // ^ Must be Queued so it's sure to run when the widget is already visible. - - eraseLayout->addWidget( m_bootloaderComboBox ); - eraseBootloaderLabel->setBuddy( m_bootloaderComboBox ); - eraseLayout->addStretch(); - - layout->addWidget( eraseWidget ); + layout->addWidget( createBootloaderPanel() ); } m_previewAfterFrame->show(); @@ -1235,35 +1198,6 @@ ChoicePage::setupEfiSystemPartitionSelector() } } - -QComboBox* -ChoicePage::createBootloaderComboBox( QWidget* parent ) -{ - QComboBox* comboForBootloader = new QComboBox( parent ); - comboForBootloader->setModel( m_core->bootLoaderModel() ); - - // When the chosen bootloader device changes, we update the choice in the PCM - connect( comboForBootloader, - QOverload< int >::of( &QComboBox::currentIndexChanged ), - this, - [ this ]( int newIndex ) - { - QComboBox* bootloaderCombo = qobject_cast< QComboBox* >( sender() ); - if ( bootloaderCombo ) - { - QVariant var = bootloaderCombo->itemData( newIndex, BootLoaderModel::BootLoaderPathRole ); - if ( !var.isValid() ) - { - return; - } - m_core->setBootLoaderInstallPath( var.toString() ); - } - } ); - - return comboForBootloader; -} - - static inline void force_uncheck( QButtonGroup* grp, PrettyRadioButton* button ) { @@ -1733,3 +1667,72 @@ ChoicePage::setLastSelectedDeviceIndex( int index ) m_lastSelectedDeviceIndex = index; m_drivesCombo->setCurrentIndex( m_lastSelectedDeviceIndex ); } + +QWidget* +ChoicePage::createBootloaderPanel() +{ + QWidget* panelWidget = new QWidget; + + QHBoxLayout* mainLayout = new QHBoxLayout; + panelWidget->setLayout( mainLayout ); + mainLayout->setContentsMargins( 0, 0, 0, 0 ); + QLabel* widgetLabel = new QLabel( panelWidget ); + mainLayout->addWidget( widgetLabel ); + widgetLabel->setText( tr( "Boot loader location:" ) ); + + QComboBox* comboForBootloader = new QComboBox( panelWidget ); + comboForBootloader->setModel( m_core->bootLoaderModel() ); + + // When the chosen bootloader device changes, we update the choice in the PCM + connect( comboForBootloader, + QOverload< int >::of( &QComboBox::currentIndexChanged ), + this, + [ this ]( int newIndex ) + { + QComboBox* bootloaderCombo = qobject_cast< QComboBox* >( sender() ); + if ( bootloaderCombo ) + { + QVariant var = bootloaderCombo->itemData( newIndex, BootLoaderModel::BootLoaderPathRole ); + if ( !var.isValid() ) + { + return; + } + m_core->setBootLoaderInstallPath( var.toString() ); + } + } ); + m_bootloaderComboBox = comboForBootloader; + + connect( m_core->bootLoaderModel(), + &QAbstractItemModel::modelReset, + [ this ]() + { + if ( !m_bootloaderComboBox.isNull() ) + { + Calamares::restoreSelectedBootLoader( *m_bootloaderComboBox, m_core->bootLoaderInstallPath() ); + } + } ); + connect( + m_core, + &PartitionCoreModule::deviceReverted, + this, + [ this ]( Device* ) + { + if ( !m_bootloaderComboBox.isNull() ) + { + if ( m_bootloaderComboBox->model() != m_core->bootLoaderModel() ) + { + m_bootloaderComboBox->setModel( m_core->bootLoaderModel() ); + } + + m_bootloaderComboBox->setCurrentIndex( m_lastSelectedDeviceIndex ); + } + }, + Qt::QueuedConnection ); + // ^ Must be Queued so it's sure to run when the widget is already visible. + + mainLayout->addWidget( m_bootloaderComboBox ); + widgetLabel->setBuddy( m_bootloaderComboBox ); + mainLayout->addStretch(); + + return panelWidget; +} diff --git a/src/modules/partition/gui/ChoicePage.h b/src/modules/partition/gui/ChoicePage.h index 12222ac63..d1699200a 100644 --- a/src/modules/partition/gui/ChoicePage.h +++ b/src/modules/partition/gui/ChoicePage.h @@ -108,7 +108,12 @@ private: void updateNextEnabled(); void setupChoices(); void checkInstallChoiceRadioButton( Config::InstallChoice choice ); ///< Sets the chosen button to "on" - QComboBox* createBootloaderComboBox( QWidget* parentButton ); + /** @brief Create a panel with "boot loader location:" + * + * Panel + dropdown and handling for model updates. Returns a pointer + * to the panel's widget. + */ + QWidget* createBootloaderPanel(); Device* selectedDevice(); /* Change the UI depending on the device selected. */