Merge branch 'calamares' into improve-partition

main
Adriaan de Groot 4 years ago
commit df20aa9ddb

@ -10,8 +10,11 @@ website will have to do for older versions.
# 3.2.40 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Anubhav Choudhary (SoK success!)
- Erik Dubois
- Lisa Vitolo
- Jerrod Frost (new contributor! welcome!)
- Joe Kamprad
- Lisa Vitolo (blast from the past!)
## Core ##
- The CMake modules for consumption by external modules (e.g. the
@ -22,16 +25,50 @@ This release contains contributions from (alphabetically by first name):
libcalamares to systematically mark filesystem (types) as "in use"
or not. This, in turn, means that modules can depend on that information
for other work (e.g. removing drivers for unused filesystems). #1635
- The "upload log file" now has a configurable log-file-size. (Thanks Anubhav)
## Modules ##
- *displaymanager* example configuration has been shuffled around a bit,
for better results when the live image is running XFCE. Also lists
more potential display managers. #1205 (Thanks Erik)
- The *netinstall* module can now fall back to alternative URLs when
loading groups data. The first URL to yield a non-empty groups
collection is accepted. No changes are needed in the configuration. #1673
- *packagechooser* can now integrate with the *packages* module; that
means you can specify package names to install for a given selection,
and the regular package-installation mechanism will take care of it.
Legacy configurations that use *contextualprocess* are still supported.
See the `packagechooser.conf` file for details. #1550
- A long-neglected pull request from Lisa Vitolo for the *partition*
module -- allowing to set filesystem labels during manual partitioning --
has been revived and merged.
# 3.2.39.3 (2021-04-14) #
A minor bugfix tweak release. Since this contains yet **another**
autologin-related fix, and there is nothing large enough to justify
a 3.2.40 release yet, add it to the growing tail of 3.2.39. (Reported
by Joe Kamprad, #1672). Also fixes a regression from 3.2.28 in
localized packages (e.g. *package-LOCALE* did not work).
# 3.2.39.2 (2021-04-02) #
This is **another** hotfix release for issues around autologin ..
autoLogin, really, since the whole problem is that internal capitalization
changed. An unrelated bug in writing /etc/default/keyboard was
also fixed. (Reported by pcrepix, #1668)
# 3.2.39.1 (2021-03-30) #
This hotfix release corrects a regression in the *displaymanager*
module caused by changes in the *users* module; autologin was
internally renamed and no longer recognized by the *displaymanager*
module. (Reported by Erik Dubois, #1665)
# 3.2.39 (2021-03-19) #
This release contains contributions from (alphabetically by first name):

@ -75,6 +75,12 @@ fi
#
#
BUILDDIR=$(mktemp -d --suffix=-build --tmpdir=.)
KEY_ID="CFDDC96F12B1915C"
# Try to make gpg cache the signing key, so we can leave the process
# to run and sign.
rm -f CHANGES.gpg
gpg -s -u $KEY_ID CHANGES
### Build with default compiler
#
@ -124,7 +130,6 @@ test -n "$V" || { echo "Could not obtain version in $BUILDDIR ." ; exit 1 ; }
#
# This is the signing key ID associated with the GitHub account adriaandegroot,
# which is used to create all "verified" tags in the Calamares repo.
KEY_ID="CFDDC96F12B1915C"
git tag -u "$KEY_ID" -m "Release v$V" "v$V" || { echo "Could not sign tag v$V." ; exit 1 ; }
### Create the tarball
@ -145,6 +150,7 @@ test -d "$TMPDIR" || { echo "Could not create tarball-build directory." ; exit 1
tar xzf "$TAR_FILE" -C "$TMPDIR" || { echo "Could not unpack tarball." ; exit 1 ; }
test -d "$TMPDIR/$TAR_V" || { echo "Tarball did not contain source directory." ; exit 1 ; }
( cd "$TMPDIR/$TAR_V" && cmake . && make -j4 && make test ) || { echo "Tarball build failed in $TMPDIR ." ; exit 1 ; }
gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball
### Cleanup
#
@ -157,7 +163,6 @@ rm -rf "$TMPDIR" # From tarball
cat <<EOF
# Next steps for this release:
git push origin v$V
gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball
# Upload tarball $TAR_FILE and the signature $TAR_FILE.asc
# Announce via https://github.com/calamares/calamares/releases/new
# SHA256: $SHA256

@ -5,7 +5,7 @@
#
# Translators:
# Adriaan de Groot <groot@kde.org>, 2020
# Christian Spaan, 2020
# Gustav Gyges, 2020
# Andreas Eitel <github-aneitel@online.de>, 2020
#
#, fuzzy

@ -227,6 +227,12 @@ slideshowAPI: 2
# - url : Defines the address of pastebin service to be used.
# Takes string as input. Important bits are the host and port,
# the scheme is not used.
# - sizeLimit : Defines maximum size limit (in KiB) of log file to be pasted.
# Takes integer as input. If < 0, no limit will be forced,
# else only last (approximately) 'n' KiB of log file will be pasted.
# Please note that upload size may be slightly over the limit (due
# to last minute logging), so provide a suitable value.
uploadServer :
type : "fiche"
url : "http://termbin.com:9999"
sizeLimit : -1

@ -10,7 +10,7 @@
#include "CalamaresApplication.h"
#include "CalamaresConfig.h"
#include "CalamaresVersion.h"
#include "CalamaresVersionX.h"
#include "CalamaresWindow.h"
#include "progresstree/ProgressTreeView.h"

@ -215,10 +215,49 @@ calamares_add_test(
${geoip_src}
)
function ( calamares_qrc_translations basename )
set( NAME ${ARGV0} )
set( options "" )
set( oneValueArgs SUBDIRECTORY OUTPUT_VARIABLE )
set( multiValueArgs LANGUAGES )
cmake_parse_arguments( _qrt "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
if( NOT _qrt_OUTPUT_VARIABLE )
set( _qrt_OUTPUT_VARIABLE "qrc_translations_${basename}" )
endif()
set( translations_qrc_infile ${CMAKE_CURRENT_BINARY_DIR}/${basename}.qrc )
set( translations_qrc_outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${basename}.cxx )
# Must use this variable name because of the @ substitution
set( calamares_i18n_qrc_content "" )
set( calamares_i18n_ts_filelist "" )
foreach( lang ${_qrt_LANGUAGES} )
string( APPEND calamares_i18n_qrc_content "<file>${basename}_${lang}.qm</file>" )
list( APPEND calamares_i18n_ts_filelist "${CMAKE_CURRENT_SOURCE_DIR}/${_qrt_SUBDIRECTORY}/${basename}_${lang}.ts" )
endforeach()
configure_file( ${CMAKE_SOURCE_DIR}/lang/calamares_i18n.qrc.in ${translations_qrc_infile} @ONLY )
qt5_add_translation(QM_FILES ${calamares_i18n_ts_filelist})
# Run the resource compiler (rcc_options should already be set)
add_custom_command(
OUTPUT ${translations_qrc_outfile}
COMMAND "${Qt5Core_RCC_EXECUTABLE}"
ARGS ${rcc_options} --format-version 1 -name ${basename} -o ${translations_qrc_outfile} ${translations_qrc_infile}
MAIN_DEPENDENCY ${translations_qrc_infile}
DEPENDS ${QM_FILES}
)
set( ${_qrt_OUTPUT_VARIABLE} ${translations_qrc_outfile} PARENT_SCOPE )
endfunction()
calamares_qrc_translations( localetest OUTPUT_VARIABLE localetest_qrc SUBDIRECTORY testdata LANGUAGES nl )
calamares_add_test(
libcalamareslocaletest
SOURCES
locale/Tests.cpp
${localetest_qrc}
)
calamares_add_test(

@ -16,6 +16,7 @@
#include "CalamaresVersion.h"
#include "GlobalStorage.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
#include <QtTest/QtTest>
@ -33,6 +34,7 @@ private Q_SLOTS:
void testTranslatableLanguages();
void testTranslatableConfig1();
void testTranslatableConfig2();
void testTranslatableConfigContext();
void testLanguageScripts();
void testEsperanto();
@ -246,6 +248,32 @@ LocaleTests::testTranslatableConfig2()
QCOMPARE( ts3.count(), 1 ); // The empty string
}
void
LocaleTests::testTranslatableConfigContext()
{
using TS = CalamaresUtils::Locale::TranslatedString;
const QString original( "Quit" );
TS quitUntranslated( original );
TS quitTranslated( original, metaObject()->className() );
QCOMPARE( quitUntranslated.get(), original );
QCOMPARE( quitTranslated.get(), original );
// Load translation data from QRC
QVERIFY( QFile::exists( ":/lang/localetest_nl.qm" ) );
QTranslator t;
QVERIFY( t.load( QString( ":/lang/localetest_nl" ) ) );
QCoreApplication::installTranslator( &t );
// Translation doesn't affect the one without context
QCOMPARE( quitUntranslated.get(), original );
// But the translation **does** affect this class' context
QCOMPARE( quitTranslated.get(), QStringLiteral( "Ophouden" ) );
QCOMPARE( tr( "Quit" ), QStringLiteral( "Ophouden" ) );
}
void
LocaleTests::testRegions()
{

@ -23,9 +23,15 @@ namespace CalamaresUtils
{
namespace Locale
{
TranslatedString::TranslatedString( const QString& key, const char* context )
: m_context( context )
{
m_strings[ QString() ] = key;
}
TranslatedString::TranslatedString( const QString& string )
: TranslatedString( string, nullptr )
{
m_strings[ QString() ] = string;
}
TranslatedString::TranslatedString( const QVariantMap& map, const QString& key, const char* context )

@ -50,11 +50,23 @@ public:
* metaObject()->className() as context (from a QObject based class)
* to give the TranslatedString the same context as other calls
* to tr() within that class.
*
* The @p context, if any, should point to static data; it is
* **not** owned by the TranslatedString.
*/
TranslatedString( const QVariantMap& map, const QString& key, const char* context = nullptr );
/** @brief Not-actually-translated string.
*/
TranslatedString( const QString& string );
/** @brief Proxy for calling QObject::tr()
*
* This is like the two constructors above, with an empty map an a
* non-null context. It will end up calling tr() with that context.
*
* The @p context, if any, should point to static data; it is
* **not** owned by the TranslatedString.
*/
TranslatedString( const QString& key, const char* context );
/// @brief Empty string
TranslatedString()
: TranslatedString( QString() )

@ -12,9 +12,9 @@
#include "GlobalStorage.h"
#include "utils/Logger.h"
bool
CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
static bool
additions( Calamares::GlobalStorage* gs,
const QString& key,
const QVariantList& installPackages,
const QVariantList& tryInstallPackages )
{
@ -25,8 +25,6 @@ CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
QVariantList packageOperations = gs->contains( PACKAGEOP ) ? gs->value( PACKAGEOP ).toList() : QVariantList();
cDebug() << "Existing package operations length" << packageOperations.length();
const QString key = module.toString();
// Clear out existing operations for this module, going backwards:
// Sometimes we remove an item, and we don't want the index to
// fall off the end of the list.
@ -66,3 +64,25 @@ CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
}
return false;
}
bool
CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
const QVariantList& installPackages,
const QVariantList& tryInstallPackages )
{
return additions( gs, module.toString(), installPackages, tryInstallPackages );
}
bool
CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
const QStringList& installPackages )
{
QVariantList l;
for ( const auto& s : installPackages )
{
l << s;
}
return additions( gs, module.toString(), l, QVariantList() );
}

@ -28,6 +28,14 @@ bool setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
const QVariantList& installPackages,
const QVariantList& tryInstallPackages );
/** @brief Sets the install-packages GS keys for the given module
*
* This replaces previously-set install-packages lists. Use this with
* plain lists of package names. It does not support try-install.
*/
bool setGSPackageAdditions( Calamares::GlobalStorage* gs,
const Calamares::ModuleSystem::InstanceKey& module,
const QStringList& installPackages );
// void setGSPackageRemovals( const Calamares::ModuleSystem::InstanceKey& key, const QVariantList& removePackages );
} // namespace Packages
} // namespace CalamaresUtils

@ -24,7 +24,15 @@ private Q_SLOTS:
void initTestCase();
void testEmpty();
void testAdd_data();
/** @brief Test various add calls, for a "clean" GS
*
* Check that adding through the variant- and the stringlist-API
* does the same thing.
*/
void testAdd();
/// Test replacement and mixing string-list with variant calls
void testAddMixed();
};
void
@ -46,18 +54,114 @@ PackagesTests::testEmpty()
// Adding nothing at all does nothing
QVERIFY( !CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList(), QVariantList() ) );
QVERIFY( !gs.contains( topKey ) );
QVERIFY( !CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QStringList() ) );
QVERIFY( !gs.contains( topKey ) );
}
void
PackagesTests::testAdd_data()
{
QTest::addColumn< QStringList >( "packages" );
QTest::newRow( "one" ) << QStringList { QString( "vim" ) };
QTest::newRow( "two" ) << QStringList { QString( "vim" ), QString( "emacs" ) };
QTest::newRow( "one-again" ) << QStringList { QString( "nano" ) };
QTest::newRow( "six" ) << QStringList { QString( "vim" ), QString( "emacs" ), QString( "nano" ),
QString( "kate" ), QString( "gedit" ), QString( "sublime" ) };
// There is no "de-duplication" so this will insert "cim" twice
QTest::newRow( "dups" ) << QStringList { QString( "cim" ), QString( "vim" ), QString( "cim" ) };
}
void
PackagesTests::testAdd()
{
Calamares::GlobalStorage gs;
const QString extraEditor( "notepad++" );
const QString topKey( "packageOperations" );
Calamares::ModuleSystem::InstanceKey k( "this", "that" );
Calamares::ModuleSystem::InstanceKey otherInstance( "this", "other" );
QFETCH( QStringList, packages );
QVERIFY( !packages.contains( extraEditor ) );
{
QVERIFY( !gs.contains( topKey ) );
QVERIFY(
CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList { QString( "vim" ) }, QVariantList() ) );
CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariant( packages ).toList(), QVariantList() ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 );
auto action = actionList[ 0 ].toMap();
QVERIFY( action.contains( "install" ) );
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), packages.length() );
for ( const auto& s : qAsConst( packages ) )
{
QVERIFY( op.contains( s ) );
}
cDebug() << op;
}
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, packages ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 2 ); // One for each instance key!
auto action = actionList[ 0 ].toMap();
auto secondaction = actionList[ 1 ].toMap();
auto op = action[ "install" ].toList();
auto secondop = secondaction[ "install" ].toList();
QCOMPARE( op, secondop );
}
{
// Replace one and expect differences
packages << extraEditor;
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, packages ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 2 ); // One for each instance key!
for ( const auto& actionVariant : qAsConst( actionList ) )
{
auto action = actionVariant.toMap();
QVERIFY( action.contains( "install" ) );
QVERIFY( action.contains( "source" ) );
if ( action[ "source" ].toString() == otherInstance.toString() )
{
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), packages.length() ); // changed from original length, though
for ( const auto& s : qAsConst( packages ) )
{
QVERIFY( op.contains( s ) );
}
}
else
{
// This is the "original" instance, so it's missing extraEditor
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), packages.length()-1 ); // changed from original length
QVERIFY( !op.contains( extraEditor ) );
}
}
}
}
void
PackagesTests::testAddMixed()
{
Calamares::GlobalStorage gs;
const QString extraEditor( "notepad++" );
const QString topKey( "packageOperations" );
Calamares::ModuleSystem::InstanceKey k( "this", "that" );
Calamares::ModuleSystem::InstanceKey otherInstance( "this", "other" );
// Just one
{
QVERIFY( !gs.contains( topKey ) );
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions(
&gs, k, QVariantList { QString( "vim" ) }, QVariantList() ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 );
@ -65,21 +169,66 @@ PackagesTests::testAdd()
QVERIFY( action.contains( "install" ) );
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), 1 );
QCOMPARE( op[ 0 ], QString( "vim" ) );
cDebug() << op;
}
// Replace with two packages
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions(
&gs, k, QVariantList { QString( "vim" ), QString( "emacs" ) }, QVariantList() ) );
QVERIFY( gs.contains( topKey ) );
actionList = gs.value( topKey ).toList();
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 );
action = actionList[ 0 ].toMap();
auto action = actionList[ 0 ].toMap();
QVERIFY( action.contains( "install" ) );
op = action[ "install" ].toList();
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), 2 );
QCOMPARE( action[ "source" ].toString(), k.toString() );
QVERIFY( op.contains( QString( "vim" ) ) );
QVERIFY( op.contains( QString( "emacs" ) ) );
cDebug() << op;
}
// Replace with one (different) package
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions(
&gs, k, QVariantList { QString( "nano" ) }, QVariantList() ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 1 );
auto action = actionList[ 0 ].toMap();
QVERIFY( action.contains( "install" ) );
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), 1 );
QCOMPARE( action[ "source" ].toString(), k.toString() );
QCOMPARE( op[ 0 ], QString( "nano" ) );
cDebug() << op;
}
// Now we have two sources
{
QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, QStringList( extraEditor ) ) );
QVERIFY( gs.contains( topKey ) );
auto actionList = gs.value( topKey ).toList();
QCOMPARE( actionList.length(), 2 );
for ( const auto& actionVariant : qAsConst( actionList ) )
{
auto action = actionVariant.toMap();
QVERIFY( action.contains( "install" ) );
QVERIFY( action.contains( "source" ) );
if ( action[ "source" ].toString() == otherInstance.toString() )
{
auto op = action[ "install" ].toList();
QCOMPARE( op.length(), 1 );
QVERIFY(
op.contains( action[ "source" ] == otherInstance.toString() ? extraEditor : QString( "nano" ) ) );
}
}
}
}
QTEST_GUILESS_MAIN( PackagesTests )

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- SPDX-FileCopyrightText: no
SPDX-License-Identifier: CC0-1.0
-->
<!DOCTYPE TS>
<TS language="nl" version="2.1">
<context>
<name>LocaleTests</name>
<message>
<location filename="Tests.cpp" line="22"/>
<source>Quit</source>
<translation>Ophouden</translation>
</message>
</context>
</TS>

@ -290,6 +290,17 @@ operator<<( QDebug& s, const Pointer& p )
return s;
}
/** @brief Convenience object for supplying SubEntry to a debug stream
*
* In a function with convoluted control paths, it may be unclear
* when to supply SubEntry to a debug stream -- it is convenient
* for the **first** debug statement from a given function to print
* the function header, and all subsequent onces to get SubEntry.
*
* Create an object of type Once and send it (first) to all CDebug
* objects; this will print the function header only once within the
* lifetime of that Once object.
*/
class Once
{
public:

@ -174,6 +174,22 @@ struct NamedEnumTable
return table.begin()->second;
}
/** @brief Find a name @p s in the table.
*
* Searches case-insensitively.
*
* If the name @p s is not found, the value @p d is returned as
* a default. Otherwise the value corresponding to @p s is returned.
* This is a shortcut over find() using a bool to distinguish
* successful and unsuccesful lookups.
*/
enum_t find( const string_t& s, enum_t d ) const
{
bool ok = false;
enum_t e = find( s, ok );
return ok ? e : d;
}
/** @brief Find a value @p s in the table and return its name.
*
* If @p s is an enum value in the table, return the corresponding

@ -113,7 +113,7 @@ BrandingLoader::tryLoad( QTranslator* translator )
}
else
{
cDebug() << Logger::SubEntry << "Branding using default, system locale not found:" << m_localeName;
cDebug() << Logger::SubEntry << "Branding no translation for" << m_localeName << "using default (en)";
// TODO: this loads something completely different
return translator->load( m_prefix + "en" );
}

@ -18,6 +18,7 @@
#include "utils/ImageRegistry.h"
#include "utils/Logger.h"
#include "utils/NamedEnum.h"
#include "utils/Units.h"
#include "utils/Yaml.h"
#include <QDir>
@ -153,15 +154,18 @@ uploadServerFromMap( const QVariantMap& map )
QString typestring = map[ "type" ].toString();
QString urlstring = map[ "url" ].toString();
qint64 sizeLimitKiB = map[ "sizeLimit" ].toLongLong();
if ( typestring.isEmpty() || urlstring.isEmpty() )
{
return Branding::UploadServerInfo( Branding::UploadServerType::None, QUrl() );
return Branding::UploadServerInfo( Branding::UploadServerType::None, QUrl(), 0 );
}
bool bogus = false; // we don't care about type-name lookup success here
return Branding::UploadServerInfo( names.find( typestring, bogus ),
QUrl( urlstring, QUrl::ParsingMode::StrictMode ) );
return Branding::UploadServerInfo(
names.find( typestring, bogus ),
QUrl( urlstring, QUrl::ParsingMode::StrictMode ),
sizeLimitKiB >= 0 ? CalamaresUtils::KiBtoBytes( static_cast< unsigned long long >( sizeLimitKiB ) ) : -1 );
}
/** @brief Load the @p map with strings from @p config

@ -223,10 +223,11 @@ public:
/** @brief Upload server configuration
*
* This is both the type (which may be none, in which case the URL
* is irrelevant and usually empty) and the URL for the upload.
* This object has 3 items : the type (which may be none, in which case the URL
* is irrelevant and usually empty), the URL for the upload and the size limit of upload
* in bytes (for configuration value < 0, it serves -1, which stands for having no limit).
*/
using UploadServerInfo = QPair< UploadServerType, QUrl >;
using UploadServerInfo = std::tuple< UploadServerType, QUrl, qint64 >;
UploadServerInfo uploadServer() const { return m_uploadServer; }
/**

@ -143,8 +143,9 @@ ViewManager::insertViewStep( int before, ViewStep* step )
void
ViewManager::onInstallationFailed( const QString& message, const QString& details )
{
bool shouldOfferWebPaste
= Calamares::Branding::instance()->uploadServer().first != Calamares::Branding::UploadServerType::None;
bool shouldOfferWebPaste = std::get< 0 >( Calamares::Branding::instance()->uploadServer() )
!= Calamares::Branding::UploadServerType::None
and std::get< 2 >( Calamares::Branding::instance()->uploadServer() ) != 0;
cError() << "Installation failed:" << message;
cDebug() << Logger::SubEntry << "- message:" << message;

@ -30,8 +30,12 @@ using namespace CalamaresUtils::Units;
* Returns an empty QByteArray() on any kind of error.
*/
STATICTEST QByteArray
logFileContents()
logFileContents( const qint64 sizeLimitBytes )
{
if ( sizeLimitBytes != -1 )
{
cDebug() << "Log upload size limit was limited to" << sizeLimitBytes << "bytes";
}
const QString name = Logger::logFile();
QFile pasteSourceFile( name );
if ( !pasteSourceFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
@ -39,12 +43,18 @@ logFileContents()
cWarning() << "Could not open log file" << name;
return QByteArray();
}
if ( sizeLimitBytes == -1 )
{
return pasteSourceFile.readAll();
}
QFileInfo fi( pasteSourceFile );
if ( fi.size() > 16_KiB )
if ( fi.size() > sizeLimitBytes )
{
pasteSourceFile.seek( fi.size() - 16_KiB );
cDebug() << "Only last" << sizeLimitBytes << "bytes of log file (sized" << fi.size() << "bytes) uploaded";
fi.refresh();
pasteSourceFile.seek( fi.size() - sizeLimitBytes );
}
return pasteSourceFile.read( 16_KiB );
return pasteSourceFile.read( sizeLimitBytes );
}
@ -101,7 +111,7 @@ ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* par
QString
CalamaresUtils::Paste::doLogUpload( QObject* parent )
{
auto [ type, serverUrl ] = Calamares::Branding::instance()->uploadServer();
auto [ type, serverUrl, sizeLimitBytes ] = Calamares::Branding::instance()->uploadServer();
if ( !serverUrl.isValid() )
{
cWarning() << "Upload configure with invalid URL";
@ -113,7 +123,7 @@ CalamaresUtils::Paste::doLogUpload( QObject* parent )
return QString();
}
QByteArray pasteData = logFileContents();
QByteArray pasteData = logFileContents( sizeLimitBytes );
if ( pasteData.isEmpty() )
{
// An error has already been logged
@ -165,6 +175,6 @@ CalamaresUtils::Paste::doLogUploadUI( QWidget* parent )
bool
CalamaresUtils::Paste::isEnabled()
{
auto [ type, serverUrl ] = Calamares::Branding::instance()->uploadServer();
auto [ type, serverUrl, sizeLimitBytes ] = Calamares::Branding::instance()->uploadServer();
return type != Calamares::Branding::UploadServerType::None;
}

@ -10,13 +10,14 @@
*/
#include "Paste.h"
#include "network/Manager.h"
#include "utils/Logger.h"
#include <QDateTime>
#include <QtTest/QtTest>
extern QByteArray logFileContents();
extern QByteArray logFileContents( qint64 sizeLimitBytes );
extern QString ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* parent );
class TestPaste : public QObject
@ -30,6 +31,7 @@ public:
private Q_SLOTS:
void testGetLogFile();
void testFichePaste();
void testUploadSize();
};
void
@ -37,14 +39,18 @@ TestPaste::testGetLogFile()
{
QFile::remove( Logger::logFile() );
// This test assumes nothing **else** has set up logging yet
QByteArray contentsOfLogfileBefore = logFileContents();
QVERIFY( contentsOfLogfileBefore.isEmpty() );
QByteArray logLimitedBefore = logFileContents( 16 );
QVERIFY( logLimitedBefore.isEmpty() );
QByteArray logUnlimitedBefore = logFileContents( -1 );
QVERIFY( logUnlimitedBefore.isEmpty() );
Logger::setupLogLevel( Logger::LOGDEBUG );
Logger::setupLogfile();
QByteArray contentsOfLogfileAfterSetup = logFileContents();
QVERIFY( !contentsOfLogfileAfterSetup.isEmpty() );
QByteArray logLimitedAfter = logFileContents( 16 );
QVERIFY( !logLimitedAfter.isEmpty() );
QByteArray logUnlimitedAfter = logFileContents( -1 );
QVERIFY( !logUnlimitedAfter.isEmpty() );
}
void
@ -60,7 +66,19 @@ TestPaste::testFichePaste()
QVERIFY( !s.isEmpty() );
}
void
TestPaste::testUploadSize()
{
QByteArray logContent = logFileContents( 100 );
QString s = ficheLogUpload( logContent, QUrl( "http://termbin.com:9999" ), nullptr );
QVERIFY( !s.isEmpty() );
QUrl url( s );
QByteArray returnedData = CalamaresUtils::Network::Manager::instance().synchronousGet( url );
QCOMPARE( returnedData.size(), 100 );
}
QTEST_GUILESS_MAIN( TestPaste )
#include "utils/moc-warnings.h"

@ -195,6 +195,8 @@ desktop_environments = [
DesktopEnvironment('/usr/bin/dwm', 'dwm'),
DesktopEnvironment('/usr/bin/jwm', 'jwm'),
DesktopEnvironment('/usr/bin/icewm-session', 'icewm-session'),
DesktopEnvironment('/usr/bin/fvwm3', 'fvwm3'),
DesktopEnvironment('/usr/bin/sway', 'sway'),
]

@ -92,6 +92,7 @@ def write_mkinitcpio_lines(hooks, modules, files, root_mount_point):
with open(path, "w") as mkinitcpio_file:
mkinitcpio_file.write("\n".join(mklins) + "\n")
def detect_plymouth():
"""
Checks existence (runnability) of plymouth in the target system.
@ -99,10 +100,8 @@ def detect_plymouth():
@return True if plymouth exists in the target, False otherwise
"""
# Used to only check existence of path /usr/bin/plymouth in target
isPlymouth = target_env_call(["sh", "-c", "which plymouth"])
debug("which plymouth exit code: {!s}".format(isPlymouth))
return target_env_call(["sh", "-c", "which plymouth"]) == 0
return isPlymouth == 0
def modify_mkinitcpio_conf(partitions, root_mount_point):
"""

@ -210,6 +210,10 @@ Config::Config( QObject* parent )
m_setxkbmapTimer.start( QApplication::keyboardInputInterval() );
emit prettyStatusChanged();
} );
m_selectedModel = m_keyboardModelsModel->key( m_keyboardModelsModel->currentIndex() );
m_selectedLayout = m_keyboardLayoutsModel->item( m_keyboardLayoutsModel->currentIndex() ).first;
m_selectedVariant = m_keyboardVariantsModel->key( m_keyboardVariantsModel->currentIndex() );
}
KeyboardModelsModel*
@ -528,38 +532,15 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
{
using namespace CalamaresUtils;
if ( configurationMap.contains( "xOrgConfFileName" )
&& configurationMap.value( "xOrgConfFileName" ).type() == QVariant::String
&& !getString( configurationMap, "xOrgConfFileName" ).isEmpty() )
{
m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName" );
}
else
const auto xorgConfDefault = QStringLiteral( "00-keyboard.conf" );
m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName", xorgConfDefault );
if ( m_xOrgConfFileName.isEmpty() )
{
m_xOrgConfFileName = "00-keyboard.conf";
m_xOrgConfFileName = xorgConfDefault;
}
if ( configurationMap.contains( "convertedKeymapPath" )
&& configurationMap.value( "convertedKeymapPath" ).type() == QVariant::String
&& !getString( configurationMap, "convertedKeymapPath" ).isEmpty() )
{
m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" );
}
else
{
m_convertedKeymapPath = QString();
}
if ( configurationMap.contains( "writeEtcDefaultKeyboard" )
&& configurationMap.value( "writeEtcDefaultKeyboard" ).type() == QVariant::Bool )
{
m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true );
}
else
{
m_writeEtcDefaultKeyboard = true;
}
}
void
Config::retranslate()

@ -259,8 +259,7 @@ Config::setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* location
auto newLocale = automaticLocaleConfiguration();
if ( !m_selectedLocaleConfiguration.explicit_lang )
{
m_selectedLocaleConfiguration.setLanguage( newLocale.language() );
emit currentLanguageStatusChanged( currentLanguageStatus() );
setLanguage( newLocale.language() );
}
if ( !m_selectedLocaleConfiguration.explicit_lc )
{
@ -302,12 +301,21 @@ Config::localeConfiguration() const
void
Config::setLanguageExplicitly( const QString& language )
{
m_selectedLocaleConfiguration.setLanguage( language );
m_selectedLocaleConfiguration.explicit_lang = true;
setLanguage( language );
}
void
Config::setLanguage( const QString& language )
{
if ( language != m_selectedLocaleConfiguration.language() )
{
m_selectedLocaleConfiguration.setLanguage( language );
emit currentLanguageStatusChanged( currentLanguageStatus() );
emit currentLanguageCodeChanged( currentLanguageCode() );
}
}
void
Config::setLCLocaleExplicitly( const QString& locale )

@ -95,6 +95,8 @@ private:
}
public Q_SLOTS:
/// Set the language, but do not mark it as user-choice
void setLanguage( const QString& language );
/// Set a language by user-choice, overriding future location changes
void setLanguageExplicitly( const QString& language );
/// Set LC (formats) by user-choice, overriding future location changes

@ -26,6 +26,8 @@ calamares_add_test(
netinstalltest
SOURCES
Tests.cpp
Config.cpp
LoaderQueue.cpp
PackageTreeItem.cpp
PackageModel.cpp
LIBRARIES

@ -97,7 +97,6 @@ Config::loadGroupList( const QVariantList& groupData )
{
setStatus( Status::Ok );
}
emit statusReady();
}
void
@ -108,6 +107,7 @@ Config::loadingDone()
m_queue->deleteLater();
m_queue = nullptr;
}
emit statusReady();
}
@ -136,26 +136,24 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
// Lastly, load the groups data
const QString key = QStringLiteral( "groupsUrl" );
const auto& groupsUrlVariant = configurationMap.value( key );
m_queue = new LoaderQueue( this );
if ( groupsUrlVariant.type() == QVariant::String )
{
m_queue = new LoaderQueue( this );
m_queue->append( SourceItem::makeSourceItem( groupsUrlVariant.toString(), configurationMap ) );
}
else if ( groupsUrlVariant.type() == QVariant::List )
{
m_queue = new LoaderQueue( this );
for ( const auto& s : groupsUrlVariant.toStringList() )
{
m_queue->append( SourceItem::makeSourceItem( s, configurationMap ) );
}
}
if ( m_queue && m_queue->count() > 0 )
{
setStatus( required() ? Status::FailedNoData : Status::Ok );
cDebug() << "Loading netinstall from" << m_queue->count() << "alternate sources.";
connect( m_queue, &LoaderQueue::done, this, &Config::loadingDone );
m_queue->load();
}
}
void
Config::finalizeGlobalStorage( const Calamares::ModuleSystem::InstanceKey& key )

@ -49,10 +49,12 @@ public:
FailedNetworkError,
FailedBadData,
FailedNoData
};
/// Human-readable, translated representation of the status
QString status() const;
/// Internal code for the status
Status statusCode() const { return m_status; }
void setStatus( Status s );
bool required() const { return m_required; }

@ -25,6 +25,10 @@
* On destruction, a new call to fetchNext() is queued, so that
* the queue continues loading. Calling release() before the
* destructor skips the fetchNext(), ending the queue-loading.
*
* Calling done(b) is a conditional release: if @p b is @c true,
* queues a call to done() on the queue and releases it; otherwise,
* does nothing.
*/
class FetchNextUnless
{
@ -41,6 +45,17 @@ public:
}
}
void release() { m_q = nullptr; }
void done( bool b )
{
if ( b )
{
if ( m_q )
{
QMetaObject::invokeMethod( m_q, "done", Qt::QueuedConnection );
}
release();
}
}
private:
LoaderQueue* m_q = nullptr;
@ -83,7 +98,6 @@ LoaderQueue::fetchNext()
{
if ( m_queue.isEmpty() )
{
m_config->setStatus( Config::Status::FailedBadData );
emit done();
return;
}
@ -138,7 +152,7 @@ LoaderQueue::fetch( const QUrl& url )
void
LoaderQueue::dataArrived()
{
FetchNextUnless finished( this );
FetchNextUnless next( this );
if ( !m_reply || !m_reply->isFinished() )
{
@ -170,16 +184,14 @@ LoaderQueue::dataArrived()
if ( groups.IsSequence() )
{
finished.release();
m_config->loadGroupList( CalamaresUtils::yamlSequenceToVariant( groups ) );
emit done();
next.done( m_config->statusCode() == Config::Status::Ok );
}
else if ( groups.IsMap() )
{
finished.release();
auto map = CalamaresUtils::yamlMapToVariant( groups );
m_config->loadGroupList( map.value( "groups" ).toList() );
emit done();
next.done( m_config->statusCode() == Config::Status::Ok );
}
else
{

@ -7,13 +7,17 @@
*
*/
#include "Config.h"
#include "PackageModel.h"
#include "PackageTreeItem.h"
#include "utils/Logger.h"
#include "utils/NamedEnum.h"
#include "utils/Variant.h"
#include "utils/Yaml.h"
#include <KMacroExpander>
#include <QtTest/QtTest>
class ItemTests : public QObject
@ -40,6 +44,9 @@ private Q_SLOTS:
void testCompare();
void testModel();
void testExampleFiles();
void testUrlFallback_data();
void testUrlFallback();
};
ItemTests::ItemTests() {}
@ -326,6 +333,93 @@ ItemTests::testExampleFiles()
}
}
void
ItemTests::testUrlFallback_data()
{
QTest::addColumn< QString >( "filename" );
QTest::addColumn< int >( "status" );
QTest::addColumn< int >( "count" );
using S = Config::Status;
QTest::newRow( "bad" ) << "1a-single-bad.conf" << smash( S::FailedBadConfiguration ) << 0;
QTest::newRow( "empty" ) << "1a-single-empty.conf" << smash( S::FailedNoData ) << 0;
QTest::newRow( "error" ) << "1a-single-error.conf" << smash( S::FailedBadData ) << 0;
QTest::newRow( "two" ) << "1b-single-small.conf" << smash( S::Ok ) << 2;
QTest::newRow( "five" ) << "1b-single-large.conf" << smash( S::Ok ) << 5;
QTest::newRow( "none" ) << "1c-none.conf" << smash( S::FailedNoData ) << 0;
QTest::newRow( "unset" ) << "1c-unset.conf" << smash( S::FailedNoData ) << 0;
// Finds small, then stops
QTest::newRow( "fallback-small" ) << "1d-fallback-small.conf" << smash( S::Ok ) << 2;
// Finds large, then stops
QTest::newRow( "fallback-large" ) << "1d-fallback-large.conf" << smash( S::Ok ) << 5;
// Finds empty, finds small
QTest::newRow( "fallback-mixed" ) << "1d-fallback-mixed.conf" << smash( S::Ok ) << 2;
// Finds empty, then bad
QTest::newRow( "fallback-bad" ) << "1d-fallback-bad.conf" << smash( S::FailedBadConfiguration ) << 0;
}
void
ItemTests::testUrlFallback()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
QFETCH( QString, filename );
QFETCH( int, status );
QFETCH( int, count );
cDebug() << "Loading" << filename;
// BUILD_AS_TEST is the source-directory path
QString testdir = QString( "%1/tests" ).arg( BUILD_AS_TEST );
QFile fi( QString( "%1/%2" ).arg( testdir, filename ) );
QVERIFY( fi.exists() );
Config c;
QFile yamlFile( fi.fileName() );
if ( yamlFile.exists() && yamlFile.open( QFile::ReadOnly | QFile::Text ) )
{
QString ba( yamlFile.readAll() );
QVERIFY( ba.length() > 0 );
QHash< QString, QString > replace;
replace.insert( "TESTDIR", testdir );
QString correctedDocument = KMacroExpander::expandMacros( ba, replace, '$' );
try
{
YAML::Node yamldoc = YAML::Load( correctedDocument.toUtf8() );
auto map = CalamaresUtils::yamlToVariant( yamldoc ).toMap();
QVERIFY( map.count() > 0 );
c.setConfigurationMap( map );
}
catch ( YAML::Exception& )
{
bool badYaml = true;
QVERIFY( !badYaml );
}
}
else
{
QCOMPARE( QStringLiteral( "not found" ), fi.fileName() );
}
// Each of the configs sets required to **true**, which is not the default
QVERIFY( c.required() );
// Now give the loader time to complete
QEventLoop loop;
connect( &c, &Config::statusReady, &loop, &QEventLoop::quit );
QSignalSpy spy( &c, &Config::statusReady );
QTimer::singleShot( std::chrono::seconds(1), &loop, &QEventLoop::quit );
loop.exec();
// Check it didn't time out
QCOMPARE( spy.count(), 1 );
// Check YAML-loading results
QCOMPARE( smash( c.statusCode() ), status );
QCOMPARE( c.model()->rowCount(), count );
}
QTEST_GUILESS_MAIN( ItemTests )

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/bad.yaml

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-empty.yaml

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-error.yaml

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-large.yaml

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-small.yaml

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl: []

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-nonexistent.yaml
- file://$TESTDIR/data-empty.yaml
- file://$TESTDIR/data-empty.yaml
- file://$TESTDIR/data-bad.yaml

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-nonexistent.yaml
- file://$TESTDIR/data-bad.yaml
- file://$TESTDIR/data-large.yaml
- file://$TESTDIR/data-small.yaml

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-nonexistent.yaml
- file://$TESTDIR/data-empty.yaml
- file://$TESTDIR/data-bad.yaml
- file://$TESTDIR/data-empty.yaml
- file://$TESTDIR/data-small.yaml
- file://$TESTDIR/data-large.yaml
- file://$TESTDIR/data-bad.yaml

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
required: true
groupsUrl:
- file://$TESTDIR/data-nonexistent.yaml
- file://$TESTDIR/data-bad.yaml
- file://$TESTDIR/data-small.yaml
- file://$TESTDIR/data-large.yaml

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
bogus: true

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
derp
derp
herpa-derp: no
--
# This file is not valid YAML

@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
- name: "Default"
description: "Default group"
hidden: false
selected: true
critical: false
packages:
- base
- name: "Two"
description: "group 2"
hidden: false
selected: true
critical: false
packages:
- chakra-live-two
- name: "Three"
description: "group 3"
hidden: false
selected: true
critical: false
packages:
- chakra-live-three
- name: "Four"
description: "group 4"
hidden: false
selected: true
critical: false
packages:
- chakra-live-four
- name: "Five"
description: "group 5"
hidden: false
selected: true
critical: false
packages:
- chakra-live-five

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
- name: "Default"
description: "Default group"
hidden: false
selected: true
critical: false
packages:
- base
- name: "Second"
description: "Second group"
hidden: false
selected: true
critical: false
packages:
- chakra-live-skel

@ -45,6 +45,7 @@ calamares_add_plugin( packagechooser
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
Config.cpp
PackageChooserPage.cpp
PackageChooserViewStep.cpp
PackageModel.cpp

@ -0,0 +1,224 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Config.h"
#ifdef HAVE_XML
#include "ItemAppData.h"
#endif
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "packages/Globals.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
const NamedEnumTable< PackageChooserMode >&
packageChooserModeNames()
{
static const NamedEnumTable< PackageChooserMode > names {
{ "optional", PackageChooserMode::Optional },
{ "required", PackageChooserMode::Required },
{ "optionalmultiple", PackageChooserMode::OptionalMultiple },
{ "requiredmultiple", PackageChooserMode::RequiredMultiple },
// and a bunch of aliases
{ "zero-or-one", PackageChooserMode::Optional },
{ "radio", PackageChooserMode::Required },
{ "one", PackageChooserMode::Required },
{ "set", PackageChooserMode::OptionalMultiple },
{ "zero-or-more", PackageChooserMode::OptionalMultiple },
{ "multiple", PackageChooserMode::RequiredMultiple },
{ "one-or-more", PackageChooserMode::RequiredMultiple }
};
return names;
}
const NamedEnumTable< PackageChooserMethod >&
PackageChooserMethodNames()
{
static const NamedEnumTable< PackageChooserMethod > names {
{ "legacy", PackageChooserMethod::Legacy },
{ "custom", PackageChooserMethod::Legacy },
{ "contextualprocess", PackageChooserMethod::Legacy },
{ "packages", PackageChooserMethod::Packages },
};
return names;
}
Config::Config( QObject* parent )
: Calamares::ModuleSystem::Config( parent )
, m_model( new PackageListModel( this ) )
, m_mode( PackageChooserMode::Required )
{
}
Config::~Config() {}
const PackageItem&
Config::introductionPackage() const
{
for ( int i = 0; i < m_model->packageCount(); ++i )
{
const auto& package = m_model->packageData( i );
if ( package.isNonePackage() )
{
return package;
}
}
static PackageItem* defaultIntroduction = nullptr;
if ( !defaultIntroduction )
{
const auto name = QT_TR_NOOP( "Package Selection" );
const auto description
= QT_TR_NOOP( "Please pick a product from the list. The selected product will be installed." );
defaultIntroduction = new PackageItem( QString(), name, description );
defaultIntroduction->screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) );
defaultIntroduction->name = CalamaresUtils::Locale::TranslatedString( name, metaObject()->className() );
defaultIntroduction->description
= CalamaresUtils::Locale::TranslatedString( description, metaObject()->className() );
}
return *defaultIntroduction;
}
void
Config::updateGlobalStorage( const QStringList& selected ) const
{
if ( m_method == PackageChooserMethod::Legacy )
{
QString value = selected.join( ',' );
Calamares::JobQueue::instance()->globalStorage()->insert( m_id, value );
cDebug() << m_id<< "selected" << value;
}
else if ( m_method == PackageChooserMethod::Packages )
{
QStringList packageNames = m_model->getInstallPackagesForNames( selected );
cDebug() << m_defaultId << "packages to install" << packageNames;
CalamaresUtils::Packages::setGSPackageAdditions(
Calamares::JobQueue::instance()->globalStorage(), m_defaultId, packageNames );
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );
}
}
static void
fillModel( PackageListModel* model, const QVariantList& items )
{
if ( items.isEmpty() )
{
cWarning() << "No *items* for PackageChooser module.";
return;
}
#ifdef HAVE_APPSTREAM
std::unique_ptr< AppStream::Pool > pool;
bool poolOk = false;
#endif
cDebug() << "Loading PackageChooser model items from config";
int item_index = 0;
for ( const auto& item_it : items )
{
++item_index;
QVariantMap item_map = item_it.toMap();
if ( item_map.isEmpty() )
{
cWarning() << "PackageChooser entry" << item_index << "is not valid.";
continue;
}
if ( item_map.contains( "appdata" ) )
{
#ifdef HAVE_XML
model->addPackage( fromAppData( item_map ) );
#else
cWarning() << "Loading AppData XML is not supported.";
#endif
}
else if ( item_map.contains( "appstream" ) )
{
#ifdef HAVE_APPSTREAM
if ( !pool )
{
pool = std::make_unique< AppStream::Pool >();
pool->setLocale( QStringLiteral( "ALL" ) );
poolOk = pool->load();
}
if ( pool && poolOk )
{
model->addPackage( fromAppStream( *pool, item_map ) );
}
#else
cWarning() << "Loading AppStream data is not supported.";
#endif
}
else
{
model->addPackage( PackageItem( item_map ) );
}
}
cDebug() << Logger::SubEntry << "Loaded PackageChooser with" << model->packageCount() << "entries.";
}
void
Config::setConfigurationMap( const QVariantMap& configurationMap )
{
m_mode = packageChooserModeNames().find( CalamaresUtils::getString( configurationMap, "mode" ),
PackageChooserMode::Required );
m_method = PackageChooserMethodNames().find( CalamaresUtils::getString( configurationMap, "method" ),
PackageChooserMethod::Legacy );
if ( m_method == PackageChooserMethod::Legacy )
{
const QString configId = CalamaresUtils::getString( configurationMap, "id" );
const QString base = QStringLiteral( "packagechooser_" );
if ( configId.isEmpty() )
{
if ( m_defaultId.id().isEmpty() )
{
// We got nothing to work with
m_id = base;
}
else
{
m_id = base + m_defaultId.id();
}
cDebug() << "Using default ID" << m_id << "from" << m_defaultId.toString();
}
else
{
m_id = base + configId;
cDebug() << "Using configured ID" << m_id;
}
}
if ( configurationMap.contains( "items" ) )
{
fillModel( m_model, configurationMap.value( "items" ).toList() );
}
QString default_item_id = CalamaresUtils::getString( configurationMap, "default" );
if ( !default_item_id.isEmpty() )
{
for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n )
{
QModelIndex item_idx = m_model->index( item_n, 0 );
QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole );
if ( item_id.toString() == default_item_id )
{
m_defaultModelIndex = item_idx;
break;
}
}
}
}

@ -0,0 +1,92 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSER_CONFIG_H
#define PACKAGECHOOSER_CONFIG_H
#include "PackageModel.h"
#include "modulesystem/Config.h"
#include "modulesystem/InstanceKey.h"
#include <memory>
enum class PackageChooserMode
{
Optional, // zero or one
Required, // exactly one
OptionalMultiple, // zero or more
RequiredMultiple // one or more
};
const NamedEnumTable< PackageChooserMode >& packageChooserModeNames();
enum class PackageChooserMethod
{
Legacy, // use contextualprocess or other custom
Packages, // use the packages module
};
const NamedEnumTable< PackageChooserMethod >& PackageChooserMethodNames();
class Config : public Calamares::ModuleSystem::Config
{
Q_OBJECT
public:
Config( QObject* parent = nullptr );
~Config() override;
/** @brief Sets the default Id for this Config
*
* The default Id is the (owning) module identifier for the config,
* and it is used when the Id read from the config file is empty.
* The **usual** configuration when using method *packages* is
* to rely on the default Id.
*/
void setDefaultId( const Calamares::ModuleSystem::InstanceKey& defaultId ) { m_defaultId = defaultId; }
void setConfigurationMap( const QVariantMap& ) override;
PackageChooserMode mode() const { return m_mode; }
PackageListModel* model() const { return m_model; }
QModelIndex defaultSelectionIndex() const { return m_defaultModelIndex; }
/** @brief Returns an "introductory package" which describes packagechooser
*
* If the model contains a "none" package, returns that one on
* the assumption that it is one to describe the whole; otherwise
* returns a totally generic description.
*/
const PackageItem& introductionPackage() const;
/** @brief Write selection to global storage
*
* Updates the GS keys for this packagechooser, marking all
* (and only) the packages in @p selected as selected.
*/
void updateGlobalStorage( const QStringList& selected ) const;
/// As updateGlobalStorage() with an empty selection list
void updateGlobalStorage() const { updateGlobalStorage( QStringList() ); }
private:
PackageListModel* m_model = nullptr;
QModelIndex m_defaultModelIndex;
/// Selection mode for this module
PackageChooserMode m_mode = PackageChooserMode::Optional;
/// How this module stores to GS
PackageChooserMethod m_method = PackageChooserMethod::Legacy;
/// Id (used to identify settings from this module in GS)
QString m_id;
/// Value to use for id if none is set in the config file
Calamares::ModuleSystem::InstanceKey m_defaultId;
};
#endif

@ -10,6 +10,7 @@
#ifndef PACKAGECHOOSERPAGE_H
#define PACKAGECHOOSERPAGE_H
#include "Config.h"
#include "PackageModel.h"
#include <QAbstractItemModel>

@ -9,20 +9,22 @@
#include "PackageChooserViewStep.h"
#include "Config.h"
#include "PackageChooserPage.h"
#include "PackageModel.h"
#ifdef HAVE_XML
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM
#include "ItemAppStream.h"
#include <AppStreamQt/pool.h>
#include <memory>
#endif
#include "PackageChooserPage.h"
#include "PackageModel.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
@ -35,9 +37,8 @@ CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlug
PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_config( new Config( this ) )
, m_widget( nullptr )
, m_model( nullptr )
, m_mode( PackageChooserMode::Required )
, m_stepName( nullptr )
{
emit nextStatusChanged( false );
@ -50,7 +51,6 @@ PackageChooserViewStep::~PackageChooserViewStep()
{
m_widget->deleteLater();
}
delete m_model;
delete m_stepName;
}
@ -67,20 +67,12 @@ PackageChooserViewStep::widget()
{
if ( !m_widget )
{
m_widget = new PackageChooserPage( m_mode, nullptr );
m_widget = new PackageChooserPage( m_config->mode(), nullptr );
connect( m_widget, &PackageChooserPage::selectionChanged, [=]() {
emit nextStatusChanged( this->isNextEnabled() );
} );
if ( m_model )
{
hookupModel();
}
else
{
cWarning() << "PackageChooser Widget created before model.";
}
}
return m_widget;
}
@ -88,18 +80,13 @@ PackageChooserViewStep::widget()
bool
PackageChooserViewStep::isNextEnabled() const
{
if ( !m_model )
{
return false;
}
if ( !m_widget )
{
// No way to have changed anything
return true;
}
switch ( m_mode )
switch ( m_config->mode() )
{
case PackageChooserMode::Optional:
case PackageChooserMode::OptionalMultiple:
@ -139,22 +126,14 @@ PackageChooserViewStep::onActivate()
{
if ( !m_widget->hasSelection() )
{
m_widget->setSelection( m_defaultIdx );
m_widget->setSelection( m_config->defaultSelectionIndex() );
}
}
void
PackageChooserViewStep::onLeave()
{
QString key = QStringLiteral( "packagechooser_%1" ).arg( m_id );
QString value;
if ( m_widget->hasSelection() )
{
value = m_widget->selectedPackageIds().join( ',' );
}
Calamares::JobQueue::instance()->globalStorage()->insert( key, value );
cDebug() << "PackageChooser" << key << "selected" << value;
m_config->updateGlobalStorage( m_widget->selectedPackageIds() );
}
Calamares::JobList
@ -167,23 +146,8 @@ PackageChooserViewStep::jobs() const
void
PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
QString mode = CalamaresUtils::getString( configurationMap, "mode" );
bool mode_ok = false;
if ( !mode.isEmpty() )
{
m_mode = roleNames().find( mode, mode_ok );
}
if ( !mode_ok )
{
m_mode = PackageChooserMode::Required;
}
m_id = CalamaresUtils::getString( configurationMap, "id" );
if ( m_id.isEmpty() )
{
// Not set, so use the instance id
m_id = moduleInstanceKey().id();
}
m_config->setDefaultId( moduleInstanceKey() );
m_config->setConfigurationMap( configurationMap );
bool labels_ok = false;
auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok );
@ -195,117 +159,22 @@ PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap
}
}
QString default_item_id = CalamaresUtils::getString( configurationMap, "default" );
m_defaultIdx = QModelIndex();
bool first_time = !m_model;
if ( configurationMap.contains( "items" ) )
{
fillModel( configurationMap.value( "items" ).toList() );
}
if ( first_time && m_widget && m_model )
if ( m_widget )
{
hookupModel();
}
// find default item
if ( first_time && m_model && !default_item_id.isEmpty() )
{
for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n )
{
QModelIndex item_idx = m_model->index( item_n, 0 );
QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole );
if ( item_id.toString() == default_item_id )
{
m_defaultIdx = item_idx;
break;
}
}
}
}
void
PackageChooserViewStep::fillModel( const QVariantList& items )
{
if ( !m_model )
{
m_model = new PackageListModel( nullptr );
}
if ( items.isEmpty() )
{
cWarning() << "No *items* for PackageChooser module.";
return;
}
#ifdef HAVE_APPSTREAM
std::unique_ptr< AppStream::Pool > pool;
bool poolOk = false;
#endif
cDebug() << "Loading PackageChooser model items from config";
int item_index = 0;
for ( const auto& item_it : items )
{
++item_index;
QVariantMap item_map = item_it.toMap();
if ( item_map.isEmpty() )
{
cWarning() << "PackageChooser entry" << item_index << "is not valid.";
continue;
}
if ( item_map.contains( "appdata" ) )
{
#ifdef HAVE_XML
m_model->addPackage( fromAppData( item_map ) );
#else
cWarning() << "Loading AppData XML is not supported.";
#endif
}
else if ( item_map.contains( "appstream" ) )
{
#ifdef HAVE_APPSTREAM
if ( !pool )
{
pool = std::make_unique< AppStream::Pool >();
pool->setLocale( QStringLiteral( "ALL" ) );
poolOk = pool->load();
}
if ( pool && poolOk )
{
m_model->addPackage( fromAppStream( *pool, item_map ) );
}
#else
cWarning() << "Loading AppStream data is not supported.";
#endif
}
else
{
m_model->addPackage( PackageItem( item_map ) );
}
}
}
void
PackageChooserViewStep::hookupModel()
{
if ( !m_model || !m_widget )
if ( !m_config->model() || !m_widget )
{
cError() << "Can't hook up model until widget and model both exist.";
return;
}
m_widget->setModel( m_model );
for ( int i = 0; i < m_model->packageCount(); ++i )
{
const auto& package = m_model->packageData( i );
if ( package.id.isEmpty() )
{
m_widget->setIntroduction( package );
break;
}
}
m_widget->setModel( m_config->model() );
m_widget->setIntroduction( m_config->introductionPackage() );
}

@ -15,12 +15,9 @@
#include "utils/PluginFactory.h"
#include "viewpages/ViewStep.h"
#include "PackageModel.h"
#include <QObject>
#include <QUrl>
#include <QVariantMap>
class Config;
class PackageChooserPage;
class PLUGINDLLEXPORT PackageChooserViewStep : public Calamares::ViewStep
@ -49,17 +46,11 @@ public:
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
void fillModel( const QVariantList& items );
void hookupModel();
Config* m_config;
PackageChooserPage* m_widget;
PackageListModel* m_model;
// Configuration
PackageChooserMode m_mode;
QString m_id;
CalamaresUtils::Locale::TranslatedString* m_stepName; // As it appears in the sidebar
QModelIndex m_defaultIdx;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory )

@ -12,46 +12,20 @@
#include "utils/Logger.h"
#include "utils/Variant.h"
const NamedEnumTable< PackageChooserMode >&
roleNames()
{
static const NamedEnumTable< PackageChooserMode > names {
{ "optional", PackageChooserMode::Optional },
{ "required", PackageChooserMode::Required },
{ "optionalmultiple", PackageChooserMode::OptionalMultiple },
{ "requiredmultiple", PackageChooserMode::RequiredMultiple },
// and a bunch of aliases
{ "zero-or-one", PackageChooserMode::Optional },
{ "radio", PackageChooserMode::Required },
{ "one", PackageChooserMode::Required },
{ "set", PackageChooserMode::OptionalMultiple },
{ "zero-or-more", PackageChooserMode::OptionalMultiple },
{ "multiple", PackageChooserMode::RequiredMultiple },
{ "one-or-more", PackageChooserMode::RequiredMultiple }
};
return names;
}
PackageItem::PackageItem() {}
PackageItem::PackageItem( const QString& a_id,
const QString& a_package,
const QString& a_name,
const QString& a_description )
PackageItem::PackageItem( const QString& a_id, const QString& a_name, const QString& a_description )
: id( a_id )
, package( a_package )
, name( a_name )
, description( a_description )
{
}
PackageItem::PackageItem( const QString& a_id,
const QString& a_package,
const QString& a_name,
const QString& a_description,
const QString& screenshotPath )
: id( a_id )
, package( a_package )
, name( a_name )
, description( a_description )
, screenshot( screenshotPath )
@ -60,10 +34,10 @@ PackageItem::PackageItem( const QString& a_id,
PackageItem::PackageItem::PackageItem( const QVariantMap& item_map )
: id( CalamaresUtils::getString( item_map, "id" ) )
, package( CalamaresUtils::getString( item_map, "package" ) )
, name( CalamaresUtils::Locale::TranslatedString( item_map, "name" ) )
, description( CalamaresUtils::Locale::TranslatedString( item_map, "description" ) )
, screenshot( CalamaresUtils::getString( item_map, "screenshot" ) )
, packageNames( CalamaresUtils::getStringList( item_map, "packages" ) )
{
if ( name.isEmpty() && id.isEmpty() )
{
@ -105,6 +79,33 @@ PackageListModel::addPackage( PackageItem&& p )
}
}
QStringList
PackageListModel::getInstallPackagesForName( const QString& id ) const
{
for ( const auto& p : qAsConst( m_packages ) )
{
if ( p.id == id )
{
return p.packageNames;
}
}
return QStringList();
}
QStringList
PackageListModel::getInstallPackagesForNames( const QStringList& ids ) const
{
QStringList l;
for ( const auto& p : qAsConst( m_packages ) )
{
if ( ids.contains( p.id ) )
{
l.append( p.packageNames );
}
}
return l;
}
int
PackageListModel::rowCount( const QModelIndex& index ) const
{

@ -18,24 +18,14 @@
#include <QPixmap>
#include <QVector>
enum class PackageChooserMode
{
Optional, // zero or one
Required, // exactly one
OptionalMultiple, // zero or more
RequiredMultiple // one or more
};
const NamedEnumTable< PackageChooserMode >& roleNames();
struct PackageItem
{
QString id;
// FIXME: unused
QString package;
CalamaresUtils::Locale::TranslatedString name;
CalamaresUtils::Locale::TranslatedString description;
QPixmap screenshot;
QStringList packageNames;
/// @brief Create blank PackageItem
PackageItem();
@ -44,7 +34,7 @@ struct PackageItem
* This constructor sets all the text members,
* but leaves the screenshot blank. Set that separately.
*/
PackageItem( const QString& id, const QString& package, const QString& name, const QString& description );
PackageItem( const QString& id, const QString& name, const QString& description );
/** @brief Creates a PackageItem from given strings.
*
@ -52,17 +42,21 @@ struct PackageItem
* @p screenshotPath, which may be a QRC path (:/path/in/qrc) or
* a filesystem path, whatever QPixmap understands.
*/
PackageItem( const QString& id,
const QString& package,
const QString& name,
const QString& description,
const QString& screenshotPath );
PackageItem( const QString& id, const QString& name, const QString& description, const QString& screenshotPath );
/** @brief Creates a PackageItem from a QVariantMap
*
* This is intended for use when loading PackageItems from a
* configuration map. It will look up the various keys in the map
* and handle translation strings as well.
*
* The following keys are used:
* - *id*: the identifier for this item; if it is the empty string
* then this is the special "no-package".
* - *name* (and *name[lang]*): for the name and its translations
* - *description* (and *description[lang]*)
* - *screenshot*: a path to a screenshot for this package
* - *packages*: a list of package names
*/
PackageItem( const QVariantMap& map );
@ -104,6 +98,19 @@ public:
/// @brief Direct (non-abstract) count of package data
int packageCount() const { return m_packages.count(); }
/** @brief Does a name lookup (based on id) and returns the packages member
*
* If there is a package with the given @p id, returns its packages
* (e.g. the names of underlying packages to install for it); returns
* an empty list if the id is not found.
*/
QStringList getInstallPackagesForName( const QString& id ) const;
/** @brief Name-lookup all the @p ids and returns the packages members
*
* Concatenates installPackagesForName() for each id in @p ids.
*/
QStringList getInstallPackagesForNames( const QStringList& ids ) const;
enum Roles : int
{
NameRole = Qt::DisplayRole,

@ -3,25 +3,46 @@
#
# Configuration for the low-density software chooser
---
# The packagechooser writes a GlobalStorage value for the choice that
# has been made. The key is *packagechooser_<id>*. If *id* is set here,
# it is substituted into the key name. If it is not set, the module's
# Software selection mode, to set whether the software packages
# can be chosen singly, or multiply.
#
# Possible modes are "optional", "required" (for zero-or-one or exactly-one)
# or "optionalmultiple", "requiredmultiple" (for zero-or-more
# or one-or-more).
mode: required
# Software installation method:
#
# - "legacy" or "custom" or "contextualprocess"
# When set to "legacy", writes a GlobalStorage value for the choice that
# has been made. The key is *packagechooser_<id>*. Normally, the module's
# instance name is used; see the *instances* section of `settings.conf`.
# If there is just one packagechooser module, and no *id* is set,
# resulting GS key is probably *packagechooser_packagechooser*.
# If there is just one packagechooser module, and no special instance is set,
# resulting GS key is probably *packagechooser@packagechooser*.
# You can set *id* to change that, but it is not recommended.
#
# The GS value is a comma-separated list of the IDs of the selected
# packages, or an empty string if none is selected.
#
# With "legacy" installation, you should have a contextualprocess or similar
# module somewhere in the `exec` phase to process the GlobalStorage key
# and actually **do** something for the packages.
#
# - "packages"
# When set to "packages", writes GlobalStorage values suitable for
# consumption by the *packages* module (which should appear later
# in the `exec` section. These package settings will then be handed
# off to whatever package manager is configured there.
# The *id* key is not used.
#
# There is no need to put this module in the `exec` section. There
# are no jobs that this module provides. You should put **other**
# modules, either *contextualprocess* or *packages* or some custom
# module, in the `exec` section to do the actual work.
method: legacy
# The *id* key is used only in "legacy" mode
# id: ""
# Software selection mode, to set whether the software packages
# can be chosen singly, or multiply.
#
# Possible modes are "optional", "required" (for zero or one)
# or "optionalmultiple", "requiredmultiple" (for zero-or-more
# or one-or-more).
mode: required
# Human-visible strings in this module. These are all optional.
# The following translated keys are used:
@ -49,27 +70,45 @@ labels:
# as a source for the data.
#
# For data provided by the list: the item has an id, which is used in
# setting the value of *packagechooser_<module-id>*. The following fields
# are mandatory:
# setting the value of *packagechooser_<module-id>*. The following field
# is mandatory:
#
# - *id* : ID for the product. The ID "" is special, and is used for
# - *id*
# ID for the product. The ID "" is special, and is used for
# "no package selected". Only include this if the mode allows
# selecting none.
# - *package* : Package name for the product. While mandatory, this is
# not actually used anywhere.
# - *name* : Human-readable name of the product. To provide translations,
# add a *[lang]* decoration as part of the key name,
# e.g. `name[nl]` for Dutch.
# The list of usable languages can be found in
# selecting none. The name and description given for the "no package
# selected" item are displayed when the module starts.
#
# Each item must adhere to one of three "styles" of item. Which styles
# are supported depends on compile-time dependencies of Calamares.
# Both AppData and AppStream may **optionally** be available.
#
# # Generic Items #
#
# These items are always supported. They require the most configuration
# **in this file** and duplicate information that may be available elsewhere
# (e.g. in AppData or AppStream), but do not require any additional
# dependencies. These items have the following **mandatory** fields:
#
# - *name*
# Human-readable name of the product. To provide translations,
# add a *[lang]* decoration as part of the key name, e.g. `name[nl]`
# for Dutch. The list of usable languages can be found in
# `CMakeLists.txt` or as part of the debug output of Calamares.
# - *description* : Human-readable description. These can be translated
# as well.
# - *screenshot* : Path to a single screenshot of the product. May be
# a filesystem path or a QRC path,
# e.g. ":/images/no-selection.png".
# - *description*
# Human-readable description. These can be translated as well.
# - *screenshot*
# Path to a single screenshot of the product. May be a filesystem
# path or a QRC path, e.g. ":/images/no-selection.png".
#
# The following field is **optional** for an item:
#
# Use the empty string "" as ID / key for the "no selection" item if
# you want to customize the display of that item as well.
# - *packages* :
# List of package names for the product. If using the *method*
# "packages", consider this item mandatory (because otherwise
# selecting the item would install no packages).
#
# # AppData Items #
#
# For data provided by AppData XML: the item has an *appdata*
# key which points to an AppData XML file in the local filesystem.
@ -84,6 +123,8 @@ labels:
# **may** specify an ID or screenshot path, as above. This will override
# the settings from AppData.
#
# # AppStream Items #
#
# For data provided by AppStream cache: the item has an *appstream*
# key which matches the AppStream identifier in the cache (e.g.
# *org.kde.kwrite.desktop*). Data is retrieved from the AppStream
@ -93,19 +134,19 @@ labels:
# key which will override the data from AppStream.
items:
- id: ""
package: ""
# packages: [] # This item installs no packages
name: "No Desktop"
name[nl]: "Geen desktop"
description: "Please pick a desktop environment from the list. If you don't want to install a desktop, that's fine, your system will start up in text-only mode and you can install a desktop environment later."
description[nl]: "Kies eventueel een desktop-omgeving uit deze lijst. Als u geen desktop-omgeving wenst te gebruiken, kies er dan geen. In dat geval start het systeem straks op in tekst-modus en kunt u later alsnog een desktop-omgeving installeren."
screenshot: ":/images/no-selection.png"
- id: kde
package: kde
packages: [ kde-frameworks, kde-plasma, kde-gear ]
name: Plasma Desktop
description: "KDE Plasma Desktop, simple by default, a clean work area for real-world usage which intends to stay out of your way. Plasma is powerful when needed, enabling the user to create the workflow that makes them more effective to complete their tasks."
screenshot: ":/images/kde.png"
- id: gnome
package: gnome
packages: [ gnome-all ]
name: GNOME
description: GNU Networked Object Modeling Environment Desktop
screenshot: ":/images/gnome.png"

@ -256,6 +256,23 @@ class PMEntropy(PackageManager):
pass
class PMLuet(PackageManager):
backend = "luet"
def install(self, pkgs, from_local=False):
check_target_env_call(["luet", "install", "-y"] + pkgs)
def remove(self, pkgs):
check_target_env_call(["luet", "uninstall", "-y"] + pkgs)
def update_db(self):
# Luet checks for DB update everytime its ran.
pass
def update_system(self):
check_target_env_call(["luet", "upgrade", "-y"])
class PMPackageKit(PackageManager):
backend = "packagekit"

@ -1,13 +1,30 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The configuration for the package manager starts with the
# *backend* key, which picks one of the backends to use.
# In `main.py` there is a base class `PackageManager`.
# Implementations must subclass that and set a (class-level)
# property *backend* to the name of the backend (e.g. "dummy").
# That property is used to match against the *backend* key here.
#
# You will have to add such a class for your package manager.
# It is fairly simple Python code. The API is described in the
# abstract methods in class `PackageManager`. Mostly, the only
# trick is to figure out the correct commands to use, and in particular,
# whether additional switches are required or not. Some package managers
# have more installer-friendly defaults than others, e.g., DNF requires
# passing --disablerepo=* -C to allow removing packages without Internet
# connectivity, and it also returns an error exit code if the package did
# not exist to begin with.
---
#
# Which package manager to use, options are:
# - apk - Alpine Linux package manager
# - apt - APT frontend for DEB and RPM
# - dnf - DNF, the new RPM frontend
# - entropy - Sabayon package manager
# - entropy - Sabayon package manager (is being deprecated)
# - luet - Sabayon package manager (next-gen)
# - packagekit - PackageKit CLI tool
# - pacman - Pacman
# - pamac - Manjaro package manager

@ -13,6 +13,7 @@ properties:
- apt
- dnf
- entropy
- luet
- packagekit
- pacman
- pamac

@ -264,7 +264,8 @@ FillGlobalStorageJob::prettyDescription() const
"<strong>%2</strong>%4." )
.arg( path )
.arg( mountPoint )
.arg( fsType ) );
.arg( fsType )
.arg( QString() ) );
}
}
}

@ -27,6 +27,16 @@ def pretty_name():
return _("Configure Plymouth theme")
def detect_plymouth():
"""
Checks existence (runnability) of plymouth in the target system.
@return True if plymouth exists in the target, False otherwise
"""
# Used to only check existence of path /usr/bin/plymouth in target
return target_env_call(["sh", "-c", "which plymouth"]) == 0
class PlymouthController:
def __init__(self):
@ -42,14 +52,8 @@ class PlymouthController:
plymouth_theme + '|', "-i",
"/etc/plymouth/plymouthd.conf"])
def detect(self):
isPlymouth = target_env_call(["sh", "-c", "which plymouth"])
debug("which plymouth exit code: {!s}".format(isPlymouth))
return isPlymouth
def run(self):
if self.detect() == 0:
if detect_plymouth():
if (("plymouth_theme" in libcalamares.job.configuration) and
(libcalamares.job.configuration["plymouth_theme"] is not None)):
self.setTheme()

@ -803,6 +803,29 @@ addPasswordCheck( const QString& key, const QVariant& value, PasswordCheckList&
return true;
}
/** @brief Returns a value of either key from the map
*
* Takes a function (e.g. getBool, or getString) and two keys,
* returning the value in the map of the one that is there (or @p defaultArg)
*/
template < typename T, typename U >
T
either( T ( *f )( const QVariantMap&, const QString&, U ),
const QVariantMap& configurationMap,
const QString& oldKey,
const QString& newKey,
U defaultArg )
{
if ( configurationMap.contains( oldKey ) )
{
return f( configurationMap, oldKey, defaultArg );
}
else
{
return f( configurationMap, newKey, defaultArg );
}
}
void
Config::setConfigurationMap( const QVariantMap& configurationMap )
{
@ -814,13 +837,21 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
// Now it might be explicitly set to empty, which is ok
setUserShell( shell );
setAutoLoginGroup( CalamaresUtils::getString( configurationMap, "autoLoginGroup" ) );
setAutoLoginGroup( either< QString, const QString& >(
CalamaresUtils::getString, configurationMap, "autologinGroup", "autoLoginGroup", QString() ) );
setSudoersGroup( CalamaresUtils::getString( configurationMap, "sudoersGroup" ) );
m_hostNameActions = getHostNameActions( configurationMap );
setConfigurationDefaultGroups( configurationMap, m_defaultGroups );
m_doAutoLogin = CalamaresUtils::getBool( configurationMap, "doAutoLogin", false );
// Renaming of Autologin -> AutoLogin in 4ffa79d4cf also affected
// configuration keys, which was not intended. Accept both.
m_doAutoLogin = either( CalamaresUtils::getBool,
configurationMap,
QStringLiteral( "doAutologin" ),
QStringLiteral( "doAutoLogin" ),
false );
m_writeRootPassword = CalamaresUtils::getBool( configurationMap, "setRootPassword", true );
Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", m_writeRootPassword );

@ -44,6 +44,9 @@ private Q_SLOTS:
void testHostActions();
void testPasswordChecks();
void testUserPassword();
void testAutoLogin_data();
void testAutoLogin();
};
UserTests::UserTests() {}
@ -339,6 +342,43 @@ UserTests::testUserPassword()
}
}
void
UserTests::testAutoLogin_data()
{
QTest::addColumn< QString >( "filename" );
QTest::addColumn< bool >( "autoLoginIsSet" );
QTest::addColumn< QString >( "autoLoginGroupName" );
QTest::newRow( "old, old" ) << "tests/6a-issue-1672.conf" << true << "derp";
QTest::newRow( "old, new" ) << "tests/6b-issue-1672.conf" << true << "derp";
QTest::newRow( "new, old" ) << "tests/6c-issue-1672.conf" << true << "derp";
QTest::newRow( "new, new" ) << "tests/6d-issue-1672.conf" << true << "derp";
QTest::newRow( "default" ) << "tests/6e-issue-1672.conf" << false << QString();
}
void
UserTests::testAutoLogin()
{
QFETCH( QString, filename );
QFETCH( bool, autoLoginIsSet );
QFETCH( QString, autoLoginGroupName );
// BUILD_AS_TEST is the source-directory path
QFile fi( QString( "%1/%2" ).arg( BUILD_AS_TEST, filename ) );
QVERIFY( fi.exists() );
bool ok = false;
const auto map = CalamaresUtils::loadYaml( fi, &ok );
QVERIFY( ok );
QVERIFY( map.count() > 0 );
Config c;
c.setConfigurationMap( map );
QCOMPARE( c.doAutoLogin(), autoLoginIsSet );
QCOMPARE( c.autoLoginGroup(), autoLoginGroupName );
}
QTEST_GUILESS_MAIN( UserTests )

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
autologinGroup: derp
doAutologin: true

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
autologinGroup: derp
doAutoLogin: true

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
autoLoginGroup: derp
doAutologin: true

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
autoLoginGroup: derp
doAutoLogin: true

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
doautologin: true
autologingroup: wheel
Loading…
Cancel
Save