diff --git a/.editorconfig b/.editorconfig index b2ae0dd2b..d282d6273 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,3 +11,8 @@ trim_trailing_whitespace = true indent_style = space indent_size = 4 insert_final_newline = true + +[*.sh] +indent_style = tab +insert_final_newline = true + diff --git a/ci/txcheck.sh b/ci/txcheck.sh index a2130c9d5..9ce8f0c30 100644 --- a/ci/txcheck.sh +++ b/ci/txcheck.sh @@ -1,5 +1,18 @@ #! /bin/sh +### LICENSE +# === This file is part of Calamares - === +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2019-2020 Adriaan de Groot +# +# This file is Free Software: you can redistribute it and/or modify +# it under the terms of the 2-clause BSD License. +# +### END LICENSE + +### USAGE +# # Does the translation tag (from a previous txpush) exist? # This assumes that the release host has also locally done # a translations push, which works for the current development @@ -7,6 +20,13 @@ # the typical txpush log messages instead of the tag. # # Use --cleanup as an argument to clean things up. +# +# Normal use: +# $ sh ci/txcheck.sh +# If there are differences, fix them and then clean up: +# $ sh ci/txcheck.sh --cleanup +# +### END USAGE # The files that are translated; should match the contents of .tx/config TX_FILE_LIST="lang/calamares_en.ts lang/python.pot src/modules/dummypythonqt/lang/dummypythonqt.pot calamares.desktop" @@ -17,15 +37,18 @@ TX_FILE_LIST="lang/calamares_en.ts lang/python.pot src/modules/dummypythonqt/lan # normally used much later in the script. tx_cleanup() { - # Cleanup artifacs of checking - git worktree remove --force build-txcheck-head - git worktree remove --force build-txcheck-prev - git branch -D build-txcheck-head > /dev/null 2>&1 + # Cleanup artifacs of checking + git worktree remove --force build-txcheck-head + git worktree remove --force build-txcheck-prev + git branch -D build-txcheck-head > /dev/null 2>&1 } if test "x$1" = "x--cleanup" ; then - tx_cleanup - exit 0 + tx_cleanup + exit 0 +fi +if test "x$1" = "x--help" ; then + sed -e '1,/USAGE/d' -e '/END.USAGE/,$d' < "$0" fi test -z "$1" || { echo "! Usage: txcheck.sh [--cleanup]" ; exit 1 ; } @@ -36,22 +59,22 @@ test -z "$1" || { echo "! Usage: txcheck.sh [--cleanup]" ; exit 1 ; } XMLLINT="" for _xmllint in xmllint do - $_xmllint --version > /dev/null 2>&1 && XMLLINT=$_xmllint - test -n "$XMLLINT" && break + $_xmllint --version > /dev/null 2>&1 && XMLLINT=$_xmllint + test -n "$XMLLINT" && break done # Distinguish GNU date from BSD date if date +%s -d "1 week ago" > /dev/null 2>&1 ; then - last_week() { date +%s -d "1 week ago" ; } + last_week() { date +%s -d "1 week ago" ; } else - last_week() { date -v1w +%s; } + last_week() { date -v1w +%s; } fi # Distinguish GNU SHA executables from BSD ones if which sha256sum > /dev/null 2>&1 ; then - SHA256=sha256sum + SHA256=sha256sum else - SHA256=sha256 + SHA256=sha256 fi ### CHECK WORKING DIRECTORY @@ -67,6 +90,8 @@ fi if test `git describe` = `git describe --dirty` ; then : else + # Don't want any local changes, since those won't be + # reflected in the worktrees and we might miss a string change. echo "! There are local changes." exit 1 fi @@ -75,13 +100,20 @@ DATE_PREV=$( git log -1 translation --date=unix | sed -e '/^Date:/s+.*:++p' -e d DATE_HEAD=$( last_week ) test "$DATE_PREV" -le "$DATE_HEAD" || { echo "! Translation tag has not aged enough." ; git log -1 translation ; exit 1 ; } -# Tag is good, do real work of checking strings: collect names of relevant files +# Tag is good, check that necessary files exist. The list of +# files is hard-coded, but should match what is in the Transifex config. test -f ".tx/config" || { echo "! No Transifex configuration is present." ; exit 1 ; } for f in $TX_FILE_LIST ; do test -f $f || { echo "! Translation file '$f' does not exist." ; exit 1 ; } done -# The state of translations +### COMPARE TRANSLATIONS +# +# + +# The state of translations; assume that sha256 is enough +# to distinguish changed translations when we cat all the +# string sources together. tx_sum() { CURDIR=`pwd` diff --git a/ci/txpull.sh b/ci/txpull.sh index b320da300..734a689b5 100755 --- a/ci/txpull.sh +++ b/ci/txpull.sh @@ -1,4 +1,18 @@ #!/bin/sh + +### LICENSE +# === This file is part of Calamares - === +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2017-2020 Adriaan de Groot +# SPDX-FileCopyrightText: 2015-2016 Teo Mrnjavac +# +# This file is Free Software: you can redistribute it and/or modify +# it under the terms of the 2-clause BSD License. +# +### END LICENSE + +### USAGE # # Fetch the Transifex translations for Calamares and incorporate them # into the source tree, adding commits of the different files. @@ -6,6 +20,8 @@ # Run this (occasionally) at the top-level directory to get # new translations. See also CMakeLists.txt and ci/txstats.py # for update instructions. +# +### END USAGE ### INITIAL SETUP # @@ -32,8 +48,8 @@ test -f "calamares.desktop" || { echo "! Not at Calamares top-level" ; exit 1 ; XMLLINT="" for _xmllint in xmllint do - $_xmllint --version > /dev/null 2>&1 && XMLLINT=$_xmllint - test -n "$XMLLINT" && break + $_xmllint --version > /dev/null 2>&1 && XMLLINT=$_xmllint + test -n "$XMLLINT" && break done # XMLLINT is optional @@ -53,9 +69,9 @@ tx pull --force --source --all # so clean them up after pulling. # drop_language() { - rm -rf lang/python/"$1" src/modules/dummypythonqt/lang/"$1" lang/calamares_"$1".ts - grep -v "\\[$1]" calamares.desktop > calamares.desktop.new - mv calamares.desktop.new calamares.desktop + rm -rf lang/python/"$1" src/modules/dummypythonqt/lang/"$1" lang/calamares_"$1".ts + grep -v "\\[$1]" calamares.desktop > calamares.desktop.new + mv calamares.desktop.new calamares.desktop } drop_language es_ES @@ -68,10 +84,10 @@ mv calamares.desktop.new calamares.desktop # And fixup the XML files like in txpush.sh if test -n "$XMLLINT" ; then - for TS_FILE in lang/calamares_*.ts - do - $XMLLINT --c14n11 "$TS_FILE" | { echo "" ; cat - ; } | $XMLLINT --format --encode utf-8 -o "$TS_FILE".new - && mv "$TS_FILE".new "$TS_FILE" - done + for TS_FILE in lang/calamares_*.ts + do + $XMLLINT --c14n11 "$TS_FILE" | { echo "" ; cat - ; } | $XMLLINT --format --encode utf-8 -o "$TS_FILE".new - && mv "$TS_FILE".new "$TS_FILE" + done fi @@ -108,24 +124,24 @@ git diff --numstat src/modules | awk '($1==1 && $2==1){print $3}' | xargs git ch # Go through the Python modules; those with a lang/ subdir have their # own complete gettext-based setup. for MODULE_DIR in $(find src/modules -maxdepth 1 -mindepth 1 -type d) ; do - FILES=$(find "$MODULE_DIR" -name "*.py" -a -type f) - if test -n "$FILES" ; then - MODULE_NAME=$(basename ${MODULE_DIR}) - if [ -d ${MODULE_DIR}/lang ]; then - # Convert PO files to MO files - for POFILE in $(find ${MODULE_DIR} -name "*.po") ; do - sed -i'' '/^"Content-Type/s/CHARSET/UTF-8/' $POFILE - msgfmt -o ${POFILE%.po}.mo $POFILE - done - git add --verbose ${MODULE_DIR}/lang/* - git commit "$AUTHOR" --message="i18n: [${MODULE_NAME}] $BOILERPLATE" | true - fi - fi + FILES=$(find "$MODULE_DIR" -name "*.py" -a -type f) + if test -n "$FILES" ; then + MODULE_NAME=$(basename ${MODULE_DIR}) + if [ -d ${MODULE_DIR}/lang ]; then + # Convert PO files to MO files + for POFILE in $(find ${MODULE_DIR} -name "*.po") ; do + sed -i'' '/^"Content-Type/s/CHARSET/UTF-8/' $POFILE + msgfmt -o ${POFILE%.po}.mo $POFILE + done + git add --verbose ${MODULE_DIR}/lang/* + git commit "$AUTHOR" --message="i18n: [${MODULE_NAME}] $BOILERPLATE" | true + fi + fi done for POFILE in $(find lang -name "python.po") ; do - sed -i'' '/^"Content-Type/s/CHARSET/UTF-8/' $POFILE - msgfmt -o ${POFILE%.po}.mo $POFILE + sed -i'' '/^"Content-Type/s/CHARSET/UTF-8/' $POFILE + msgfmt -o ${POFILE%.po}.mo $POFILE done git add --verbose lang/python* git commit "$AUTHOR" --message="i18n: [python] $BOILERPLATE" | true diff --git a/ci/txpush.sh b/ci/txpush.sh index 6882fa523..3195f42b4 100755 --- a/ci/txpush.sh +++ b/ci/txpush.sh @@ -1,13 +1,33 @@ #!/bin/sh + +### LICENSE +# === This file is part of Calamares - === +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2017-2020 Adriaan de Groot +# SPDX-FileCopyrightText: 2015-2016 Teo Mrnjavac +# +# This file is Free Software: you can redistribute it and/or modify +# it under the terms of the 2-clause BSD License. +# +### END LICENSE + +### USAGE # # Extract translations from Calamares source and send them -# to Transifex. +# to Transifex. Also (forcibly) updates the git "translation" +# tag to document that source texts were updated and sent; +# this is used by txcheck.sh to ensure that there's enough +# time between updates and releases, and that strings don't +# change between updates and releases. # # Run this at the top-level. # # Use the --no-tx option to do the extraction, but not the # pushing-to-Transifex part. This can be useful to check for # new strings or when testing the tools themselves. +# +### END USAGE ### INITIAL SETUP # @@ -28,21 +48,21 @@ test -f ".tx/config" || { echo "! Not at Calamares top-level" ; exit 1 ; } test -f "calamares.desktop" || { echo "! Not at Calamares top-level" ; exit 1 ; } if test "x$1" = "x--no-tx" ; then - # tx is the transifex command -- eat its arguments and do nothing - tx() { - echo "Skipped tx $*" - } - # txtag is used to tag in git to measure changes -- skip it too - txtag() { - echo "Skipped tx tagging." - } + # tx is the transifex command -- eat its arguments and do nothing + tx() { + echo "Skipped tx $*" + } + # txtag is used to tag in git to measure changes -- skip it too + txtag() { + echo "Skipped tx tagging." + } else - # tx is the regular transifex command - # txtag is used to tag in git to measure changes - txtag() { - git tag -f translation - git push --force origin translation - } + # tx is the regular transifex command + # txtag is used to tag in git to measure changes + txtag() { + git tag -f translation + git push --force origin translation + } fi @@ -52,18 +72,18 @@ fi LUPDATE="" for _lupdate in lupdate-qt5 lupdate do - export QT_SELECT=5 - $_lupdate -version > /dev/null 2>&1 || export QT_SELECT=qt5 - $_lupdate -version > /dev/null 2>&1 && LUPDATE=$_lupdate - test -n "$LUPDATE" && break + export QT_SELECT=5 + $_lupdate -version > /dev/null 2>&1 || export QT_SELECT=qt5 + $_lupdate -version > /dev/null 2>&1 && LUPDATE=$_lupdate + test -n "$LUPDATE" && break done test -n "$LUPDATE" || { echo "! No working lupdate" ; lupdate -version ; exit 1 ; } XMLLINT="" for _xmllint in xmllint do - $_xmllint --version > /dev/null 2>&1 && XMLLINT=$_xmllint - test -n "$XMLLINT" && break + $_xmllint --version > /dev/null 2>&1 && XMLLINT=$_xmllint + test -n "$XMLLINT" && break done # XMLLINT is optional @@ -82,8 +102,8 @@ $LUPDATE -no-obsolete $_srcdirs -ts lang/calamares_en.ts # $LUPDATE -no-obsolete -extensions cxxtr src/libcalamares/locale -ts lang/tz_en.ts if test -n "$XMLLINT" ; then - TS_FILE="lang/calamares_en.ts" - $XMLLINT --c14n11 "$TS_FILE" | { echo "" ; cat - ; } | $XMLLINT --format --encode utf-8 -o "$TS_FILE".new - && mv "$TS_FILE".new "$TS_FILE" + TS_FILE="lang/calamares_en.ts" + $XMLLINT --c14n11 "$TS_FILE" | { echo "" ; cat - ; } | $XMLLINT --format --encode utf-8 -o "$TS_FILE".new - && mv "$TS_FILE".new "$TS_FILE" fi tx push --source --no-interactive -r calamares.calamares-master @@ -103,29 +123,29 @@ PYGETTEXT="xgettext --keyword=_n:1,2 -L python" SHARED_PYTHON="" for MODULE_DIR in $(find src/modules -maxdepth 1 -mindepth 1 -type d) ; do - FILES=$(find "$MODULE_DIR" -name "*.py" -a -type f) - if test -n "$FILES" ; then - MODULE_NAME=$(basename ${MODULE_DIR}) - if [ -d ${MODULE_DIR}/lang ]; then - ${PYGETTEXT} -p ${MODULE_DIR}/lang -d ${MODULE_NAME} -o ${MODULE_NAME}.pot ${MODULE_DIR}/*.py - POTFILE="${MODULE_DIR}/lang/${MODULE_NAME}.pot" - if [ -f "$POTFILE" ]; then - sed -i'' '/^"Content-Type/s/CHARSET/UTF-8/' "$POTFILE" - tx set -r calamares.${MODULE_NAME} --source -l en "$POTFILE" - tx push --source --no-interactive -r calamares.${MODULE_NAME} - fi - else - SHARED_PYTHON="$SHARED_PYTHON $FILES" - fi - fi + FILES=$(find "$MODULE_DIR" -name "*.py" -a -type f) + if test -n "$FILES" ; then + MODULE_NAME=$(basename ${MODULE_DIR}) + if [ -d ${MODULE_DIR}/lang ]; then + ${PYGETTEXT} -p ${MODULE_DIR}/lang -d ${MODULE_NAME} -o ${MODULE_NAME}.pot ${MODULE_DIR}/*.py + POTFILE="${MODULE_DIR}/lang/${MODULE_NAME}.pot" + if [ -f "$POTFILE" ]; then + sed -i'' '/^"Content-Type/s/CHARSET/UTF-8/' "$POTFILE" + tx set -r calamares.${MODULE_NAME} --source -l en "$POTFILE" + tx push --source --no-interactive -r calamares.${MODULE_NAME} + fi + else + SHARED_PYTHON="$SHARED_PYTHON $FILES" + fi + fi done if test -n "$SHARED_PYTHON" ; then - ${PYGETTEXT} -p lang -d python -o python.pot $SHARED_PYTHON - POTFILE="lang/python.pot" - sed -i'' '/^"Content-Type/s/CHARSET/UTF-8/' "$POTFILE" - tx set -r calamares.python --source -l en "$POTFILE" - tx push --source --no-interactive -r calamares.python + ${PYGETTEXT} -p lang -d python -o python.pot $SHARED_PYTHON + POTFILE="lang/python.pot" + sed -i'' '/^"Content-Type/s/CHARSET/UTF-8/' "$POTFILE" + tx set -r calamares.python --source -l en "$POTFILE" + tx push --source --no-interactive -r calamares.python fi txtag diff --git a/src/branding/default/branding.desc b/src/branding/default/branding.desc index f70715aea..af1d39ca4 100644 --- a/src/branding/default/branding.desc +++ b/src/branding/default/branding.desc @@ -69,11 +69,11 @@ windowPlacement: center strings: productName: "@{NAME}" shortProductName: Generic - version: 2017.8 LTS - shortVersion: 2017.8 - versionedName: Generic GNU/Linux 2017.8 LTS "Soapy Sousaphone" - shortVersionedName: Generic 2017.8 - bootloaderEntryName: Generic + version: 2020.2 LTS + shortVersion: 2020.2 + versionedName: Fancy GNU/Linux 2020.2 LTS "Turgid Tuba" + shortVersionedName: FancyGL 2020.2 + bootloaderEntryName: FancyGL productUrl: https://calamares.io/ supportUrl: https://github.com/calamares/calamares/issues knownIssuesUrl: https://calamares.io/about/ diff --git a/src/calamares/CalamaresApplication.cpp b/src/calamares/CalamaresApplication.cpp index 1584b11fa..48e54b76e 100644 --- a/src/calamares/CalamaresApplication.cpp +++ b/src/calamares/CalamaresApplication.cpp @@ -42,12 +42,17 @@ #include #include +/// @brief Convenience for "are the settings in debug mode" +static bool +isDebug() +{ + return Calamares::Settings::instance() && Calamares::Settings::instance()->debugMode(); +} CalamaresApplication::CalamaresApplication( int& argc, char* argv[] ) : QApplication( argc, argv ) , m_mainwindow( nullptr ) , m_moduleManager( nullptr ) - , m_debugMode( false ) { // Setting the organization name makes the default cache // directory -- where Calamares stores logs, for instance -- @@ -59,8 +64,6 @@ CalamaresApplication::CalamaresApplication( int& argc, char* argv[] ) setApplicationName( QStringLiteral( CALAMARES_APPLICATION_NAME ) ); setApplicationVersion( QStringLiteral( CALAMARES_VERSION ) ); - CalamaresUtils::installTranslator( QLocale::system(), QString(), this ); - QFont f = font(); CalamaresUtils::setDefaultFontSize( f.pointSize() ); } @@ -73,15 +76,20 @@ CalamaresApplication::init() cDebug() << "Calamares version:" << CALAMARES_VERSION; cDebug() << " languages:" << QString( CALAMARES_TRANSLATION_LANGUAGES ).replace( ";", ", " ); - setQuitOnLastWindowClosed( false ); - + if ( !Calamares::Settings::instance() ) + { + cError() << "Must create Calamares::Settings before the application."; + ::exit( 1 ); + } initQmlPath(); - initSettings(); initBranding(); + CalamaresUtils::installTranslator( QLocale::system(), QString(), this ); + + setQuitOnLastWindowClosed( false ); setWindowIcon( QIcon( Calamares::Branding::instance()->imagePath( Calamares::Branding::ProductIcon ) ) ); - cDebug() << "STARTUP: initQmlPath, initSettings, initBranding done"; + cDebug() << "STARTUP: initSettings, initQmlPath, initBranding done"; initModuleManager(); //also shows main window @@ -103,20 +111,6 @@ CalamaresApplication::instance() } -void -CalamaresApplication::setDebug( bool enabled ) -{ - m_debugMode = enabled; -} - - -bool -CalamaresApplication::isDebug() -{ - return m_debugMode; -} - - CalamaresWindow* CalamaresApplication::mainWindow() { @@ -152,35 +146,6 @@ qmlDirCandidates( bool assumeBuilddir ) } -static QStringList -settingsFileCandidates( bool assumeBuilddir ) -{ - static const char settings[] = "settings.conf"; - - QStringList settingsPaths; - if ( CalamaresUtils::isAppDataDirOverridden() ) - { - settingsPaths << CalamaresUtils::appDataDir().absoluteFilePath( settings ); - } - else - { - if ( assumeBuilddir ) - { - settingsPaths << QDir::current().absoluteFilePath( settings ); - } - if ( CalamaresUtils::haveExtraDirs() ) - for ( auto s : CalamaresUtils::extraConfigDirs() ) - { - settingsPaths << ( s + settings ); - } - settingsPaths << CMAKE_INSTALL_FULL_SYSCONFDIR "/calamares/settings.conf"; // String concat - settingsPaths << CalamaresUtils::appDataDir().absoluteFilePath( settings ); - } - - return settingsPaths; -} - - static QStringList brandingFileCandidates( bool assumeBuilddir, const QString& brandingFilename ) { @@ -246,49 +211,6 @@ CalamaresApplication::initQmlPath() } -void -CalamaresApplication::initSettings() -{ - QStringList settingsFileCandidatesByPriority = settingsFileCandidates( isDebug() ); - - QFileInfo settingsFile; - bool found = false; - - foreach ( const QString& path, settingsFileCandidatesByPriority ) - { - QFileInfo pathFi( path ); - if ( pathFi.exists() && pathFi.isReadable() ) - { - settingsFile = pathFi; - found = true; - break; - } - } - - if ( !found || !settingsFile.exists() || !settingsFile.isReadable() ) - { - cError() << "Cowardly refusing to continue startup without settings." - << Logger::DebugList( settingsFileCandidatesByPriority ); - if ( CalamaresUtils::isAppDataDirOverridden() ) - { - cError() << "FATAL: explicitly configured application data directory is missing settings.conf"; - } - else - { - cError() << "FATAL: none of the expected configuration file paths exist."; - } - ::exit( EXIT_FAILURE ); - } - - auto* settings = new Calamares::Settings( settingsFile.absoluteFilePath(), isDebug(), this ); // Creates singleton - if ( settings->modulesSequence().count() < 1 ) - { - cError() << "FATAL: no sequence set."; - ::exit( EXIT_FAILURE ); - } -} - - void CalamaresApplication::initBranding() { diff --git a/src/calamares/CalamaresApplication.h b/src/calamares/CalamaresApplication.h index 634f4cdb2..f42c21b56 100644 --- a/src/calamares/CalamaresApplication.h +++ b/src/calamares/CalamaresApplication.h @@ -49,16 +49,6 @@ public: void init(); static CalamaresApplication* instance(); - /** - * @brief setDebug controls whether debug mode is enabled - */ - void setDebug( bool enabled ); - - /** - * @brief isDebug returns true if running in debug mode, otherwise false. - */ - bool isDebug(); - /** * @brief mainWindow returns the Calamares application main window. */ @@ -70,16 +60,14 @@ private slots: void initFailed( const QStringList& l ); private: + // Initialization steps happen in this order void initQmlPath(); - void initSettings(); void initBranding(); void initModuleManager(); void initJobQueue(); CalamaresWindow* m_mainwindow; Calamares::ModuleManager* m_moduleManager; - - bool m_debugMode; }; #endif // CALAMARESAPPLICATION_H diff --git a/src/calamares/main.cpp b/src/calamares/main.cpp index caf1f6cfd..670b7a654 100644 --- a/src/calamares/main.cpp +++ b/src/calamares/main.cpp @@ -20,9 +20,10 @@ #include "CalamaresApplication.h" -#include "CalamaresConfig.h" +#include "Settings.h" #include "utils/Dirs.h" #include "utils/Logger.h" +#include "utils/Retranslator.h" #include "3rdparty/kdsingleapplicationguard/kdsingleapplicationguard.h" @@ -35,6 +36,21 @@ #include #include +static unsigned int +debug_level( QCommandLineParser& parser, QCommandLineOption& levelOption ) +{ + bool ok = true; + int l = parser.value( levelOption ).toInt( &ok ); + if ( !ok || ( l < 0 ) ) + { + return Logger::LOGVERBOSE; + } + else + { + return static_cast< unsigned int >( l ); // l >= 0 + } +} + static void handle_args( CalamaresApplication& a ) { @@ -42,6 +58,9 @@ handle_args( CalamaresApplication& a ) "Also look in current directory for configuration. Implies -D8." ); QCommandLineOption debugLevelOption( QStringLiteral( "D" ), "Verbose output for debugging purposes (0-8).", "level" ); + QCommandLineOption debugTxOption( QStringList { "T", "debug-translation" }, + "Also look in the current directory for translation." ); + QCommandLineOption configOption( QStringList { "c", "config" }, "Configuration directory to use, for testing purposes.", "config" ); QCommandLineOption xdgOption( QStringList { "X", "xdg-config" }, "Use XDG_{CONFIG,DATA}_DIRS as well." ); @@ -55,29 +74,11 @@ handle_args( CalamaresApplication& a ) parser.addOption( debugLevelOption ); parser.addOption( configOption ); parser.addOption( xdgOption ); + parser.addOption( debugTxOption ); parser.process( a ); - a.setDebug( parser.isSet( debugOption ) ); - if ( parser.isSet( debugOption ) ) - { - Logger::setupLogLevel( Logger::LOGVERBOSE ); - } - else if ( parser.isSet( debugLevelOption ) ) - { - bool ok = true; - int l = parser.value( debugLevelOption ).toInt( &ok ); - unsigned int dlevel = 0; - if ( !ok || ( l < 0 ) ) - { - dlevel = Logger::LOGVERBOSE; - } - else - { - dlevel = static_cast< unsigned int >( l ); // l >= 0 - } - Logger::setupLogLevel( dlevel ); - } + Logger::setupLogLevel( parser.isSet( debugOption ) ? Logger::LOGVERBOSE : debug_level( parser, debugLevelOption ) ); if ( parser.isSet( configOption ) ) { CalamaresUtils::setAppDataDir( QDir( parser.value( configOption ) ) ); @@ -86,6 +87,9 @@ handle_args( CalamaresApplication& a ) { CalamaresUtils::setXdgDirs(); } + CalamaresUtils::setAllowLocalTranslation( parser.isSet( debugOption ) || parser.isSet( debugTxOption ) ); + Calamares::Settings::init( parser.isSet( debugOption ) ); + a.init(); } int @@ -113,14 +117,11 @@ main( int argc, char* argv[] ) // TODO: umount anything in /tmp/calamares-... as an emergency save function #endif - handle_args( a ); KDSingleApplicationGuard guard( KDSingleApplicationGuard::AutoKillOtherInstances ); - - int returnCode = 0; if ( guard.isPrimaryInstance() ) { - a.init(); - returnCode = a.exec(); + handle_args( a ); + return a.exec(); } else { @@ -135,7 +136,6 @@ main( int argc, char* argv[] ) { qDebug() << " " << i.isValid() << i.pid() << i.arguments(); } + return 69; // EX_UNAVAILABLE on FreeBSD } - - return returnCode; } diff --git a/src/calamares/testmain.cpp b/src/calamares/testmain.cpp index 885915041..0845218eb 100644 --- a/src/calamares/testmain.cpp +++ b/src/calamares/testmain.cpp @@ -225,7 +225,7 @@ main( int argc, char* argv[] ) return 1; } - std::unique_ptr< Calamares::Settings > settings_p( new Calamares::Settings( QString(), true ) ); + std::unique_ptr< Calamares::Settings > settings_p( Calamares::Settings::init( QString() ) ); std::unique_ptr< Calamares::JobQueue > jobqueue_p( new Calamares::JobQueue( nullptr ) ); QMainWindow* mw = nullptr; diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt index 0e7f7c6e7..cad7e7a6e 100644 --- a/src/libcalamares/CMakeLists.txt +++ b/src/libcalamares/CMakeLists.txt @@ -171,6 +171,18 @@ if ( ECM_FOUND AND BUILD_TESTING ) ) calamares_automoc( libcalamarestest ) + ecm_add_test( + utils/TestPaths.cpp + TEST_NAME + libcalamarestestpaths + LINK_LIBRARIES + calamares + Qt5::Core + Qt5::Test + ) + calamares_automoc( libcalamarestestpaths ) + + ecm_add_test( geoip/GeoIPTests.cpp ${geoip_src} diff --git a/src/libcalamares/Settings.cpp b/src/libcalamares/Settings.cpp index 456956430..48f8c606d 100644 --- a/src/libcalamares/Settings.cpp +++ b/src/libcalamares/Settings.cpp @@ -21,6 +21,7 @@ #include "Settings.h" +#include "CalamaresConfig.h" #include "utils/Dirs.h" #include "utils/Logger.h" #include "utils/Yaml.h" @@ -193,8 +194,8 @@ interpretSequence( const YAML::Node& node, Settings::ModuleSequence& moduleSeque } } -Settings::Settings( const QString& settingsFilePath, bool debugMode, QObject* parent ) - : QObject( parent ) +Settings::Settings( const QString& settingsFilePath, bool debugMode ) + : QObject() , m_debug( debugMode ) , m_doChroot( true ) , m_promptInstall( false ) @@ -265,37 +266,93 @@ Settings::brandingComponentName() const return m_brandingComponentName; } - -bool -Settings::showPromptBeforeExecution() const +static QStringList +settingsFileCandidates( bool assumeBuilddir ) { - return m_promptInstall; + static const char settings[] = "settings.conf"; + + QStringList settingsPaths; + if ( CalamaresUtils::isAppDataDirOverridden() ) + { + settingsPaths << CalamaresUtils::appDataDir().absoluteFilePath( settings ); + } + else + { + if ( assumeBuilddir ) + { + settingsPaths << QDir::current().absoluteFilePath( settings ); + } + if ( CalamaresUtils::haveExtraDirs() ) + for ( auto s : CalamaresUtils::extraConfigDirs() ) + { + settingsPaths << ( s + settings ); + } + settingsPaths << CMAKE_INSTALL_FULL_SYSCONFDIR "/calamares/settings.conf"; // String concat + settingsPaths << CalamaresUtils::appDataDir().absoluteFilePath( settings ); + } + + return settingsPaths; } - -bool -Settings::debugMode() const +Settings* +Settings::init( bool debugMode ) { - return m_debug; + if ( s_instance ) + { + cWarning() << "Calamares::Settings already created"; + return s_instance; + } + + QStringList settingsFileCandidatesByPriority = settingsFileCandidates( debugMode ); + + QFileInfo settingsFile; + bool found = false; + + foreach ( const QString& path, settingsFileCandidatesByPriority ) + { + QFileInfo pathFi( path ); + if ( pathFi.exists() && pathFi.isReadable() ) + { + settingsFile = pathFi; + found = true; + break; + } + } + + if ( !found || !settingsFile.exists() || !settingsFile.isReadable() ) + { + cError() << "Cowardly refusing to continue startup without settings." + << Logger::DebugList( settingsFileCandidatesByPriority ); + if ( CalamaresUtils::isAppDataDirOverridden() ) + { + cError() << "FATAL: explicitly configured application data directory is missing settings.conf"; + } + else + { + cError() << "FATAL: none of the expected configuration file paths exist."; + } + ::exit( EXIT_FAILURE ); + } + + auto* settings = new Calamares::Settings( settingsFile.absoluteFilePath(), debugMode ); // Creates singleton + if ( settings->modulesSequence().count() < 1 ) + { + cError() << "FATAL: no sequence set."; + ::exit( EXIT_FAILURE ); + } + + return settings; } -bool -Settings::doChroot() const +Settings* +Settings::init( const QString& path ) { - return m_doChroot; + if ( s_instance ) + { + cWarning() << "Calamares::Settings already created"; + return s_instance; + } + return new Calamares::Settings( path, true ); } -bool -Settings::disableCancel() const -{ - return m_disableCancel; -} - -bool -Settings::disableCancelDuringExec() const -{ - return m_disableCancelDuringExec; -} - - } // namespace Calamares diff --git a/src/libcalamares/Settings.h b/src/libcalamares/Settings.h index 4c2f2ed9d..26990f027 100644 --- a/src/libcalamares/Settings.h +++ b/src/libcalamares/Settings.h @@ -35,11 +35,14 @@ namespace Calamares class DLLEXPORT Settings : public QObject { Q_OBJECT + explicit Settings( const QString& settingsFilePath, bool debugMode ); public: - explicit Settings( const QString& settingsFilePath, bool debugMode, QObject* parent = nullptr ); - static Settings* instance(); - + /// @brief Find a settings.conf, following @p debugMode + static Settings* init( bool debugMode ); + /// @brief Explicif filename, debug is always true (for testing) + static Settings* init( const QString& filename ); + QStringList modulesSearchPaths() const; using InstanceDescription = QMap< QString, QString >; @@ -51,11 +54,31 @@ public: QString brandingComponentName() const; - bool showPromptBeforeExecution() const; + /** @brief Is this a debugging run? + * + * Returns true if Calamares is in debug mode. In debug mode, + * modules and settings are loaded from more locations, to help + * development and debugging. + */ + bool debugMode() const { return m_debug; } - bool debugMode() const; + /** @brief Distinguish "install" from "OEM" modes. + * + * Returns true in "install" mode, which is where actions happen + * in a chroot -- the target system, which exists separately from + * the source system. In "OEM" mode, returns false and most actions + * apply to the *current* (host) system. + */ + bool doChroot() const { return m_doChroot; } - bool doChroot() const; + /** @brief Global setting of prompt-before-install. + * + * Returns true when the configuration is such that the user + * should be prompted one-last-time before any action is taken + * that really affects the machine. + */ + bool showPromptBeforeExecution() const { return m_promptInstall; } + /** @brief Distinguish between "install" and "setup" modes. * * This influences user-visible strings, for instance using the @@ -64,9 +87,9 @@ public: bool isSetupMode() const { return m_isSetupMode; } /** @brief Global setting of disable-cancel: can't cancel ever. */ - bool disableCancel() const; + bool disableCancel() const { return m_disableCancel; } /** @brief Temporary setting of disable-cancel: can't cancel during exec. */ - bool disableCancelDuringExec() const; + bool disableCancelDuringExec() const { return m_disableCancelDuringExec; } private: static Settings* s_instance; diff --git a/src/libcalamares/network/Tests.cpp b/src/libcalamares/network/Tests.cpp index 559a955fe..3a15b3c59 100644 --- a/src/libcalamares/network/Tests.cpp +++ b/src/libcalamares/network/Tests.cpp @@ -19,6 +19,7 @@ #include "Tests.h" #include "Manager.h" +#include "utils/Logger.h" #include @@ -43,6 +44,9 @@ NetworkTests::testInstance() void NetworkTests::testPing() { - auto& nam = CalamaresUtils::Network::Manager::instance(); - QVERIFY( nam.synchronousPing( QUrl( "https://www.kde.org" ) ) ); + using namespace CalamaresUtils::Network; + Logger::setupLogLevel( Logger::LOGVERBOSE ); + auto& nam = Manager::instance(); + auto r = nam.synchronousPing( QUrl( "https://www.kde.org" ), RequestOptions( RequestOptions::FollowRedirect ) ); + QVERIFY( r ); } diff --git a/src/libcalamares/utils/CalamaresUtilsSystem.cpp b/src/libcalamares/utils/CalamaresUtilsSystem.cpp index ea8f507bd..61e05976a 100644 --- a/src/libcalamares/utils/CalamaresUtilsSystem.cpp +++ b/src/libcalamares/utils/CalamaresUtilsSystem.cpp @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014, Teo Mrnjavac - * Copyright 2017-2018, Adriaan de Groot + * Copyright 2017-2018, 2020, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -263,11 +263,16 @@ System::runCommand( System::RunLocation location, return ProcessResult( r, output ); } +/// @brief Cheap check if a path is absolute. +static inline bool +isAbsolutePath( const QString& path ) +{ + return path.startsWith( '/' ); +} + QString System::targetPath( const QString& path ) const { - QString completePath; - if ( doChroot() ) { Calamares::GlobalStorage* gs @@ -275,18 +280,17 @@ System::targetPath( const QString& path ) const if ( !gs || !gs->contains( "rootMountPoint" ) ) { - cWarning() << "No rootMountPoint in global storage, cannot create target file" << path; + cWarning() << "No rootMountPoint in global storage, cannot name target file" << path; return QString(); } - completePath = gs->value( "rootMountPoint" ).toString() + '/' + path; + QString root = gs->value( "rootMountPoint" ).toString(); + return isAbsolutePath( path ) ? ( root + path ) : ( root + '/' + path ); } else { - completePath = QStringLiteral( "/" ) + path; + return isAbsolutePath( path ) ? path : ( QStringLiteral( "/" ) + path ); } - - return completePath; } QString @@ -327,6 +331,59 @@ System::createTargetFile( const QString& path, const QByteArray& contents ) cons return QFileInfo( f ).canonicalFilePath(); } +void +System::removeTargetFile( const QString& path ) const +{ + if ( !isAbsolutePath( path ) ) + { + cWarning() << "Will not remove non-absolute path" << path; + return; + } + QString target = targetPath( path ); + if ( !target.isEmpty() ) + { + QFile::remove( target ); + } + // If it was empty, a warning was already printed +} + +bool +System::createTargetDirs( const QString& path ) const +{ + if ( !isAbsolutePath( path ) ) + { + cWarning() << "Will not create basedirs for non-absolute path" << path; + return false; + } + + QString target = targetPath( path ); + if ( target.isEmpty() ) + { + // If it was empty, a warning was already printed + return false; + } + + QString root = Calamares::JobQueue::instance()->globalStorage()->value( "rootMountPoint" ).toString(); + if ( root.isEmpty() ) + { + return false; + } + + QDir d( root ); + if ( !d.exists() ) + { + cWarning() << "Root mountpoint" << root << "does not exist."; + return false; + } + return d.mkpath( target ); // This re-does everything starting from the **host** / +} + +bool +System::createTargetParentDirs( const QString& filePath ) const +{ + return createTargetDirs( QFileInfo( filePath ).dir().path() ); +} + QPair< quint64, float > System::getTotalMemoryB() const diff --git a/src/libcalamares/utils/CalamaresUtilsSystem.h b/src/libcalamares/utils/CalamaresUtilsSystem.h index 8265f0fdb..ca8e0d797 100644 --- a/src/libcalamares/utils/CalamaresUtilsSystem.h +++ b/src/libcalamares/utils/CalamaresUtilsSystem.h @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014, Teo Mrnjavac - * Copyright 2017-2018, Adriaan de Groot + * Copyright 2017-2018, 2020, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -246,6 +246,33 @@ public: */ DLLEXPORT QString createTargetFile( const QString& path, const QByteArray& contents ) const; + /** @brief Remove a file from the target system. + * + * @param path Path to the file; this is interpreted from the root + * of the target system (@see targetPath()). + * + * Does no error checking to see if the target file was really removed. + */ + DLLEXPORT void removeTargetFile( const QString& path ) const; + + /** @brief Ensure that the directory @p path exists + * + * @param path a full pathname to a desired directory. + * + * All the directory components including the last path component are + * created, as needed. Returns true on success. + * + * @see QDir::mkpath + */ + DLLEXPORT bool createTargetDirs( const QString& path ) const; + + /** @brief Convenience to create parent directories of a file path. + * + * Creates all the parent directories until the last + * component of @p filePath . @see createTargetDirs() + */ + DLLEXPORT bool createTargetParentDirs( const QString& filePath ) const; + /** * @brief getTotalMemoryB returns the total main memory, in bytes. * diff --git a/src/libcalamares/utils/Retranslator.cpp b/src/libcalamares/utils/Retranslator.cpp index 85dfb62b6..767d0581e 100644 --- a/src/libcalamares/utils/Retranslator.cpp +++ b/src/libcalamares/utils/Retranslator.cpp @@ -27,6 +27,8 @@ #include #include +static bool s_allowLocalTranslations = false; + /** @brief Helper class for loading translations * * This is used by the loadSingletonTranslator() function to hand off @@ -131,7 +133,7 @@ static bool tryLoad( QTranslator* translator, const QString& prefix, const QString& localeName ) { // In debug-mode, try loading from the current directory - if ( Calamares::Settings::instance() && Calamares::Settings::instance()->debugMode() && translator->load( prefix + localeName ) ) + if ( s_allowLocalTranslations && translator->load( prefix + localeName ) ) { cDebug() << Logger::SubEntry << "Loaded local translation" << prefix << localeName; return true; @@ -139,14 +141,15 @@ tryLoad( QTranslator* translator, const QString& prefix, const QString& localeNa // Or load from appDataDir -- often /usr/share/calamares -- subdirectory land/ QDir localeData( CalamaresUtils::appDataDir() ); - if ( localeData.exists() && translator->load( localeData.absolutePath() + QStringLiteral("/lang/") + prefix + localeName) ) + if ( localeData.exists() + && translator->load( localeData.absolutePath() + QStringLiteral( "/lang/" ) + prefix + localeName ) ) { cDebug() << Logger::SubEntry << "Loaded appdata translation" << prefix << localeName; return true; } // Or from QRC (most common) - if ( translator->load( QStringLiteral( ":/lang/") + prefix + localeName ) ) + if ( translator->load( QStringLiteral( ":/lang/" ) + prefix + localeName ) ) { cDebug() << Logger::SubEntry << "Loaded QRC translation" << prefix << localeName; return true; @@ -260,5 +263,11 @@ Retranslator::eventFilter( QObject* obj, QEvent* e ) return QObject::eventFilter( obj, e ); } +void +setAllowLocalTranslation( bool allow ) +{ + s_allowLocalTranslations = allow; +} + } // namespace CalamaresUtils diff --git a/src/libcalamares/utils/Retranslator.h b/src/libcalamares/utils/Retranslator.h index 58c60b761..af322e5b5 100644 --- a/src/libcalamares/utils/Retranslator.h +++ b/src/libcalamares/utils/Retranslator.h @@ -42,6 +42,15 @@ DLLEXPORT void installTranslator( const QLocale& locale, const QString& branding DLLEXPORT QString translatorLocaleName(); +/** @brief Set @p allow to true to load translations from current dir. + * + * If false, (or never called) the translations are loaded only from + * system locations (the AppData dir) and from QRC (compiled in). + * Enable local translations to test translations stored in the + * current directory. + */ +DLLEXPORT void setAllowLocalTranslation( bool allow ); + class Retranslator : public QObject { Q_OBJECT diff --git a/src/libcalamares/utils/TestPaths.cpp b/src/libcalamares/utils/TestPaths.cpp new file mode 100644 index 000000000..2f9f4e657 --- /dev/null +++ b/src/libcalamares/utils/TestPaths.cpp @@ -0,0 +1,165 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "CalamaresUtilsSystem.h" +#include "Entropy.h" +#include "Logger.h" +#include "UMask.h" +#include "Yaml.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" + +#include +// #include + +#include + +// #include +// #include +// #include + +class TestPaths : public QObject +{ + Q_OBJECT +public: + TestPaths() {}; + virtual ~TestPaths() {}; + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanupTestCase(); + + void testTargetPath(); + void testCreateTarget(); + void testCreateTargetBasedirs(); + +private: + CalamaresUtils::System* m_system = nullptr; // Points to singleton instance, not owned + Calamares::GlobalStorage* m_gs = nullptr; +}; + +static const char testFile[] = "/calamares-testcreate"; +static const char absFile[] = "/tmp/calamares-testcreate"; // With rootMountPoint prepended + +void +TestPaths::initTestCase() +{ + Logger::setupLogLevel( Logger::LOGDEBUG ); + + // Ensure we have a system object, expect it to be a "bogus" one + CalamaresUtils::System* system = CalamaresUtils::System::instance(); + QVERIFY( system ); + QVERIFY( system->doChroot() ); + + // Ensure we have a system-wide GlobalStorage with /tmp as root + if ( !Calamares::JobQueue::instance() ) + { + cDebug() << "Creating new JobQueue"; + (void)new Calamares::JobQueue(); + } + Calamares::GlobalStorage* gs + = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr; + QVERIFY( gs ); + + m_system = system; + m_gs = gs; +} + +void +TestPaths::cleanupTestCase() +{ + QFile::remove( absFile ); +} + +void +TestPaths::init() +{ + cDebug() << "Setting rootMountPoint"; + m_gs->insert( "rootMountPoint", "/tmp" ); +} + + +void +TestPaths::testTargetPath() +{ + // Paths mapped normally + QCOMPARE( m_system->targetPath( "/etc/calamares" ), QStringLiteral( "/tmp/etc/calamares" ) ); + QCOMPARE( m_system->targetPath( "//etc//calamares" ), + QStringLiteral( "/tmp//etc//calamares" ) ); // extra / are not cleaned up + QCOMPARE( m_system->targetPath( "etc/calamares" ), QStringLiteral( "/tmp/etc/calamares" ) ); // relative to root + + // Weird Paths + QCOMPARE( m_system->targetPath( QString() ), QStringLiteral( "/tmp/" ) ); + + // Now break GS + m_gs->remove( "rootMountPoint" ); + QCOMPARE( m_system->targetPath( QString() ), QString() ); // Without root, no path +} + + +void +TestPaths::testCreateTarget() +{ + QCOMPARE( m_system->createTargetFile( testFile, "Hello" ), QString( absFile ) ); // Success + + QFileInfo fi( absFile ); + QVERIFY( fi.exists() ); + QCOMPARE( fi.size(), 5 ); + + m_system->removeTargetFile( testFile ); + QFileInfo fi2( absFile ); // fi caches information + QVERIFY( !fi2.exists() ); +} + +struct DirRemover +{ + DirRemover( const QString& base, const QString& dir ) + : m_base( base ) + , m_dir( dir ) + { + } + ~DirRemover() { QDir( m_base ).rmpath( m_dir ); } + + bool exists() const { return QDir( m_base ).exists( m_dir ); } + + QString m_base, m_dir; +}; + +void +TestPaths::testCreateTargetBasedirs() +{ + { + DirRemover dirrm( "/tmp", "var/lib/dbus" ); + QVERIFY( m_system->createTargetDirs( "/" ) ); + QVERIFY( m_system->createTargetDirs( "/var/lib/dbus" ) ); + QVERIFY( QFile( "/tmp/var/lib/dbus" ).exists() ); + QVERIFY( dirrm.exists() ); + } + QVERIFY( !QFile( "/tmp/var/lib/dbus" ).exists() ); + + // QFileInfo.dir() behaves even when things don't exist + QCOMPARE( QFileInfo( "/tmp/var/lib/dbus/bogus" ).dir().path(), QStringLiteral( "/tmp/var/lib/dbus" ) ); +} + +QTEST_GUILESS_MAIN( TestPaths ) + +#include "utils/moc-warnings.h" + +#include "TestPaths.moc" diff --git a/src/libcalamares/utils/Tests.cpp b/src/libcalamares/utils/Tests.cpp index 16faec9a1..e39d182ea 100644 --- a/src/libcalamares/utils/Tests.cpp +++ b/src/libcalamares/utils/Tests.cpp @@ -24,6 +24,9 @@ #include "UMask.h" #include "Yaml.h" +#include "GlobalStorage.h" +#include "JobQueue.h" + #include #include diff --git a/src/libcalamaresui/CMakeLists.txt b/src/libcalamaresui/CMakeLists.txt index a9d31c2c3..061a401bb 100644 --- a/src/libcalamaresui/CMakeLists.txt +++ b/src/libcalamaresui/CMakeLists.txt @@ -18,6 +18,8 @@ set( calamaresui_SOURCES utils/Paste.cpp viewpages/BlankViewStep.cpp + viewpages/ExecutionViewStep.cpp + viewpages/QmlViewStep.cpp viewpages/ViewStep.cpp widgets/ClickableLabel.cpp @@ -25,7 +27,6 @@ set( calamaresui_SOURCES widgets/WaitingWidget.cpp ${CMAKE_SOURCE_DIR}/3rdparty/waitingspinnerwidget.cpp - ExecutionViewStep.cpp Branding.cpp ViewManager.cpp ) diff --git a/src/libcalamaresui/ViewManager.cpp b/src/libcalamaresui/ViewManager.cpp index 68d918971..7492f1f8b 100644 --- a/src/libcalamaresui/ViewManager.cpp +++ b/src/libcalamaresui/ViewManager.cpp @@ -21,17 +21,16 @@ #include "ViewManager.h" -#include "viewpages/BlankViewStep.h" -#include "viewpages/ViewStep.h" - #include "Branding.h" -#include "ExecutionViewStep.h" #include "JobQueue.h" #include "Settings.h" #include "utils/Logger.h" #include "utils/Paste.h" #include "utils/Retranslator.h" +#include "viewpages/BlankViewStep.h" +#include "viewpages/ViewStep.h" +#include "viewpages/ExecutionViewStep.h" #include #include @@ -159,13 +158,6 @@ void ViewManager::insertViewStep( int before, ViewStep* step ) { m_steps.insert( before, step ); - QLayout* layout = step->widget()->layout(); - if ( layout ) - { - layout->setContentsMargins( 0, 0, 0, 0 ); - } - m_stack->insertWidget( before, step->widget() ); - connect( step, &ViewStep::enlarge, this, &ViewManager::enlarge ); connect( step, &ViewStep::nextStatusChanged, this, [this]( bool status ) { ViewStep* vs = qobject_cast< ViewStep* >( sender() ); @@ -178,6 +170,17 @@ ViewManager::insertViewStep( int before, ViewStep* step ) } } ); + if ( !step->widget() ) + { + cError() << "ViewStep" << step->moduleInstanceKey() << "has no widget."; + } + + QLayout* layout = step->widget()->layout(); + if ( layout ) + { + layout->setContentsMargins( 0, 0, 0, 0 ); + } + m_stack->insertWidget( before, step->widget() ); m_stack->setCurrentIndex( 0 ); step->widget()->setFocus(); } diff --git a/src/libcalamaresui/modulesystem/ModuleManager.cpp b/src/libcalamaresui/modulesystem/ModuleManager.cpp index c3e9569ef..6202493e2 100644 --- a/src/libcalamaresui/modulesystem/ModuleManager.cpp +++ b/src/libcalamaresui/modulesystem/ModuleManager.cpp @@ -19,7 +19,6 @@ #include "ModuleManager.h" -#include "ExecutionViewStep.h" #include "Module.h" #include "RequirementsChecker.h" #include "Settings.h" @@ -27,6 +26,7 @@ #include "utils/Logger.h" #include "utils/Yaml.h" +#include "viewpages/ExecutionViewStep.h" #include #include @@ -129,8 +129,9 @@ ModuleManager::doInit() } // At this point m_availableDescriptorsByModuleName is filled with // the modules that were found in the search paths. - cDebug() << "Found" << m_availableDescriptorsByModuleName.count() << "modules" - << m_moduleDirectoriesByModuleName.count() << "names"; + cDebug() << "Found" + << m_availableDescriptorsByModuleName.count() << "modules" + << m_moduleDirectoriesByModuleName.count() << "names"; emit initDone(); } diff --git a/src/libcalamaresui/viewpages/BlankViewStep.h b/src/libcalamaresui/viewpages/BlankViewStep.h index 17d323c85..ab44205ac 100644 --- a/src/libcalamaresui/viewpages/BlankViewStep.h +++ b/src/libcalamaresui/viewpages/BlankViewStep.h @@ -19,12 +19,7 @@ #ifndef BLANKVIEWSTEP_H #define BLANKVIEWSTEP_H -#include - -#include -#include - -class QWidget; +#include "viewpages/ViewStep.h" namespace Calamares { diff --git a/src/libcalamaresui/ExecutionViewStep.cpp b/src/libcalamaresui/viewpages/ExecutionViewStep.cpp similarity index 99% rename from src/libcalamaresui/ExecutionViewStep.cpp rename to src/libcalamaresui/viewpages/ExecutionViewStep.cpp index 501995c07..e0921910d 100644 --- a/src/libcalamaresui/ExecutionViewStep.cpp +++ b/src/libcalamaresui/viewpages/ExecutionViewStep.cpp @@ -18,16 +18,16 @@ * along with Calamares. If not, see . */ -#include +#include "ExecutionViewStep.h" #include "Branding.h" #include "Job.h" #include "JobQueue.h" #include "Settings.h" #include "ViewManager.h" + #include "modulesystem/Module.h" #include "modulesystem/ModuleManager.h" - #include "utils/CalamaresUtilsGui.h" #include "utils/Dirs.h" #include "utils/Logger.h" diff --git a/src/libcalamaresui/ExecutionViewStep.h b/src/libcalamaresui/viewpages/ExecutionViewStep.h similarity index 100% rename from src/libcalamaresui/ExecutionViewStep.h rename to src/libcalamaresui/viewpages/ExecutionViewStep.h diff --git a/src/libcalamaresui/viewpages/QmlViewStep.cpp b/src/libcalamaresui/viewpages/QmlViewStep.cpp new file mode 100644 index 000000000..b009d1626 --- /dev/null +++ b/src/libcalamaresui/viewpages/QmlViewStep.cpp @@ -0,0 +1,277 @@ +/* === This file is part of Calamares - === + * + * Copyright 2020, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "QmlViewStep.h" + +#include "Branding.h" + +#include "utils/Dirs.h" +#include "utils/Logger.h" +#include "utils/NamedEnum.h" +#include "utils/Variant.h" +#include "widgets/WaitingWidget.h" + +#include +#include +#include +#include +#include +#include + +static const NamedEnumTable< Calamares::QmlViewStep::QmlSearch >& +searchNames() +{ + using Search = Calamares::QmlViewStep::QmlSearch; + // *INDENT-OFF* + // clang-format off + static NamedEnumTable< Search > names { + { QStringLiteral( "both" ), Search::Both }, + { QStringLiteral( "qrc" ), Search::QrcOnly }, + { QStringLiteral( "branding" ), Search::BrandingOnly } + }; + // *INDENT-ON* + // clang-format on + + return names; +} + +namespace Calamares +{ + +QmlViewStep::QmlViewStep( const QString& name, QObject* parent ) + : ViewStep( parent ) + , m_name( name ) + , m_widget( new QWidget ) + , m_spinner( new WaitingWidget( tr( "Loading ..." ) ) ) + , m_qmlWidget( new QQuickWidget ) +{ + QVBoxLayout* layout = new QVBoxLayout( m_widget ); + layout->addWidget( m_spinner ); + + m_qmlWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + m_qmlWidget->setResizeMode( QQuickWidget::SizeRootObjectToView ); + m_qmlWidget->engine()->addImportPath( CalamaresUtils::qmlModulesDir().absolutePath() ); + + // QML Loading starts when the configuration for the module is set. +} + +QmlViewStep::~QmlViewStep() {} + +QString +QmlViewStep::prettyName() const +{ + // TODO: query the QML itself + return tr( "QML Step %1." ).arg( m_name ); +} + + +} // namespace Calamares + +bool +Calamares::QmlViewStep::isAtBeginning() const +{ + return true; +} + +bool +Calamares::QmlViewStep::isAtEnd() const +{ + return true; +} +bool +Calamares::QmlViewStep::isBackEnabled() const +{ + return true; +} + +bool +Calamares::QmlViewStep::isNextEnabled() const +{ + return true; +} + +Calamares::JobList +Calamares::QmlViewStep::jobs() const +{ + return JobList(); +} + +void +Calamares::QmlViewStep::onActivate() +{ + // TODO: call into QML +} + +void +Calamares::QmlViewStep::onLeave() +{ + // TODO: call into QML +} + +QWidget* +Calamares::QmlViewStep::widget() +{ + return m_widget; +} + +void +Calamares::QmlViewStep::loadComplete() +{ + cDebug() << "QML component" << m_qmlFileName << m_qmlComponent->status(); + if ( m_qmlComponent->status() == QQmlComponent::Error ) + { + showFailedQml(); + } + if ( m_qmlComponent->isReady() && !m_qmlObject ) + { + cDebug() << "QML component complete" << m_qmlFileName; + // Don't do this again + disconnect( m_qmlComponent, &QQmlComponent::statusChanged, this, &QmlViewStep::loadComplete ); + + QObject* o = m_qmlComponent->create(); + m_qmlObject = qobject_cast< QQuickItem* >( o ); + if ( !m_qmlObject ) + { + cError() << Logger::SubEntry << "Could not create QML from" << m_qmlFileName; + delete o; + } + else + { + // setContent() is public API, but not documented publicly. + // It is marked \internal in the Qt sources, but does exactly + // what is needed: sets up visual parent by replacing the root + // item, and handling resizes. + m_qmlWidget->setContent( QUrl( m_qmlFileName ), m_qmlComponent, m_qmlObject ); + showQml(); + } + } +} + +void +Calamares::QmlViewStep::showQml() +{ + if ( !m_qmlWidget || !m_qmlObject ) + { + cDebug() << "showQml() called but no QML object"; + return; + } + if ( m_spinner ) + { + m_widget->layout()->removeWidget( m_spinner ); + m_widget->layout()->addWidget( m_qmlWidget ); + delete m_spinner; + m_spinner = nullptr; + } + else + { + cDebug() << "showQml() called twice"; + } +} + + +/** @brief Find a suitable QML file, given the search method and name hints + * + * Returns QString() if nothing is found (which would mean the module + * is badly configured). + */ +QString +searchQmlFile( Calamares::QmlViewStep::QmlSearch method, const QString& configuredName, const QString& moduleName ) +{ + using QmlSearch = Calamares::QmlViewStep::QmlSearch; + + cDebug() << "Looking for QML for" << moduleName; + QStringList candidates; + if ( configuredName.startsWith( '/' ) ) + { + candidates << configuredName; + } + if ( ( method == QmlSearch::Both ) || ( method == QmlSearch::BrandingOnly ) ) + { + QString brandDir = Calamares::Branding::instance()->componentDirectory(); + candidates << ( configuredName.isEmpty() ? QString() + : QStringLiteral( "%1/%2.qml" ).arg( brandDir, configuredName ) ) + << ( moduleName.isEmpty() ? QString() : QStringLiteral( "%1/%2.qml" ).arg( brandDir, moduleName ) ); + } + if ( ( method == QmlSearch::Both ) || ( method == QmlSearch::QrcOnly ) ) + { + candidates << ( configuredName.isEmpty() ? QString() : QStringLiteral( ":/%1.qml" ).arg( configuredName ) ) + << ( moduleName.isEmpty() ? QString() : QStringLiteral( ":/%1.qml" ).arg( moduleName ) ); + } + for ( const QString& candidate : candidates ) + { + if ( candidate.isEmpty() ) + { + continue; + } + cDebug() << Logger::SubEntry << "Looking at QML file" << candidate; + if ( QFile::exists( candidate ) ) + { + if ( candidate.startsWith( ':' ) ) + { + // Inconsistency: QFile only sees the file with :, + // but QML needs an explicit scheme (of qrc:) + return QStringLiteral( "qrc" ) + candidate; + } + return candidate; + } + } + cDebug() << Logger::SubEntry << "None found."; + return QString(); +} + +void +Calamares::QmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) +{ + bool ok = false; + m_searchMethod = searchNames().find( CalamaresUtils::getString( configurationMap, "search" ), ok ); + if ( !ok ) + { + cDebug() << "Bad QML search mode."; + } + + QString qmlFile = CalamaresUtils::getString( configurationMap, "filename" ); + if ( qmlFile.isEmpty() ) + { + // TODO use the module instance + } + + if ( !m_qmlComponent ) + { + m_qmlFileName = searchQmlFile( m_searchMethod, qmlFile, m_name ); + + cDebug() << "QmlViewStep" << moduleInstanceKey() << "loading" << m_qmlFileName; + m_qmlComponent = new QQmlComponent( + m_qmlWidget->engine(), QUrl( m_qmlFileName ), QQmlComponent::CompilationMode::Asynchronous ); + connect( m_qmlComponent, &QQmlComponent::statusChanged, this, &QmlViewStep::loadComplete ); + if ( m_qmlComponent->status() == QQmlComponent::Error ) + { + showFailedQml(); + } + } + else + { + cWarning() << "QML configuration set after component has loaded."; + } +} + +void +Calamares::QmlViewStep::showFailedQml() +{ + cWarning() << "QmlViewStep" << moduleInstanceKey() << "loading failed."; + m_spinner->setText( prettyName() + ' ' + tr( "Loading failed." ) ); +} diff --git a/src/libcalamaresui/viewpages/QmlViewStep.h b/src/libcalamaresui/viewpages/QmlViewStep.h new file mode 100644 index 000000000..46ba29a53 --- /dev/null +++ b/src/libcalamaresui/viewpages/QmlViewStep.h @@ -0,0 +1,102 @@ +/* === This file is part of Calamares - === + * + * Copyright 2020, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef QMLVIEWSTEP_H +#define QMLVIEWSTEP_H + +#include "viewpages/ViewStep.h" + +class QQmlComponent; +class QQuickItem; +class QQuickWidget; +class WaitingWidget; + +namespace Calamares +{ + +/** @brief A viewstep that uses QML for the UI + * + * This is generally a **base** class for other view steps, but + * it can be used stand-alone for viewsteps that don't really have + * any functionality. + */ +class QmlViewStep : public Calamares::ViewStep +{ + Q_OBJECT + +public: + enum class QmlSearch + { + QrcOnly, + BrandingOnly, + Both + }; + + /** @brief Creates a QML view step + * + * The name should not have an extension or schema or anything; + * just the plain name, which will be searched as "/.qml" in + * QRC files, or ".qml" in suitable branding paths. + * The search behavior depends on a QmlSearch value. + */ + QmlViewStep( const QString& name, QObject* parent = nullptr ); + virtual ~QmlViewStep() override; + + virtual QString prettyName() const override; + + virtual QWidget* widget() override; + + virtual bool isNextEnabled() const override; + virtual bool isBackEnabled() const override; + + virtual bool isAtBeginning() const override; + virtual bool isAtEnd() const override; + + virtual void onActivate() override; + virtual void onLeave() override; + + /// @brief QML widgets don't produce jobs by default + virtual JobList jobs() const override; + + /// @brief Configure search paths; subclasses should call this as well + virtual void setConfigurationMap( const QVariantMap& configurationMap ) override; + +private Q_SLOTS: + void loadComplete(); + +private: + /// @brief Swap out the spinner for the QQuickWidget + void showQml(); + /// @brief Show error message in spinner. + void showFailedQml(); + + /// @brief Controls where m_name is searched + QmlSearch m_searchMethod; + + QString m_name; + QString m_qmlFileName; + + QWidget* m_widget = nullptr; + WaitingWidget* m_spinner = nullptr; + QQuickWidget* m_qmlWidget = nullptr; + QQmlComponent* m_qmlComponent = nullptr; + QQuickItem* m_qmlObject = nullptr; +}; + +} // namespace Calamares +#endif diff --git a/src/modules/dummyqml/CMakeLists.txt b/src/modules/dummyqml/CMakeLists.txt new file mode 100644 index 000000000..9a7532e9e --- /dev/null +++ b/src/modules/dummyqml/CMakeLists.txt @@ -0,0 +1,11 @@ +calamares_add_plugin( dummyqml + TYPE viewmodule + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + DummyQmlViewStep.cpp + RESOURCES + dummyqml.qrc + LINK_PRIVATE_LIBRARIES + calamaresui + SHARED_LIB +) diff --git a/src/modules/dummyqml/DummyQmlViewStep.cpp b/src/modules/dummyqml/DummyQmlViewStep.cpp new file mode 100644 index 000000000..030779f7f --- /dev/null +++ b/src/modules/dummyqml/DummyQmlViewStep.cpp @@ -0,0 +1,52 @@ +/* === This file is part of Calamares - === + * + * Copyright 2020, Adriaan de Groot + * Copyright 2020, Anke Boersma + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "DummyQmlViewStep.h" + +#include + +DummyQmlViewStep::DummyQmlViewStep( QObject* parent ) + : Calamares::QmlViewStep( "dummyqml", parent ) +{ +} + +DummyQmlViewStep::~DummyQmlViewStep() {} + +QString +DummyQmlViewStep::prettyName() const +{ + return m_notesName ? m_notesName->get() : tr( "Notes" ); +} + +void +DummyQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) +{ + Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation + + bool qmlLabel_ok = false; + auto qmlLabel = CalamaresUtils::getSubMap( configurationMap, "qmlLabel", qmlLabel_ok ); + + if ( qmlLabel.contains( "notes" ) ) + { + m_notesName = new CalamaresUtils::Locale::TranslatedString( qmlLabel, "notes" ); + } + +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( DummyQmlViewStepFactory, registerPlugin< DummyQmlViewStep >(); ) diff --git a/src/modules/dummyqml/DummyQmlViewStep.h b/src/modules/dummyqml/DummyQmlViewStep.h new file mode 100644 index 000000000..cf49436b0 --- /dev/null +++ b/src/modules/dummyqml/DummyQmlViewStep.h @@ -0,0 +1,48 @@ +/* === This file is part of Calamares - === + * + * Copyright 2020, Adriaan de Groot + * Copyright 2020, Anke Boersma + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef DUMMYQMLVIEWSTEP_H +#define DUMMYQMLVIEWSTEP_H + +#include "PluginDllMacro.h" +#include "locale/TranslatableConfiguration.h" +#include "utils/CalamaresUtilsSystem.h" +#include "utils/Variant.h" +#include "utils/PluginFactory.h" +#include "viewpages/QmlViewStep.h" + +class PLUGINDLLEXPORT DummyQmlViewStep : public Calamares::QmlViewStep +{ + Q_OBJECT + +public: + DummyQmlViewStep( QObject* parent = nullptr ); + virtual ~DummyQmlViewStep() override; + + QString prettyName() const override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + +private: + CalamaresUtils::Locale::TranslatedString* m_notesName; // As it appears in the sidebar +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( DummyQmlViewStepFactory ) + +#endif diff --git a/src/modules/dummyqml/dummyqml.conf b/src/modules/dummyqml/dummyqml.conf new file mode 100644 index 000000000..9e7a83fdd --- /dev/null +++ b/src/modules/dummyqml/dummyqml.conf @@ -0,0 +1,29 @@ +# The dummy QML module just displays a QML page. It doesn't +# have much in the way of own configuration, only where +# the QML file is searched. +# +# QML modules can search for the QML inside the Qt resources +# (QRC) which are compiled into the module, or in the branding +# setup for Calamares, (or both of them, with branding taking +# precedence). This allows the module to ship a default UI and +# branding to optionally introduce a replacement file. +# +# Generally, leave the search method set to "both" because if +# you don't want to brand the UI, just don't ship a branding +# QML file for it. +# +# To support instanced QML modules, searches in the branding +# directory look for the full module@instanceid name as well. +--- +# Search mode. Valid values are "both", "qrc" and "branding" +search: both + +# Name of the QML file. If not set, uses the name of the instance +# of the module (e.g. if you list this module in `settings.conf` +# in the *instances* section, you get *id*, otherwise it would +# normally be "dummyqml"). +# filename: dummyqml + +qmlLabel: + notes: "Release Notes" + notes[nl]: "Opmerkingen" diff --git a/src/modules/dummyqml/dummyqml.qml b/src/modules/dummyqml/dummyqml.qml new file mode 100644 index 000000000..3cbfa65fc --- /dev/null +++ b/src/modules/dummyqml/dummyqml.qml @@ -0,0 +1,73 @@ +/* === This file is part of Calamares - === + * + * Copyright 2020, Anke Boersma + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Window 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.1 + +Item { + width: 740 + height: 420 + + Flickable { + id: flick + anchors.fill: parent + contentHeight: 800 + + ScrollBar.vertical: ScrollBar { + width: 10 + policy: ScrollBar.AlwaysOn + } + + TextArea { + id: intro + x: 1 + y: 0 + width: 720 + font.pointSize: 14 + textFormat: Text.RichText + antialiasing: true + activeFocusOnPress: false + wrapMode: Text.WordWrap + + text: qsTr("

Generic GNU/Linux 2017.8 LTS Soapy Sousaphone

+

This an example QML file, showing options in RichText with Flickable content.

+ +

QML with RichText can use HTML tags, Flickable content is useful for touchscreens.

+ +

This is bold text

+

This is italic text

+

This is underlined text

+

This is strikethrough

+ +

Code example: + ls -l /home

+ +

Lists:

+
    +
  • Intel CPU systems
  • +
  • AMD CPU systems
  • +
+ +

The vertical scrollbar is adjustable, current width set to 10.

") + + } + } +} diff --git a/src/modules/dummyqml/dummyqml.qrc b/src/modules/dummyqml/dummyqml.qrc new file mode 100644 index 000000000..85b1da5ca --- /dev/null +++ b/src/modules/dummyqml/dummyqml.qrc @@ -0,0 +1,5 @@ + + + dummyqml.qml + + diff --git a/src/modules/machineid/CMakeLists.txt b/src/modules/machineid/CMakeLists.txt index efb6454e8..a57d5163d 100644 --- a/src/modules/machineid/CMakeLists.txt +++ b/src/modules/machineid/CMakeLists.txt @@ -12,6 +12,7 @@ calamares_add_plugin( machineid if ( ECM_FOUND AND BUILD_TESTING ) ecm_add_test( Tests.cpp + MachineIdJob.cpp Workers.cpp TEST_NAME machineidtest diff --git a/src/modules/machineid/MachineIdJob.cpp b/src/modules/machineid/MachineIdJob.cpp index 393950ded..fc535e356 100644 --- a/src/modules/machineid/MachineIdJob.cpp +++ b/src/modules/machineid/MachineIdJob.cpp @@ -3,7 +3,7 @@ * Copyright 2014, Kevin Kofler * Copyright 2016, Philip Müller * Copyright 2017, Alf Gaida - * Copyright 2019, Adriaan de Groot + * Copyright 2019-2020, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -68,18 +68,20 @@ MachineIdJob::exec() QString target_dbus_machineid_file = QStringLiteral( "/var/lib/dbus/machine-id" ); QString target_entropy_file = QStringLiteral( "/var/lib/urandom/random-seed" ); + const CalamaresUtils::System* system = CalamaresUtils::System::instance(); + // Clear existing files if ( m_entropy ) { - MachineId::removeFile( root, target_entropy_file ); + system->removeTargetFile( target_entropy_file ); } if ( m_dbus ) { - MachineId::removeFile( root, target_dbus_machineid_file ); + system->removeTargetFile( target_dbus_machineid_file ); } if ( m_systemd ) { - MachineId::removeFile( root, target_systemd_machineid_file ); + system->removeTargetFile( target_systemd_machineid_file ); } //Create new files @@ -104,6 +106,10 @@ MachineIdJob::exec() } if ( m_dbus ) { + if ( !system->createTargetParentDirs( target_dbus_machineid_file ) ) + { + cWarning() << "Could not create DBus data-directory."; + } if ( m_dbus_symlink && QFile::exists( root + target_systemd_machineid_file ) ) { auto r = MachineId::createDBusLink( root, target_dbus_machineid_file, target_systemd_machineid_file ); diff --git a/src/modules/machineid/Tests.cpp b/src/modules/machineid/Tests.cpp index 273645e22..53abd0482 100644 --- a/src/modules/machineid/Tests.cpp +++ b/src/modules/machineid/Tests.cpp @@ -16,11 +16,14 @@ * along with Calamares. If not, see . */ +#include "MachineIdJob.h" #include "Workers.h" +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.h" - #include #include #include @@ -35,10 +38,11 @@ public: private Q_SLOTS: void initTestCase(); - void testRemoveFile(); void testCopyFile(); void testPoolSize(); + + void testJob(); }; void @@ -84,11 +88,6 @@ MachineIdTests::testCopyFile() } } -void -MachineIdTests::testRemoveFile() -{ -} - void MachineIdTests::testPoolSize() { @@ -101,6 +100,62 @@ MachineIdTests::testPoolSize() #endif } +void +MachineIdTests::testJob() +{ + Logger::setupLogLevel( Logger::LOGDEBUG ); + + // Ensure we have a system object, expect it to be a "bogus" one + CalamaresUtils::System* system = CalamaresUtils::System::instance(); + QVERIFY( system ); + QVERIFY( system->doChroot() ); + + // Ensure we have a system-wide GlobalStorage with /tmp as root + if ( !Calamares::JobQueue::instance() ) + { + cDebug() << "Creating new JobQueue"; + (void)new Calamares::JobQueue(); + } + Calamares::GlobalStorage* gs + = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr; + QVERIFY( gs ); + gs->insert( "rootMountPoint", "/tmp" ); + + // Prepare part of the target filesystem + QVERIFY( system->createTargetDirs("/etc") ); + QVERIFY( !(system->createTargetFile( "/etc/machine-id", "Hello" ).isEmpty() ) ); + + MachineIdJob job( nullptr ); + QVERIFY( !job.prettyName().isEmpty() ); + + QVariantMap config; + config.insert( "dbus", true ); + job.setConfigurationMap( config ); + + { + auto r = job.exec(); + QVERIFY( !r ); // It's supposed to fail, because no dbus-uuidgen executable exists + QVERIFY( QFile::exists( "/tmp/var/lib/dbus" ) ); // but the target dir exists + } + + config.insert( "dbus-symlink", true ); + job.setConfigurationMap( config ); + { + auto r = job.exec(); + QVERIFY( !r ); // It's supposed to fail, because no dbus-uuidgen executable exists + QVERIFY( QFile::exists( "/tmp/var/lib/dbus" ) ); // but the target dir exists + + // These all (would) fail, because the chroot isn't viable +#if 0 + QVERIFY( QFile::exists( "/tmp/var/lib/dbus/machine-id" ) ); + + QFileInfo fi( "/tmp/var/lib/dbus/machine-id" ); + QVERIFY( fi.exists() ); + QVERIFY( fi.isSymLink() ); + QCOMPARE( fi.size(), 5); +#endif + } +} QTEST_GUILESS_MAIN( MachineIdTests ) diff --git a/src/modules/machineid/Workers.cpp b/src/modules/machineid/Workers.cpp index fefaf24b9..39acdfbf2 100644 --- a/src/modules/machineid/Workers.cpp +++ b/src/modules/machineid/Workers.cpp @@ -36,17 +36,6 @@ isAbsolutePath( const QString& fileName ) return fileName.startsWith( '/' ); } -// might need to use a helper to remove the file -void -removeFile( const QString& rootMountPoint, const QString& fileName ) -{ - if ( isAbsolutePath( fileName ) ) - { - QFile::remove( rootMountPoint + fileName ); - } - // Otherwise, do nothing -} - Calamares::JobResult copyFile( const QString& rootMountPoint, const QString& fileName ) { @@ -192,7 +181,7 @@ Calamares::JobResult createDBusLink( const QString& rootMountPoint, const QString& fileName, const QString& systemdFileName ) { Q_UNUSED( rootMountPoint ) - return runCmd( QStringList { QStringLiteral( "ln" ), QStringLiteral( "-s" ), systemdFileName, fileName } ); + return runCmd( QStringList { QStringLiteral( "ln" ), QStringLiteral( "-sf" ), systemdFileName, fileName } ); } } // namespace MachineId diff --git a/src/modules/machineid/Workers.h b/src/modules/machineid/Workers.h index 5cf6270d9..31561af1b 100644 --- a/src/modules/machineid/Workers.h +++ b/src/modules/machineid/Workers.h @@ -30,9 +30,6 @@ namespace MachineId * for moving files around in the target system. */ -/// @brief Remove @p fileName from the target system at @p rootMountPoint -void removeFile( const QString& rootMountPoint, const QString& fileName ); - /// @brief Copy @p fileName from host into target system at @p rootMountPoint Calamares::JobResult copyFile( const QString& rootMountPoint, const QString& fileName ); diff --git a/src/modules/machineid/main.py b/src/modules/machineid/main.py deleted file mode 100644 index 9756055eb..000000000 --- a/src/modules/machineid/main.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# === This file is part of Calamares - === -# -# Copyright 2014, Kevin Kofler -# Copyright 2019, Philip Müller -# Copyright 2017, Alf Gaida -# Copyright 2019, Adriaan de Groot -# -# Calamares is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Calamares is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Calamares. If not, see . - -import libcalamares -import os -from libcalamares.utils import check_target_env_call, debug - -import gettext -_ = gettext.translation("calamares-python", - localedir=libcalamares.utils.gettext_path(), - languages=libcalamares.utils.gettext_languages(), - fallback=True).gettext - - -def pretty_name(): - return _("Generate machine-id.") - - -def run(): - """ - Generate machine-id using dbus and systemd. - - :return: - """ - root_mount_point = libcalamares.globalstorage.value("rootMountPoint") - - if root_mount_point is None: - libcalamares.utils.warning("rootMountPoint is empty, {!s}".format(root_mount_point)) - return (_("Configuration Error"), - _("No root mount point is given for
{!s}
to use." ).format("machineid")) - - enable_systemd = libcalamares.job.configuration["systemd"] - enable_dbus = libcalamares.job.configuration["dbus"] - enable_symlink = libcalamares.job.configuration["symlink"] - target_systemd_machineid_file = root_mount_point + "/etc/machine-id" - target_dbus_machineid_file = root_mount_point + "/var/lib/dbus/machine-id" - - if os.path.exists(target_dbus_machineid_file): - os.remove(target_dbus_machineid_file) - - if enable_systemd: - if os.path.exists(target_systemd_machineid_file): - os.remove(target_systemd_machineid_file) - check_target_env_call("systemd-machine-id-setup") - - if enable_dbus: - if enable_symlink and os.path.exists(target_systemd_machineid_file): - check_target_env_call(["ln", "-sf", "/etc/machine-id", - "/var/lib/dbus/machine-id"]) - else: - check_target_env_call(["dbus-uuidgen", "--ensure"]) - - return None diff --git a/src/modules/shellprocess/Tests.cpp b/src/modules/shellprocess/Tests.cpp index 943a70957..e991973db 100644 --- a/src/modules/shellprocess/Tests.cpp +++ b/src/modules/shellprocess/Tests.cpp @@ -176,7 +176,7 @@ script: if ( !Calamares::JobQueue::instance() ) (void)new Calamares::JobQueue( nullptr ); if ( !Calamares::Settings::instance() ) - (void)new Calamares::Settings( QString(), true ); + (void)Calamares::Settings::init( QString() ); Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); QVERIFY( gs != nullptr ); diff --git a/src/modules/summary/SummaryPage.cpp b/src/modules/summary/SummaryPage.cpp index 61a9c2ec2..7114f27ee 100644 --- a/src/modules/summary/SummaryPage.cpp +++ b/src/modules/summary/SummaryPage.cpp @@ -23,13 +23,13 @@ #include "SummaryViewStep.h" #include "Branding.h" -#include "ExecutionViewStep.h" #include "Settings.h" #include "ViewManager.h" #include "utils/CalamaresUtilsGui.h" #include "utils/Logger.h" #include "utils/Retranslator.h" +#include "viewpages/ExecutionViewStep.h" #include #include diff --git a/src/modules/welcome/CMakeLists.txt b/src/modules/welcome/CMakeLists.txt index e25b7f5d0..b25bb6720 100644 --- a/src/modules/welcome/CMakeLists.txt +++ b/src/modules/welcome/CMakeLists.txt @@ -40,3 +40,13 @@ calamares_add_plugin( welcome Qt5::Network SHARED_LIB ) + +add_executable( welcomeqmltest qmlmain.cpp Config.cpp ) +target_link_libraries( welcomeqmltest PRIVATE calamaresui Qt5::Core ) +set_target_properties( welcomeqmltest + PROPERTIES + ENABLE_EXPORTS TRUE + RUNTIME_OUTPUT_NAME welcomeqmltest +) +calamares_automoc( welcomeqmltest ) +calamares_autouic( welcomeqmltest ) diff --git a/src/modules/welcome/Config.cpp b/src/modules/welcome/Config.cpp new file mode 100644 index 000000000..b46b85bf3 --- /dev/null +++ b/src/modules/welcome/Config.cpp @@ -0,0 +1,28 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "Config.h" + +Config::Config() + : m_helpUrl( "https://www.kde.org/" ) +{ +} + +Config::~Config() +{ +} diff --git a/src/modules/welcome/Config.h b/src/modules/welcome/Config.h new file mode 100644 index 000000000..7b0cfd734 --- /dev/null +++ b/src/modules/welcome/Config.h @@ -0,0 +1,40 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef WELCOME_CONFIG_H +#define WELCOME_CONFIG_H + +#include +#include + +class Config : public QObject +{ + Q_OBJECT + Q_PROPERTY( QUrl helpUrl READ helpUrl WRITE setHelpUrl CONSTANT ) +public: + Config(); + virtual ~Config(); + + QUrl helpUrl() const { return m_helpUrl; } + void setHelpUrl( const QUrl& url ) { m_helpUrl = url; } + +private: + QUrl m_helpUrl; +}; + +#endif diff --git a/src/modules/welcome/qmlmain.cpp b/src/modules/welcome/qmlmain.cpp new file mode 100644 index 000000000..a9dd1875d --- /dev/null +++ b/src/modules/welcome/qmlmain.cpp @@ -0,0 +1,84 @@ +/* Example executable showing a QML page and using the + * models from libcalamares for displaying a welcome. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Branding.h" +#include "JobQueue.h" +#include "Settings.h" +#include "locale/LabelModel.h" +#include "utils/Logger.h" + +#include "Config.h" + +static Config* theConfig() +{ + static Config* cnf = new Config(); + return cnf; +} + +int main(int argc, char **argv) +{ + QApplication a( argc, argv ); + + KAboutData aboutData( "calamares", + "Calamares", + "0.1", + "Calamares QML Test Application", + KAboutLicense::GPL_V3, + QString(), + QString(), + "https://calamares.io", + "https://github.com/calamares/calamares/issues" ); + KAboutData::setApplicationData( aboutData ); + a.setApplicationDisplayName( QString() ); // To avoid putting an extra "Calamares/" into the log-file + + Logger::setupLogLevel( Logger::LOGVERBOSE ); + + std::unique_ptr< Calamares::Settings > settings_p( Calamares::Settings::init( QString() ) ); + std::unique_ptr< Calamares::JobQueue > jobqueue_p( new Calamares::JobQueue( nullptr ) ); + + Calamares::Branding defaultBrand( "src/branding/default/branding.desc" ); + cDebug() << "Branding @" << (void *)Calamares::Branding::instance(); + + QMainWindow mw; + QWidget background; + QVBoxLayout vl; + QLabel l( "Hello, world", &mw ); + QQuickWidget qqw( &mw ); + vl.addWidget( &qqw ); + vl.addWidget( &l ); + background.setLayout( &vl ); + mw.setCentralWidget( &background ); + mw.resize( QSize( 400, 400 ) ); + mw.show(); + + Config cnf; + if ( argc > 1 ) + { + cnf.setHelpUrl( QUrl( argv[1] ) ); + } + + // TODO: this should put the one config object in the context, rather than adding a factory function to share it everywhere + qmlRegisterSingletonType< Config >( "io.calamares.modules.welcome", 1, 0, "PotatoConfig", [](QQmlEngine*, QJSEngine*) -> QObject* { return theConfig(); }); + + qmlRegisterSingletonType< CalamaresUtils::Locale::LabelModel >( "io.calamares.locale", 1, 0, "LocaleModel", [](QQmlEngine*, QJSEngine*) -> QObject* { return CalamaresUtils::Locale::availableTranslations(); } ); + + qqw.setSource( QUrl::fromLocalFile("../src/modules/welcome/welcome.qml") ); + + return a.exec(); +} diff --git a/src/modules/welcome/welcome.qml b/src/modules/welcome/welcome.qml new file mode 100644 index 000000000..bdb7c4416 --- /dev/null +++ b/src/modules/welcome/welcome.qml @@ -0,0 +1,29 @@ +import QtQuick 2.0; +import QtQuick.Controls 2.3; +import io.calamares.modules.welcome 1.0; +import io.calamares.locale 1.0; + +Rectangle { + width: 200; + height: 200; + color: "pink"; + + Label { + id: label; + anchors.centerIn: parent; + text: "Welcome to Calamares"; + } + + Button { + id: thebutton; + anchors.top: label.bottom; + text: PotatoConfig.helpUrl; + + } + + ListView { + anchors.fill: parent; + model: LocaleModel; + delegate: Label { text: display } + } +} diff --git a/src/modules/welcomeq/CMakeLists.txt b/src/modules/welcomeq/CMakeLists.txt new file mode 100644 index 000000000..7ee0350af --- /dev/null +++ b/src/modules/welcomeq/CMakeLists.txt @@ -0,0 +1,44 @@ +# This is a re-write of the welcome module using QML view steps +# instead of widgets. + +set( _welcome ${CMAKE_CURRENT_SOURCE_DIR}/../welcome ) + +include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui ${_welcome} ) + +# DUPLICATED WITH WELCOME MODULE +find_package( Qt5 ${QT_VERSION} CONFIG REQUIRED DBus Network ) + +find_package( LIBPARTED ) +if ( LIBPARTED_FOUND ) + set( PARTMAN_SRC ${_welcome}/checker/partman_devices.c ) + set( CHECKER_LINK_LIBRARIES ${LIBPARTED_LIBRARY} ) +else() + set( PARTMAN_SRC ) + set( CHECKER_LINK_LIBRARIES ) + add_definitions( -DWITHOUT_LIBPARTED ) +endif() + +set( CHECKER_SOURCES + ${_welcome}/checker/CheckerContainer.cpp + ${_welcome}/checker/GeneralRequirements.cpp + ${_welcome}/checker/ResultWidget.cpp + ${_welcome}/checker/ResultsListWidget.cpp + ${PARTMAN_SRC} +) + +calamares_add_plugin( welcomeq + TYPE viewmodule + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + ${CHECKER_SOURCES} + WelcomeQmlViewStep.cpp + Config.cpp + RESOURCES + welcomeq.qrc + LINK_PRIVATE_LIBRARIES + calamaresui + ${CHECKER_LINK_LIBRARIES} + Qt5::DBus + Qt5::Network + SHARED_LIB +) diff --git a/src/modules/welcomeq/Config.cpp b/src/modules/welcomeq/Config.cpp new file mode 100644 index 000000000..6d085143c --- /dev/null +++ b/src/modules/welcomeq/Config.cpp @@ -0,0 +1,28 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "Config.h" + +Config::Config() + : m_helpUrl( "https://www.kde.org/" ) +{ +} + +Config::~Config() +{ +} diff --git a/src/modules/welcomeq/Config.h b/src/modules/welcomeq/Config.h new file mode 100644 index 000000000..2295d4ee2 --- /dev/null +++ b/src/modules/welcomeq/Config.h @@ -0,0 +1,40 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef WELCOME_CONFIG_H +#define WELCOME_CONFIG_H + +#include +#include + +class Config : public QObject +{ + Q_OBJECT + Q_PROPERTY( QUrl helpUrl READ helpUrl WRITE setHelpUrl CONSTANT ) +public: + Config(); + virtual ~Config(); + + QUrl helpUrl() const { return m_helpUrl; } + void setHelpUrl( const QUrl& url ) { m_helpUrl = url; } + +private: + QUrl m_helpUrl; +}; + +#endif diff --git a/src/modules/welcomeq/WelcomeQmlViewStep.cpp b/src/modules/welcomeq/WelcomeQmlViewStep.cpp new file mode 100644 index 000000000..2a5b0f661 --- /dev/null +++ b/src/modules/welcomeq/WelcomeQmlViewStep.cpp @@ -0,0 +1,243 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * Copyright 2018,2020 Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "WelcomeQmlViewStep.h" + +#include "checker/GeneralRequirements.h" + +#include "geoip/Handler.h" +#include "locale/LabelModel.h" +#include "locale/Lookup.h" +#include "utils/Logger.h" +#include "utils/Variant.h" + +#include "Branding.h" +#include "modulesystem/ModuleManager.h" + +#include +#include +#include + +CALAMARES_PLUGIN_FACTORY_DEFINITION( WelcomeQmlViewStepFactory, registerPlugin< WelcomeQmlViewStep >(); ) + +WelcomeQmlViewStep::WelcomeQmlViewStep( QObject* parent ) + : Calamares::ViewStep( parent ) + , m_requirementsChecker( new GeneralRequirements( this ) ) +{ + connect( Calamares::ModuleManager::instance(), + &Calamares::ModuleManager::requirementsComplete, + this, + &WelcomeQmlViewStep::nextStatusChanged ); +} + + +WelcomeQmlViewStep::~WelcomeQmlViewStep() +{ +} + + +QString +WelcomeQmlViewStep::prettyName() const +{ + return tr( "Welcome" ); +} + + +QWidget* +WelcomeQmlViewStep::widget() +{ + return nullptr; +} + + +bool +WelcomeQmlViewStep::isNextEnabled() const +{ + // TODO: should return true + return false; +} + + +bool +WelcomeQmlViewStep::isBackEnabled() const +{ + // TODO: should return true (it's weird that you are not allowed to have welcome *after* anything + return false; +} + + +bool +WelcomeQmlViewStep::isAtBeginning() const +{ + // TODO: adjust to "pages" in the QML + return true; +} + + +bool +WelcomeQmlViewStep::isAtEnd() const +{ + // TODO: adjust to "pages" in the QML + return true; +} + + +Calamares::JobList +WelcomeQmlViewStep::jobs() const +{ + return Calamares::JobList(); +} + + +/** @brief Look up a URL for a button + * + * Looks up @p key in @p map; if it is a *boolean* value, then + * assume an old-style configuration, and fetch the string from + * the branding settings @p e. If it is a string, not a boolean, + * use it as-is. If not found, or a weird type, returns empty. + * + * This allows switching the showKnownIssuesUrl and similar settings + * in welcome.conf from a boolean (deferring to branding) to an + * actual string for immediate use. Empty strings, as well as + * "false" as a setting, will hide the buttons as before. + */ +static QString +jobOrBrandingSetting( Calamares::Branding::StringEntry e, const QVariantMap& map, const QString& key ) +{ + if ( !map.contains( key ) ) + { + return QString(); + } + auto v = map.value( key ); + if ( v.type() == QVariant::Bool ) + { + return v.toBool() ? ( *e ) : QString(); + } + if ( v.type() == QVariant::String ) + { + return v.toString(); + } + + return QString(); +} + +void +WelcomeQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) +{ + using Calamares::Branding; + + m_config.setHelpUrl( jobOrBrandingSetting( Branding::SupportUrl, configurationMap, "showSupportUrl" ) ); + // TODO: expand Config class and set the remaining fields + + // TODO: figure out how the requirements (held by ModuleManager) should be accessible + // to QML as a odel. + if ( configurationMap.contains( "requirements" ) + && configurationMap.value( "requirements" ).type() == QVariant::Map ) + { + m_requirementsChecker->setConfigurationMap( configurationMap.value( "requirements" ).toMap() ); + } + else + cWarning() << "no valid requirements map found in welcome " + "module configuration."; + + bool ok = false; + QVariantMap geoip = CalamaresUtils::getSubMap( configurationMap, "geoip", ok ); + if ( ok ) + { + using FWString = QFutureWatcher< QString >; + + auto* handler = new CalamaresUtils::GeoIP::Handler( CalamaresUtils::getString( geoip, "style" ), + CalamaresUtils::getString( geoip, "url" ), + CalamaresUtils::getString( geoip, "selector" ) ); + if ( handler->type() != CalamaresUtils::GeoIP::Handler::Type::None ) + { + auto* future = new FWString(); + connect( future, &FWString::finished, [view = this, f = future, h = handler]() { + QString countryResult = f->future().result(); + cDebug() << "GeoIP result for welcome=" << countryResult; + view->setCountry( countryResult, h ); + f->deleteLater(); + delete h; + } ); + future->setFuture( handler->queryRaw() ); + } + else + { + // Would not produce useful country code anyway. + delete handler; + } + } + + QString language = CalamaresUtils::getString( configurationMap, "languageIcon" ); + if ( !language.isEmpty() ) + { + auto icon = Calamares::Branding::instance()->image( language, QSize( 48, 48 ) ); + if ( !icon.isNull() ) + { + // TODO: figure out where to set this: Config? + } + } +} + +Calamares::RequirementsList +WelcomeQmlViewStep::checkRequirements() +{ + return m_requirementsChecker->checkRequirements(); +} + +static inline void +logGeoIPHandler( CalamaresUtils::GeoIP::Handler* handler ) +{ + if ( handler ) + { + cDebug() << Logger::SubEntry << "Obtained from" << handler->url() << " (" + << static_cast< int >( handler->type() ) << handler->selector() << ')'; + } +} + +void +WelcomeQmlViewStep::setCountry( const QString& countryCode, CalamaresUtils::GeoIP::Handler* handler ) +{ + if ( countryCode.length() != 2 ) + { + cDebug() << "Unusable country code" << countryCode; + logGeoIPHandler( handler ); + return; + } + + auto c_l = CalamaresUtils::Locale::countryData( countryCode ); + if ( c_l.first == QLocale::Country::AnyCountry ) + { + cDebug() << "Unusable country code" << countryCode; + logGeoIPHandler( handler ); + return; + } + else + { + int r = CalamaresUtils::Locale::availableTranslations()->find( countryCode ); + if ( r < 0 ) + { + cDebug() << "Unusable country code" << countryCode << "(no suitable translation)"; + } + if ( ( r >= 0 ) ) + { + // TODO: update Config to point to selected language + } + } +} diff --git a/src/modules/welcomeq/WelcomeQmlViewStep.h b/src/modules/welcomeq/WelcomeQmlViewStep.h new file mode 100644 index 000000000..5486d8d31 --- /dev/null +++ b/src/modules/welcomeq/WelcomeQmlViewStep.h @@ -0,0 +1,95 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020 Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef WELCOME_QMLVIEWSTEP_H +#define WELCOME_QMLVIEWSTEP_H + +#include "Config.h" + +#include "modulesystem/Requirement.h" +#include "utils/PluginFactory.h" +#include "viewpages/ViewStep.h" + +#include + +#include +#include + +namespace CalamaresUtils +{ +namespace GeoIP +{ +class Handler; +} +} // namespace CalamaresUtils + +class GeneralRequirements; + +class QQmlComponent; +class QQuickItem; +class QQuickWidget; + +// TODO: Needs a generic Calamares::QmlViewStep as base class +// TODO: refactor and move what makes sense to base class +class PLUGINDLLEXPORT WelcomeQmlViewStep : public Calamares::ViewStep +{ + Q_OBJECT + +public: + explicit WelcomeQmlViewStep( QObject* parent = nullptr ); + virtual ~WelcomeQmlViewStep() override; + + QString prettyName() const override; + + QWidget* widget() override; + + bool isNextEnabled() const override; + bool isBackEnabled() const override; + + bool isAtBeginning() const override; + bool isAtEnd() const override; + + Calamares::JobList jobs() const override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + + /** @brief Sets the country that Calamares is running in. + * + * This (ideally) sets up language and locale settings that are right for + * the given 2-letter country code. Uses the handler's information (if + * given) for error reporting. + */ + void setCountry( const QString&, CalamaresUtils::GeoIP::Handler* handler ); + + Calamares::RequirementsList checkRequirements() override; + +private: + // TODO: a generic QML viewstep should return a config object from a method + Config m_config; + GeneralRequirements* m_requirementsChecker; + + // TODO: these need to be in the base class (also a base class of ExecutionViewStep) + QQuickWidget* m_qmlWidget; + QQmlComponent* m_qmlComponent; + QQuickItem* m_qmlItem; + +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( WelcomeQmlViewStepFactory ) + +#endif // WELCOME_QMLVIEWSTEP_H