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/CHANGES b/CHANGES index 1f1d37f61..cb807cdc4 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,9 @@ website will have to do for older versions. # 3.2.19 (unreleased) # This release contains contributions from (alphabetically by first name): - - No other contributors this time around. + - Anke Boersma + - Camilo Higuita + - Gabriel Craciunescu ## Core ## - *Assamese* translation has been completed. @@ -17,10 +19,18 @@ This release contains contributions from (alphabetically by first name): translations without requiring a recompile: helpful for translators and possibly for distributions with their own translation style. See the translators and deployers wiki for details. + - A new `ViewStep` base class, `QmlViewStep`, has been added that loads + a configurable QML file and plays it. This is used by the new *notesqml* + module -- which is in itself a minimal wrapper around the same that + adds only a translatable module name. ## Modules ## - The *machineid* and *users* modules now prefer high-quality random data from `/dev/urandom` rather than pseudo-random data. #1254 + - A new *notesqml* module supports loading QML. This can be used for + "fancy" release notes as a QML application, rather than a webview + or text widget. Note that this does not replace the slideshow-during- + installation module. # 3.2.18 (2020-01-28) # diff --git a/README.md b/README.md index e2e87fddf..7b12532e5 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,11 @@ --------- [![GitHub release](https://img.shields.io/github/release/calamares/calamares.svg)](https://github.com/calamares/calamares/releases) -[![Build Status](https://calamares.io/ci/buildStatus/icon?job=calamares-post_commit)](https://calamares.io/ci/job/calamares-post_commit/) [![Travis Build Status](https://travis-ci.org/calamares/calamares.svg?branch=master)](https://travis-ci.org/calamares/calamares) [![Coverity Scan Build Status](https://scan.coverity.com/projects/5389/badge.svg)](https://scan.coverity.com/projects/5389) [![GitHub license](https://img.shields.io/github/license/calamares/calamares.svg)](https://github.com/calamares/calamares/blob/master/LICENSE) -| [Report a Bug](https://github.com/calamares/calamares/issues/new) | [Contribute](https://github.com/calamares/calamares/blob/master/ci/HACKING.md) | [Translate](https://www.transifex.com/projects/p/calamares/) | Freenode (IRC): #calamares | [Wiki](https://github.com/calamares/calamares/wiki) | +| [Report a Bug](https://github.com/calamares/calamares/issues/new) | [Translate](https://www.transifex.com/projects/p/calamares/) | [Contribute](https://github.com/calamares/calamares/wiki/Develop-Guide) | Freenode (IRC): #calamares | [Wiki](https://github.com/calamares/calamares/wiki) | |:-----------------------------------------:|:----------------------:|:-----------------------:|:--------------------------:|:--------------------------:| ### Dependencies diff --git a/ci/HACKING.md b/ci/HACKING.md index 02eb8fd17..f1c8b750b 100644 --- a/ci/HACKING.md +++ b/ci/HACKING.md @@ -1,211 +1 @@ -Hacking on Calamares -==================== - -These are the guidelines for hacking on Calamares. Except for the licensing, -which **must** be GPLv3+, these are guidelines and -- like PEP8 -- the most -important thing is to know when you can ignore them. - - -Licensing ---------- -Calamares is released under the terms of the GNU GPL, version 3 or later. -Every source file must have a license header, with a list of copyright holders and years. - -Example: -``` -/* === This file is part of Calamares - === - * - * Copyright 2013-2014, Random Person - * Copyright 2010, Someone Else - * - * 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 . - */ -``` -Copyright holders must be physical or legal personalities. A statement such as -`Copyright 2014, The FooBarQuux project` has no legal value if "The FooBarQuux -project" is not the legal name of a person, company, incorporated -organization, etc. - -Please add your name to files you touch when making any contribution (even if -it's just a typo-fix which might not be copyrightable in all jurisdictions). - - -Formatting C++ --------------- -This formatting guide applies to C++ code only; for Python modules, we use -[pycodestyle](https://github.com/PyCQA/pycodestyle) to apply a check of -some PEP8 guidelines. - -* Spaces, not tabs. -* Indentation is 4 spaces. -* Lines should be limited to 90 characters. -* Spaces between brackets and argument functions, including for template arguments -* No space before brackets, except for keywords, for example `function( argument )` but - `if ( condition )`. -* For pointer and reference variable declarations, put a space before the variable name - and no space between the type and the `*` or `&`, e.g. `int* p`. -* `for`, `if`, `else`, `while` and similar statements put the braces on the next line, - if the following block is more than one statement. Always use braces. -* Function and class definitions have their braces on separate lines. -* A function implementation's return type is on its own line. -* `CamelCase.{cpp,h}` style file names. -* Lambdas are preferrably indented to a 4-space tab, even when passed as an - argument to functions. - -Example: -``` -bool -MyClass::myMethod( QStringList list, const QString& name ) -{ - if ( list.isEmpty() ) - return false; - - cDebug() << "Items in list .."; - foreach ( const QString& string, list ) - cDebug() << " .." << string; - - switch ( m_enumValue ) - { - case Something: - return true; - case SomethingElse: - doSomething(); - break; - } -} -``` - -You can use `clang-format` (version 7) to have Calamares sources formatted -the right way. There is a `.clang-format` file that specifies the details. -In general: -``` - $ clang-format-7 -i -style=file -``` -` - -**NOTE:** An .editorconfig file is included to assist with formatting. In -order to take advantage of this functionality you will need to acquire the -[EditorConfig](http://editorconfig.org/#download) plug-in for your editor. - - -Naming ------- -* Use CamelCase for everything. -* Local variables should start out with a lowercase letter. -* Class names are capitalized -* Prefix class member variables with `m_`, e.g. `m_queue`. -* Prefix static member variables with `s_`, e.g. `s_instance`. -* Functions are named in the Qt style, like Java's, without the 'get' prefix. - * A getter is `variable()`. - * If it's a getter for a boolean, prefix with 'is', so `isCondition()`. - * A setter is `setVariable( arg )`. - - -Includes --------- -Header includes should be listed in the following order: - -* own header, -* Calamares includes, -* includes for Qt-based libraries, -* Qt includes, -* other includes. - -They should also be sorted alphabetically for ease of locating them. - -Includes in a header file should be kept to the absolute minimum, as to keep -compile times short. This can be achieved by using forward declarations -instead of includes, like `class QListView;`. - -Example: -``` -#include "Settings.h" - -#include "CalamaresApplication.h" -#include "utils/CalamaresUtils.h" -#include "utils/Logger.h" -#include "YamlUtils.h" - -#include -#include - -#include -``` - -Use include guards, not `#pragma once`. - - -C++ tips --------- -All C++11 features are acceptable, and the use of new C++11 features is encouraged when -it makes the code easier to understand and more maintainable. - -The use of `nullptr` is preferred over the use of `0` or `NULL`. - -For Qt containers it is better to use Qt's own `foreach`. For all other containers, the -range-based `for` syntax introduced with C++11 is preferred ([see this blog post][1]). - -When re-implementing a virtual method, always add the `override` keyword. - -Try to keep your code const correct. Declare methods const if they don't mutate the -object, and use const variables. It improves safety, and also makes it easier to -understand the code. - -For the Qt signal-slot system, the new (Qt5) syntax is to be preferred because it allows -the compiler to check for the existence of signals and slots. As an added benefit, the -new syntax can also be used with `tr1::bind` and C++11 lambdas. For more information, see -the [Qt wiki][2]. - -Example: -``` -connect( m_next, &QPushButton::clicked, this, &ViewManager::next ); - -connect( m_moduleManager, &Calamares::ModuleManager::modulesLoaded, [this] -{ - m_mainwindow->show(); -}); -``` - -[1]: http://blog.qt.digia.com/blog/2011/05/26/cpp0x-in-qt/ -[2]: http://qt-project.org/wiki/New_Signal_Slot_Syntax - - -Debugging ---------- -Use `cDebug()` from `utils/Logger.h`. You can pass a debug-level to the -macro (6 is debugging, higher is less important). Use `cWarning()` for warning -messages (equivalent to level 2) and `cError()` for errors (level 1). Warnings -and errors will add relevant text automatically. See `libcalamares/utils/Logger.h` -for details. - -For log messages that are continued across multiple calls to `cDebug()`, -in particular listing things, conventional formatting is as follows: -* End the first debug message with ` ..` -* Start the next debug message by outputting `Logger::SubEntry` - -For single-outputs that need to be split across multiplt lines, -output `Logger::Continuation`. - - -Commit Messages ---------------- -Keep commit messages short(-ish) and try to describe what is being changed -*as well as why*. Use the commit keywords for GitHub, especially *FIXES:* -to auto-close issues when they are resolved. - -For functional changes to Calamares modules or libraries, try to put -*[modulename]* in front of the first line of the commit message. - -For non-functional changes to infrastructure, try to label the change -with the kind of change, e.g. *CMake* or *i18n* or *Documentation*. +This has moved [to the wiki](https://github.com/calamares/calamares/wiki/Develop-Code). diff --git a/ci/RELEASE.md b/ci/RELEASE.md index 3198ee95d..5293abf6b 100644 --- a/ci/RELEASE.md +++ b/ci/RELEASE.md @@ -1,17 +1,16 @@ -The Calamares release process -============================= +# Calamares Release Process -> As releases from *master* are now rolling when-they-are-ready releases, -> some of these steps no longer are followed. In particular, -RC releases -> are not done anymore (although the RC variable is set in `CMakeLists.txt` -> to avoid accidents) and most things are automated through the release -> script [RELEASE.sh](RELEASE.sh) +> Calamares releases are now rolling when-they-are-ready releases. +> Releases are made from *master* and tagged there. When, in future, +> LTS releases resume, these steps may be edited again. +> +> Most things are automated through the release script [RELEASE.sh](RELEASE.sh) -#### (0) A week in advance +## (0) A week in advance -* (Only releases from master) - Run [Coverity scan][coverity], fix what's relevant. The Coverity scan runs - automatically once a week on master. +* Run [Coverity scan][coverity], fix what's relevant. The Coverity scan runs + automatically once a week on master. The badge is displayed on the + project front page and in the wiki. * Build with clang -Weverything, fix what's relevant. ``` rm -rf build ; mkdir build ; cd build @@ -26,34 +25,69 @@ The Calamares release process an additional environment variable to be set for some tests, which will destroy an attached disk. This is not always desirable. There are some sample config-files that are empty and which fail the config-tests. -* (Only releases from master) - Notify [translators][transifex]. In the dashboard there is an *Announcements* - link that you can use to send a translation announcement. +* Notify [translators][transifex]. In the dashboard there is an *Announcements* + link that you can use to send a translation announcement. Note that regular + use of `txpush.sh` will notify translators as well of any changes. [coverity]: https://scan.coverity.com/projects/calamares-calamares?tab=overview [transifex]: https://www.transifex.com/calamares/calamares/dashboard/ -#### (1) Preparation -* Bump version in `CMakeLists.txt`, *CALAMARES_VERSION* variables, and set - RC to a non-zero value (e.g. doing -rc1, -rc2, ...). Push that. -* Check `README.md` and everything `ci/HACKING.md`, make sure it's all still - relevant. Run `ci/calamaresstyle` to check the C++ code style. - Run pycodestyle on recently-modified Python modules, fix what makes sense. -* Check defaults in `settings.conf` and other configuration files. -* (Only releases from master) - Pull latest translations from Transifex. We only push / pull translations +## (1) Preparation + +* Pull latest translations from Transifex. We only push / pull translations from master, so longer-lived branches (e.g. 3.1.x) don't get translation - updates. This is to keep the translation workflow simple. + updates. This is to keep the translation workflow simple. The script + automatically commits changes to the translations. ``` sh ci/txpull.sh ``` -* (Only releases from master) - Update the list of enabled translation languages in `CMakeLists.txt`. +* Update the list of enabled translation languages in `CMakeLists.txt`. Check the [translation site][transifex] for the list of languages with - fairly complete translations. + fairly complete translations, or use `ci/txstats.py` for an automated + suggestion. If there are changes, commit them. +* Push the changes. +* Drop the RC variable to 0 in `CMakeLists.txt`, *CALAMARES_VERSION_RC*. +* Check `README.md` and the + [Coding Guide](https://github.com/calamares/calamares/wiki/Develop-Code), + make sure it's all still + relevant. Run `ci/calamaresstyle` to check the C++ code style. + Run pycodestyle on recently-modified Python modules, fix what makes sense. +* Check defaults in `settings.conf` and other configuration files. +* Edit `CHANGES` and set the date of the release. +* Commit both. This is usually done with commit-message + *Changes: pre-release housekeeping*. -#### (2) Tarball + +## (2) Release Day + +* Run the helper script `ci/RELEASE.sh` or follow steps below. + The script checks: + - for uncommitted local changes, + - if translations are up-to-date and translators + have had enough time to chase new strings, + - that the build is successful (with gcc and clang, if available), + - tests pass, + - tarball can be created, + - tarball can be signed. + On success, it prints out a suitable signature- and SHA256 blurb + for use in the release announcement. + +### (2.1) Buld and Test + +* Build with gcc. If available, build again with Clang and double-check + any warnings Clang produces. +* Run the tests; `make test` in the build directory should have no + failures (or if there are, know why they are there). + +### (2.2) Tag + +* `git tag -s v1.1.0` Make sure the signing key is known in GitHub, so that the + tag is shown as a verified tag. Do not sign -rc tags. + You can use `make show-version` in the build directory to get the right + version number -- this will fail if you didn't follow step (1). + +### (2.3) Tarball * Create tarball: `git-archive-all -v calamares-1.1-rc1.tar.gz` or without the helper script, @@ -64,21 +98,25 @@ The Calamares release process Double check that the tarball matches the version number. * Test tarball (e.g. unpack somewhere else and run the tests from step 0). -#### (3) Tag -* Set RC to zero in `CMakeLists.txt` if this is the actual release. -* `git tag -s v1.1.0` Make sure the signing key is known in GitHub, so that the - tag is shown as a verified tag. Do not sign -rc tags. +## (3) Housekeeping + * Generate MD5 and SHA256 checksums. * Upload tarball. * Announce on mailing list, notify packagers. * Write release article. - -#### (4) Release day - * Publish tarball. * Update download page. * Publish release article on `calamares.io`. * Publicize on social networks. * Close associated milestone on GitHub if this is the actual release. * Publish blog post. + +## (4) Post-Release + +* Bump the version number in `CMakeLists.txt` in the `project()` command. +* Set *CALAMARES_VERSION_RC* back to 1. +* Add a placeholder entry for the next release in `CHANGES` with date + text *not released yet*. +* Commit and push that, usually with the message + *Changes: post-release housekeeping*. 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/GlobalStorage.cpp b/src/libcalamares/GlobalStorage.cpp index 5094ad2fd..428b01103 100644 --- a/src/libcalamares/GlobalStorage.cpp +++ b/src/libcalamares/GlobalStorage.cpp @@ -27,17 +27,6 @@ #include #include -#ifdef WITH_PYTHON -#include "PythonHelper.h" - - -#undef slots -#include -#include - -namespace bp = boost::python; -#endif - using CalamaresUtils::operator""_MiB; namespace Calamares @@ -167,75 +156,3 @@ GlobalStorage::loadYaml( const QString& filename ) } // namespace Calamares - -#ifdef WITH_PYTHON - -namespace CalamaresPython -{ - -Calamares::GlobalStorage* GlobalStoragePythonWrapper::s_gs_instance = nullptr; - -// The special handling for nullptr is only for the testing -// script for the python bindings, which passes in None; -// normal use will have a GlobalStorage from JobQueue::instance() -// passed in. Testing use will leak the allocated GlobalStorage -// object, but that's OK for testing. -GlobalStoragePythonWrapper::GlobalStoragePythonWrapper( Calamares::GlobalStorage* gs ) - : m_gs( gs ? gs : s_gs_instance ) -{ - if ( !m_gs ) - { - s_gs_instance = new Calamares::GlobalStorage; - m_gs = s_gs_instance; - } -} - -bool -GlobalStoragePythonWrapper::contains( const std::string& key ) const -{ - return m_gs->contains( QString::fromStdString( key ) ); -} - - -int -GlobalStoragePythonWrapper::count() const -{ - return m_gs->count(); -} - - -void -GlobalStoragePythonWrapper::insert( const std::string& key, const bp::object& value ) -{ - m_gs->insert( QString::fromStdString( key ), CalamaresPython::variantFromPyObject( value ) ); -} - -bp::list -GlobalStoragePythonWrapper::keys() const -{ - bp::list pyList; - const auto keys = m_gs->keys(); - for ( const QString& key : keys ) - { - pyList.append( key.toStdString() ); - } - return pyList; -} - - -int -GlobalStoragePythonWrapper::remove( const std::string& key ) -{ - return m_gs->remove( QString::fromStdString( key ) ); -} - - -bp::object -GlobalStoragePythonWrapper::value( const std::string& key ) const -{ - return CalamaresPython::variantToPyObject( m_gs->value( QString::fromStdString( key ) ) ); -} - -} // namespace CalamaresPython - -#endif // WITH_PYTHON diff --git a/src/libcalamares/GlobalStorage.h b/src/libcalamares/GlobalStorage.h index b070e23f6..bef9ec1cc 100644 --- a/src/libcalamares/GlobalStorage.h +++ b/src/libcalamares/GlobalStorage.h @@ -26,20 +26,6 @@ #include #include -#ifdef WITH_PYTHON -namespace boost -{ -namespace python -{ -namespace api -{ -class object; -} -class list; -} // namespace python -} // namespace boost -#endif - namespace Calamares { @@ -106,33 +92,4 @@ private: } // namespace Calamares -#ifdef WITH_PYTHON -namespace CalamaresPython -{ - -class GlobalStoragePythonWrapper -{ -public: - explicit GlobalStoragePythonWrapper( Calamares::GlobalStorage* gs ); - - bool contains( const std::string& key ) const; - int count() const; - void insert( const std::string& key, const boost::python::api::object& value ); - boost::python::list keys() const; - int remove( const std::string& key ); - boost::python::api::object value( const std::string& key ) const; - - // This is a helper for scripts that do not go through - // the JobQueue (i.e. the module testpython script), - // which allocate their own (singleton) GlobalStorage. - static Calamares::GlobalStorage* globalStorageInstance() { return s_gs_instance; } - -private: - Calamares::GlobalStorage* m_gs; - static Calamares::GlobalStorage* s_gs_instance; // See globalStorageInstance() -}; - -} // namespace CalamaresPython -#endif - #endif // CALAMARES_GLOBALSTORAGE_H diff --git a/src/libcalamares/JobQueue.cpp b/src/libcalamares/JobQueue.cpp index 6772671b7..2690769db 100644 --- a/src/libcalamares/JobQueue.cpp +++ b/src/libcalamares/JobQueue.cpp @@ -19,15 +19,11 @@ #include "JobQueue.h" +#include "CalamaresConfig.h" #include "GlobalStorage.h" #include "Job.h" #include "utils/Logger.h" -#include "CalamaresConfig.h" -#ifdef WITH_PYTHON -#include "PythonHelper.h" -#endif - #include namespace Calamares diff --git a/src/libcalamares/PythonHelper.cpp b/src/libcalamares/PythonHelper.cpp index 26a57ec14..d9db8581e 100644 --- a/src/libcalamares/PythonHelper.cpp +++ b/src/libcalamares/PythonHelper.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 @@ -19,20 +19,13 @@ #include "PythonHelper.h" +#include "GlobalStorage.h" #include "utils/Dirs.h" #include "utils/Logger.h" #include #include -#undef slots -#include "utils/boost-warnings.h" -#include - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - namespace bp = boost::python; namespace CalamaresPython @@ -398,5 +391,67 @@ Helper::handleLastError() return QString( "
%1
" ).arg( msgList.join( "
" ) ); } +Calamares::GlobalStorage* GlobalStoragePythonWrapper::s_gs_instance = nullptr; + +// The special handling for nullptr is only for the testing +// script for the python bindings, which passes in None; +// normal use will have a GlobalStorage from JobQueue::instance() +// passed in. Testing use will leak the allocated GlobalStorage +// object, but that's OK for testing. +GlobalStoragePythonWrapper::GlobalStoragePythonWrapper( Calamares::GlobalStorage* gs ) + : m_gs( gs ? gs : s_gs_instance ) +{ + if ( !m_gs ) + { + s_gs_instance = new Calamares::GlobalStorage; + m_gs = s_gs_instance; + } +} + +bool +GlobalStoragePythonWrapper::contains( const std::string& key ) const +{ + return m_gs->contains( QString::fromStdString( key ) ); +} + + +int +GlobalStoragePythonWrapper::count() const +{ + return m_gs->count(); +} + + +void +GlobalStoragePythonWrapper::insert( const std::string& key, const bp::object& value ) +{ + m_gs->insert( QString::fromStdString( key ), CalamaresPython::variantFromPyObject( value ) ); +} + +bp::list +GlobalStoragePythonWrapper::keys() const +{ + bp::list pyList; + const auto keys = m_gs->keys(); + for ( const QString& key : keys ) + { + pyList.append( key.toStdString() ); + } + return pyList; +} + + +int +GlobalStoragePythonWrapper::remove( const std::string& key ) +{ + return m_gs->remove( QString::fromStdString( key ) ); +} + + +bp::object +GlobalStoragePythonWrapper::value( const std::string& key ) const +{ + return CalamaresPython::variantToPyObject( m_gs->value( QString::fromStdString( key ) ) ); +} } // namespace CalamaresPython diff --git a/src/libcalamares/PythonHelper.h b/src/libcalamares/PythonHelper.h index 9035ba6d4..418c75e5f 100644 --- a/src/libcalamares/PythonHelper.h +++ b/src/libcalamares/PythonHelper.h @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014, Teo Mrnjavac - * Copyright 2018, Adriaan de Groot + * 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 @@ -21,19 +21,14 @@ #define CALAMARES_PYTHONJOBHELPER_H #include "PythonJob.h" +#include "utils/BoostPython.h" #include -#undef slots -#include "utils/boost-warnings.h" - -#include -#include -#include - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif +namespace Calamares +{ +class GlobalStorage; +} namespace CalamaresPython { @@ -72,6 +67,28 @@ private: QStringList m_pythonPaths; }; +class GlobalStoragePythonWrapper +{ +public: + explicit GlobalStoragePythonWrapper( Calamares::GlobalStorage* gs ); + + bool contains( const std::string& key ) const; + int count() const; + void insert( const std::string& key, const boost::python::api::object& value ); + boost::python::list keys() const; + int remove( const std::string& key ); + boost::python::api::object value( const std::string& key ) const; + + // This is a helper for scripts that do not go through + // the JobQueue (i.e. the module testpython script), + // which allocate their own (singleton) GlobalStorage. + static Calamares::GlobalStorage* globalStorageInstance() { return s_gs_instance; } + +private: + Calamares::GlobalStorage* m_gs; + static Calamares::GlobalStorage* s_gs_instance; // See globalStorageInstance() +}; + } // namespace CalamaresPython #endif // CALAMARES_PYTHONJOBHELPER_H diff --git a/src/libcalamares/PythonJob.cpp b/src/libcalamares/PythonJob.cpp index c18371881..d94a20981 100644 --- a/src/libcalamares/PythonJob.cpp +++ b/src/libcalamares/PythonJob.cpp @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014-2016, Teo Mrnjavac - * Copyright 2018, Adriaan de Groot + * 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 @@ -19,20 +19,16 @@ #include "PythonJob.h" +#include "CalamaresVersion.h" #include "GlobalStorage.h" #include "JobQueue.h" #include "PythonHelper.h" +#include "PythonJobApi.h" +#include "utils/BoostPython.h" #include "utils/Logger.h" #include -#undef slots -#include -#include - -#include "PythonJobApi.h" - - namespace bp = boost::python; BOOST_PYTHON_FUNCTION_OVERLOADS( mount_overloads, CalamaresPython::mount, 2, 4 ); @@ -180,7 +176,7 @@ PythonJob::PythonJob( const ModuleSystem::InstanceKey& instance, , m_workingPath( workingPath ) , m_description() , m_configurationMap( moduleConfiguration ) - , m_weight( (instance.module() == QStringLiteral( "unpackfs" )) ? 12.0 : 1.0 ) + , m_weight( ( instance.module() == QStringLiteral( "unpackfs" ) ) ? 12.0 : 1.0 ) { } diff --git a/src/libcalamares/PythonJob.h b/src/libcalamares/PythonJob.h index c63daacdc..7cd1b7165 100644 --- a/src/libcalamares/PythonJob.h +++ b/src/libcalamares/PythonJob.h @@ -1,6 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014, Teo Mrnjavac + * 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 @@ -23,7 +24,7 @@ #include "modulesystem/InstanceKey.h" -#include +#include namespace CalamaresPython { diff --git a/src/libcalamares/PythonJobApi.cpp b/src/libcalamares/PythonJobApi.cpp index 7b65e979c..132a9dcf5 100644 --- a/src/libcalamares/PythonJobApi.cpp +++ b/src/libcalamares/PythonJobApi.cpp @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014-2016, Teo Mrnjavac - * Copyright 2017-2019, Adriaan de Groot + * Copyright 2017-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 @@ -19,26 +19,17 @@ #include "PythonJobApi.h" +#include "GlobalStorage.h" +#include "JobQueue.h" #include "PythonHelper.h" #include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.h" #include "utils/String.h" -#include "GlobalStorage.h" -#include "JobQueue.h" - #include #include #include -#undef slots -#include "utils/boost-warnings.h" -#include - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - namespace bp = boost::python; static int diff --git a/src/libcalamares/PythonJobApi.h b/src/libcalamares/PythonJobApi.h index 3d3783f5f..6fb27cd62 100644 --- a/src/libcalamares/PythonJobApi.h +++ b/src/libcalamares/PythonJobApi.h @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014-2016, 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 @@ -20,12 +20,14 @@ #ifndef PYTHONJOBAPI_H #define PYTHONJOBAPI_H -#include "CalamaresVersion.h" +#include "qglobal.h" // For qreal -#include "PythonJob.h" +#include "utils/BoostPython.h" -#undef slots -#include +namespace Calamares +{ +class PythonJob; +} namespace CalamaresPython { 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/locale/Label.cpp b/src/libcalamares/locale/Label.cpp index 58c19101d..816246699 100644 --- a/src/libcalamares/locale/Label.cpp +++ b/src/libcalamares/locale/Label.cpp @@ -24,23 +24,16 @@ namespace CalamaresUtils namespace Locale { -Label::Label() - : m_locale( QLocale() ) +Label::Label( QObject* parent ) + : Label( QString(), LabelFormat::IfNeededWithCountry, parent ) { - m_localeId = m_locale.name(); - - setLabels( QString(), LabelFormat::IfNeededWithCountry ); } -Label::Label( const QString& locale, LabelFormat format ) - : m_locale( Label::getLocale( locale ) ) - , m_localeId( locale ) -{ - setLabels( locale, format ); -} +Label::Label( const QString& locale, LabelFormat format, QObject* parent ) + : QObject( parent ) + , m_locale( Label::getLocale( locale ) ) + , m_localeId( locale.isEmpty() ? m_locale.name() : locale ) -void -Label::setLabels( const QString& locale, LabelFormat format ) { //: language[name] (country[name]) QString longFormat = QObject::tr( "%1 (%2)" ); @@ -69,6 +62,10 @@ Label::setLabels( const QString& locale, LabelFormat format ) QLocale Label::getLocale( const QString& localeName ) { + if ( localeName.isEmpty() ) + { + return QLocale(); + } if ( localeName.contains( "@latin" ) ) { QLocale loc( localeName ); // Ignores @latin diff --git a/src/libcalamares/locale/Label.h b/src/libcalamares/locale/Label.h index 95129d38c..d7fa14453 100644 --- a/src/libcalamares/locale/Label.h +++ b/src/libcalamares/locale/Label.h @@ -21,6 +21,7 @@ #define LOCALE_LABEL_H #include +#include #include namespace CalamaresUtils @@ -35,8 +36,14 @@ namespace Locale * translation system) into QLocales, and also into consistent * human-readable text labels. */ -class Label +class Label : public QObject { + Q_OBJECT + + Q_PROPERTY( QString label READ label CONSTANT FINAL ) + Q_PROPERTY( QString englishLabel READ englishLabel CONSTANT FINAL ) + Q_PROPERTY( QString localeId MEMBER m_localeId CONSTANT FINAL ) + public: /** @brief Formatting option for label -- add (country) to label. */ enum class LabelFormat @@ -46,7 +53,7 @@ public: }; /** @brief Empty locale. This uses the system-default locale. */ - Label(); + Label( QObject* parent = nullptr ); /** @brief Construct from a locale name. * @@ -54,7 +61,9 @@ public: * The @p format determines whether the country name is always present * in the label (human-readable form) or only if needed for disambiguation. */ - Label( const QString& localeName, LabelFormat format = LabelFormat::IfNeededWithCountry ); + Label( const QString& localeName, + LabelFormat format = LabelFormat::IfNeededWithCountry, + QObject* parent = nullptr ); /** @brief Define a sorting order. * @@ -94,8 +103,6 @@ public: static QLocale getLocale( const QString& localeName ); protected: - void setLabels( const QString& name, LabelFormat format ); - QLocale m_locale; QString m_localeId; // the locale identifier, e.g. "en_GB" QString m_label; // the native name of the locale diff --git a/src/libcalamares/locale/LabelModel.cpp b/src/libcalamares/locale/LabelModel.cpp index bcb8af057..da4e1a9f7 100644 --- a/src/libcalamares/locale/LabelModel.cpp +++ b/src/libcalamares/locale/LabelModel.cpp @@ -1,6 +1,7 @@ /* === This file is part of Calamares - === * - * Copyright 2019, Adriaan de Groot + * Copyright 2019-2020 Adriaan de Groot + * Copyright 2019, Camilo Higuita * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,7 +37,7 @@ LabelModel::LabelModel( const QStringList& locales, QObject* parent ) for ( const auto& l : locales ) { - m_locales.push_back( Label( l ) ); + m_locales.push_back( new Label( l, Label::LabelFormat::IfNeededWithCountry, this ) ); } } @@ -65,27 +66,33 @@ LabelModel::data( const QModelIndex& index, int role ) const switch ( role ) { case LabelRole: - return locale.label(); + return locale->label(); case EnglishLabelRole: - return locale.englishLabel(); + return locale->englishLabel(); default: return QVariant(); } } +QHash< int, QByteArray > +LabelModel::roleNames() const +{ + return { { LabelRole, "label" }, { EnglishLabelRole, "englishLabel" } }; +} + const Label& LabelModel::locale( int row ) const { if ( ( row < 0 ) || ( row >= m_locales.count() ) ) { for ( const auto& l : m_locales ) - if ( l.isEnglish() ) + if ( l->isEnglish() ) { - return l; + return *l; } - return m_locales[ 0 ]; + return *m_locales[ 0 ]; } - return m_locales[ row ]; + return *m_locales[ row ]; } int @@ -93,7 +100,7 @@ LabelModel::find( std::function< bool( const Label& ) > predicate ) const { for ( int row = 0; row < m_locales.count(); ++row ) { - if ( predicate( m_locales[ row ] ) ) + if ( predicate( *m_locales[ row ] ) ) { return row; } diff --git a/src/libcalamares/locale/LabelModel.h b/src/libcalamares/locale/LabelModel.h index 03daddbf3..7bd1fad67 100644 --- a/src/libcalamares/locale/LabelModel.h +++ b/src/libcalamares/locale/LabelModel.h @@ -1,6 +1,7 @@ /* === This file is part of Calamares - === * - * Copyright 2019, Adriaan de Groot + * Copyright 2019-2020, Adriaan de Groot + * Copyright 2019, Camilo Higuita * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,6 +34,8 @@ namespace Locale class DLLEXPORT LabelModel : public QAbstractListModel { + Q_OBJECT + public: enum { @@ -46,6 +49,7 @@ public: int rowCount( const QModelIndex& parent ) const override; QVariant data( const QModelIndex& index, int role ) const override; + QHash< int, QByteArray > roleNames() const override; /** @brief Gets locale information for entry #n * @@ -69,7 +73,7 @@ public: int find( const QString& countryCode ) const; private: - QVector< Label > m_locales; + QVector< Label* > m_locales; QStringList m_localeIds; }; 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/BoostPython.h b/src/libcalamares/utils/BoostPython.h new file mode 100644 index 000000000..7bd8865da --- /dev/null +++ b/src/libcalamares/utils/BoostPython.h @@ -0,0 +1,73 @@ +/* === 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 . + */ + +/* + * The Python and Boost::Python headers are not C++14 warning-proof, especially + * with picky compilers like Clang 8 and 9. Since we use Clang for the + * find-all-the-warnings case, switch those warnings off for + * the we-can't-change-them system headers. + * + * This convenience header handles including all the bits we need for + * Python support, while silencing warnings. + */ +#ifndef UTILS_BOOSTPYTHON_H +#define UTILS_BOOSTPYTHON_H + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Wimplicit-float-conversion" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec" +#pragma clang diagnostic ignored "-Wshadow-field-in-constructor" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wcast-align" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wredundant-parens" +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#pragma clang diagnostic ignored "-Wdocumentation" +#pragma clang diagnostic ignored "-Wcomma" +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wunused-template" + +// Actually for Python headers +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif + +#undef slots +#include +#include +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif 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/Entropy.cpp b/src/libcalamares/utils/Entropy.cpp index 643346855..ce1f6ba9d 100644 --- a/src/libcalamares/utils/Entropy.cpp +++ b/src/libcalamares/utils/Entropy.cpp @@ -35,7 +35,7 @@ CalamaresUtils::getEntropy( int size, QByteArray& b ) char* buffer = b.data(); std::fill( buffer, buffer + size, 0xcb ); - int readSize = 0; + qint64 readSize = 0; QFile urandom( "/dev/urandom" ); if ( urandom.exists() && urandom.open( QIODevice::ReadOnly ) ) { @@ -62,7 +62,7 @@ CalamaresUtils::getEntropy( int size, QByteArray& b ) #define GET_ONE_BYTE \ if ( readSize < size ) \ { \ - buffer[ readSize++ ] = next & 0xff; \ + buffer[ readSize++ ] = char( next & 0xffU ); \ next = next >> 8; \ } GET_ONE_BYTE 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..da67f9dd2 --- /dev/null +++ b/src/libcalamares/utils/TestPaths.cpp @@ -0,0 +1,165 @@ +/* === This file is part of Calamares - === + * + * 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 "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..34701a940 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 @@ -221,3 +224,30 @@ LibCalamaresTests::testPrintableEntropy() QVERIFY( c.cell() < 127 ); } } + +void +LibCalamaresTests::testOddSizedPrintable() +{ + QString s; + for ( int l = 0; l <= 37; ++l ) + { + auto r = CalamaresUtils::getPrintableEntropy( l, s ); + if ( l == 0 ) + { + QCOMPARE( r, CalamaresUtils::EntropySource::None ); + } + else + { + QVERIFY( r != CalamaresUtils::EntropySource::None ); + } + QCOMPARE( s.length(), l ); + + for ( QChar c : s ) + { + QVERIFY( c.isPrint() ); + QCOMPARE( c.row(), 0 ); + QVERIFY( c.cell() > 32 ); // ASCII SPACE + QVERIFY( c.cell() < 127 ); + } + } +} diff --git a/src/libcalamares/utils/Tests.h b/src/libcalamares/utils/Tests.h index d369ed4cb..f9908c74c 100644 --- a/src/libcalamares/utils/Tests.h +++ b/src/libcalamares/utils/Tests.h @@ -43,6 +43,7 @@ private Q_SLOTS: /** @brief Tests the entropy functions. */ void testEntropy(); void testPrintableEntropy(); + void testOddSizedPrintable(); }; #endif diff --git a/src/libcalamares/utils/boost-warnings.h b/src/libcalamares/utils/boost-warnings.h deleted file mode 100644 index 65a66b0f6..000000000 --- a/src/libcalamares/utils/boost-warnings.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wreserved-id-macro" -#pragma clang diagnostic ignored "-Wold-style-cast" -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#pragma clang diagnostic ignored "-Wextra-semi-stmt" -#pragma clang diagnostic ignored "-Wall" -#endif diff --git a/src/libcalamaresui/Branding.cpp b/src/libcalamaresui/Branding.cpp index a5b6e5dce..9bdccfa51 100644 --- a/src/libcalamaresui/Branding.cpp +++ b/src/libcalamaresui/Branding.cpp @@ -3,6 +3,7 @@ * Copyright 2014-2015, Teo Mrnjavac * Copyright 2017-2019, Adriaan de Groot * Copyright 2018, Raul Rodrigo Segura (raurodse) + * Copyright 2019, Camilo Higuita * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -74,7 +75,8 @@ const QStringList Branding::s_imageEntryStrings = { "productLogo", "productIcon", - "productWelcome" + "productWelcome", + "productWallpaper" }; const QStringList Branding::s_styleEntryStrings = diff --git a/src/libcalamaresui/Branding.h b/src/libcalamaresui/Branding.h index 30e8be846..e3952881e 100644 --- a/src/libcalamaresui/Branding.h +++ b/src/libcalamaresui/Branding.h @@ -3,6 +3,7 @@ * Copyright 2014-2015, Teo Mrnjavac * Copyright 2017-2018, Adriaan de Groot * Copyright 2018, Raul Rodrigo Segura (raurodse) + * Copyright 2019, Camilo Higuita * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,7 +49,7 @@ public: * e.g. *Branding::ProductName to get the string value for * the product name. */ - enum StringEntry : short + enum StringEntry { ProductName, Version, @@ -62,13 +63,16 @@ public: KnownIssuesUrl, ReleaseNotesUrl }; + Q_ENUM( StringEntry ) enum ImageEntry : short { ProductLogo, ProductIcon, - ProductWelcome + ProductWelcome, + ProductWallpaper }; + Q_ENUM( ImageEntry ) enum StyleEntry : short { @@ -77,6 +81,7 @@ public: SidebarTextSelect, SidebarTextHighlight }; + Q_ENUM( StyleEntry ) /** @brief Setting for how much the main window may expand. */ enum class WindowExpansion @@ -85,6 +90,7 @@ public: Fullscreen, Fixed }; + Q_ENUM( WindowExpansion ) /** @brief Setting for the main window size. * * The units are pixels (Pixies) or something-based-on-fontsize (Fonties), which @@ -96,6 +102,7 @@ public: Pixies, Fonties }; + Q_ENUM( WindowDimensionUnit ) class WindowDimension : public NamedSuffix< WindowDimensionUnit, WindowDimensionUnit::None > { public: diff --git a/src/libcalamaresui/CMakeLists.txt b/src/libcalamaresui/CMakeLists.txt index a9d31c2c3..c603ca22d 100644 --- a/src/libcalamaresui/CMakeLists.txt +++ b/src/libcalamaresui/CMakeLists.txt @@ -16,8 +16,11 @@ set( calamaresui_SOURCES utils/CalamaresUtilsGui.cpp utils/ImageRegistry.cpp utils/Paste.cpp + utils/Qml.cpp viewpages/BlankViewStep.cpp + viewpages/ExecutionViewStep.cpp + viewpages/QmlViewStep.cpp viewpages/ViewStep.cpp widgets/ClickableLabel.cpp @@ -25,7 +28,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/utils/Qml.cpp b/src/libcalamaresui/utils/Qml.cpp new file mode 100644 index 000000000..1ea72e674 --- /dev/null +++ b/src/libcalamaresui/utils/Qml.cpp @@ -0,0 +1,52 @@ +/* === 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 "Qml.h" + +#include "utils/Logger.h" + +#include +#include +#include +#include + +namespace CalamaresUtils +{ + +void +callQMLFunction( QQuickItem* qmlObject, const char* method ) +{ + QByteArray methodSignature( method ); + methodSignature.append( "()" ); + + if ( qmlObject && qmlObject->metaObject()->indexOfMethod( methodSignature ) >= 0 ) + { + QVariant returnValue; + QMetaObject::invokeMethod( qmlObject, method, Q_RETURN_ARG( QVariant, returnValue ) ); + if ( !returnValue.isNull() ) + { + cDebug() << "QML" << methodSignature << "returned" << returnValue; + } + } + else if ( qmlObject ) + { + cDebug() << "QML" << methodSignature << "is missing."; + } +} + +} // namespace CalamaresUtils diff --git a/src/libcalamaresui/utils/Qml.h b/src/libcalamaresui/utils/Qml.h new file mode 100644 index 000000000..70f59e7ec --- /dev/null +++ b/src/libcalamaresui/utils/Qml.h @@ -0,0 +1,42 @@ +/* === 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 UTILS_QML_H +#define UTILS_QML_H + +#include "DllMacro.h" + +class QQuickItem; + +namespace CalamaresUtils +{ + +/** @brief Calls the QML method @p method on @p qmlObject + * + * Pass in only the name of the method (e.g. onActivate). This function + * checks if the method exists (with no arguments) before trying to + * call it, so that no warnings are printed due to missing methods. + * + * If there is a return value from the QML method, it is logged (but not otherwise used). + */ +DLLEXPORT void +callQMLFunction( QQuickItem* qmlObject, const char* method ); + +} // namespace CalamaresUtils + +#endif 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 88% rename from src/libcalamaresui/ExecutionViewStep.cpp rename to src/libcalamaresui/viewpages/ExecutionViewStep.cpp index 501995c07..8ea918690 100644 --- a/src/libcalamaresui/ExecutionViewStep.cpp +++ b/src/libcalamaresui/viewpages/ExecutionViewStep.cpp @@ -18,19 +18,20 @@ * 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" +#include "utils/Qml.h" #include "utils/Retranslator.h" #include @@ -42,35 +43,6 @@ #include #include -/** @brief Calls the QML method @p method() - * - * Pass in only the name of the method (e.g. onActivate). This function - * checks if the method exists (with no arguments) before trying to - * call it, so that no warnings are printed due to missing methods. - * - * If there is a return value from the QML method, it is logged (but not otherwise used). - */ -static void -callQMLFunction( QQuickItem* qmlObject, const char* method ) -{ - QByteArray methodSignature( method ); - methodSignature.append( "()" ); - - if ( qmlObject && qmlObject->metaObject()->indexOfMethod( methodSignature ) >= 0 ) - { - QVariant returnValue; - QMetaObject::invokeMethod( qmlObject, method, Q_RETURN_ARG( QVariant, returnValue ) ); - if ( !returnValue.isNull() ) - { - cDebug() << "QML" << methodSignature << "returned" << returnValue; - } - } - else if ( qmlObject ) - { - cDebug() << "QML" << methodSignature << "is missing."; - } -} - namespace Calamares { @@ -205,7 +177,7 @@ changeSlideShowState( Slideshow state, QQuickItem* slideshow, QQuickWidget* widg if ( Branding::instance()->slideshowAPI() == 2 ) { // The QML was already loaded in the constructor, need to start it - callQMLFunction( slideshow, activate ? "onActivate" : "onLeave" ); + CalamaresUtils::callQMLFunction( slideshow, activate ? "onActivate" : "onLeave" ); } else if ( !Calamares::Branding::instance()->slideshowPath().isEmpty() ) { 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..2bfc72757 --- /dev/null +++ b/src/libcalamaresui/viewpages/QmlViewStep.cpp @@ -0,0 +1,322 @@ +/* === 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 "ViewManager.h" + +#include "utils/Dirs.h" +#include "utils/Logger.h" +#include "utils/NamedEnum.h" +#include "utils/Qml.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; +} + +/// @brief State-change of the QML, for changeQMLState() +enum class QMLAction +{ + Start, + Stop +}; + +/** @brief Tells the QML we activated or left it. + * + * If @p action is @c QMLAction::Start, calls onActivate in the QML. + * If @p action is @c QMLAction::Stop, calls onLeave in the QML. + * + * Sets *activatedInCalamares* property on the QML as well (to true + * if @p action is @c QMLAction::Start, false otherwise). + */ +static void +changeQMLState( QMLAction action, QQuickItem* item ) +{ + static const char propertyName[] = "activatedInCalamares"; + + bool activate = action == QMLAction::Start; + CalamaresUtils::callQMLFunction( item, activate ? "onActivate" : "onLeave" ); + + auto property = item->property( propertyName ); + if ( property.isValid() && ( property.type() == QVariant::Bool ) && ( property.toBool() != activate ) ) + { + item->setProperty( propertyName, activate ); + } +} + +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 ); +} + + +bool +QmlViewStep::isAtBeginning() const +{ + return true; +} + +bool +QmlViewStep::isAtEnd() const +{ + return true; +} +bool +QmlViewStep::isBackEnabled() const +{ + return true; +} + +bool +QmlViewStep::isNextEnabled() const +{ + return true; +} + +Calamares::JobList +QmlViewStep::jobs() const +{ + return JobList(); +} + +void +QmlViewStep::onActivate() +{ + if ( m_qmlObject ) + { + changeQMLState( QMLAction::Start, m_qmlObject ); + } +} + +void +QmlViewStep::onLeave() +{ + if ( m_qmlObject ) + { + changeQMLState( QMLAction::Stop, m_qmlObject ); + } +} + +QWidget* +QmlViewStep::widget() +{ + return m_widget; +} + +void +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 +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"; + } + + if ( ViewManager::instance()->currentStep() == this ) + { + // We're alreay visible! Must have been slow QML loading, and we + // passed onActivate already. + changeQMLState( QMLAction::Start, m_qmlObject ); + } +} + + +/** @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( 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 +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 +QmlViewStep::showFailedQml() +{ + cWarning() << "QmlViewStep" << moduleInstanceKey() << "loading failed."; + m_spinner->setText( prettyName() + ' ' + tr( "Loading failed." ) ); +} + +} // namespace Calamares 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/hostinfo/HostInfoJob.cpp b/src/modules/hostinfo/HostInfoJob.cpp index 3e0e4258c..c2959fb6b 100644 --- a/src/modules/hostinfo/HostInfoJob.cpp +++ b/src/modules/hostinfo/HostInfoJob.cpp @@ -156,7 +156,8 @@ HostInfoJob::exec() gs->insert( "hostOSName", hostOSName() ); gs->insert( "hostCPU", hostCPU() ); - auto ram = CalamaresUtils::BytesToMiB( CalamaresUtils::System::instance()->getTotalMemoryB().first ); + // Memory can't be negative, so it's reported as unsigned long. + auto ram = CalamaresUtils::BytesToMiB( qint64( CalamaresUtils::System::instance()->getTotalMemoryB().first ) ); if ( ram ) { gs->insert( "hostRAMMiB", ram ); diff --git a/src/modules/initcpiocfg/main.py b/src/modules/initcpiocfg/main.py index 6147a508a..ba1d962bb 100644 --- a/src/modules/initcpiocfg/main.py +++ b/src/modules/initcpiocfg/main.py @@ -4,7 +4,7 @@ # === This file is part of Calamares - === # # Copyright 2014, Rohan Garg -# Copyright 2015,2019, Philip Müller +# Copyright 2015,2019,2020, Philip Müller # Copyright 2017, Alf Gaida # Copyright 2019, Adriaan de Groot # @@ -136,6 +136,13 @@ def modify_mkinitcpio_conf(partitions, root_mount_point): if detect_plymouth(): hooks.append("plymouth") + # Detect bootsplash theme and enable hook + bootsplash_folder = os.path.join(root_mount_point, "usr/lib/firmware/bootsplash-themes") + if os.path.exists(bootsplash_folder): + bootsplash_themes = os.listdir(bootsplash_folder) + for bootsplash_theme in bootsplash_themes: + hooks.append("bootsplash-{!s}".format(bootsplash_theme)) + for partition in partitions: if partition["fs"] == "linuxswap": swap_uuid = partition["uuid"] 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/notesqml/CMakeLists.txt b/src/modules/notesqml/CMakeLists.txt new file mode 100644 index 000000000..6aedab5aa --- /dev/null +++ b/src/modules/notesqml/CMakeLists.txt @@ -0,0 +1,11 @@ +calamares_add_plugin( notesqml + TYPE viewmodule + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + NotesQmlViewStep.cpp + RESOURCES + notesqml.qrc + LINK_PRIVATE_LIBRARIES + calamaresui + SHARED_LIB +) diff --git a/src/modules/notesqml/NotesQmlViewStep.cpp b/src/modules/notesqml/NotesQmlViewStep.cpp new file mode 100644 index 000000000..e729c2df7 --- /dev/null +++ b/src/modules/notesqml/NotesQmlViewStep.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 "NotesQmlViewStep.h" + +#include + +NotesQmlViewStep::NotesQmlViewStep( QObject* parent ) + : Calamares::QmlViewStep( "notesqml", parent ) +{ +} + +NotesQmlViewStep::~NotesQmlViewStep() {} + +QString +NotesQmlViewStep::prettyName() const +{ + return m_notesName ? m_notesName->get() : tr( "Notes" ); +} + +void +NotesQmlViewStep::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( NotesQmlViewStepFactory, registerPlugin< NotesQmlViewStep >(); ) diff --git a/src/modules/notesqml/NotesQmlViewStep.h b/src/modules/notesqml/NotesQmlViewStep.h new file mode 100644 index 000000000..445b34c81 --- /dev/null +++ b/src/modules/notesqml/NotesQmlViewStep.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 NOTESQMLVIEWSTEP_H +#define NOTESQMLVIEWSTEP_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 NotesQmlViewStep : public Calamares::QmlViewStep +{ + Q_OBJECT + +public: + NotesQmlViewStep( QObject* parent = nullptr ); + virtual ~NotesQmlViewStep() 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( NotesQmlViewStepFactory ) + +#endif diff --git a/src/modules/notesqml/examples/dummyqml.qml b/src/modules/notesqml/examples/dummyqml.qml new file mode 100644 index 000000000..3cbfa65fc --- /dev/null +++ b/src/modules/notesqml/examples/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/notesqml/notesqml.conf b/src/modules/notesqml/notesqml.conf new file mode 100644 index 000000000..1afd9e682 --- /dev/null +++ b/src/modules/notesqml/notesqml.conf @@ -0,0 +1,40 @@ +# The *notesqml* module can be used to display a QML file +# as an installer step. This is most useful for release-notes +# and similar somewhat-static content, but if you want to you +# can put SameGame in there as well. +# +# While the module compiles a QML file into a QRC for inclusion +# into the shared library, normal use will configure it with +# an external file, either from Calamares AppData directory or +# from the branding directory. +# +# --- +# +# 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 notesqml@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 "notesqml"). +#filename: notesqml + +# This is the name of the module in the progress-tree / sidebar +# in Calamares. To support multiple instances of the QML module, +# the name is configurable and translatable here. +qmlLabel: + notes: "Release Notes" + notes[nl]: "Opmerkingen" diff --git a/src/modules/notesqml/notesqml.qml b/src/modules/notesqml/notesqml.qml new file mode 100644 index 000000000..d1ff4f1b5 --- /dev/null +++ b/src/modules/notesqml/notesqml.qml @@ -0,0 +1,75 @@ +/* === 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 { + id: fscrollbar + width: 10 + policy: ScrollBar.AlwaysOn + } + + TextArea { + id: intro + x: 1 + y: 0 + width: parent.width - fscrollbar.width + font.pointSize: 14 + textFormat: Text.RichText + antialiasing: true + activeFocusOnPress: false + wrapMode: Text.WordWrap + + text: qsTr("

Generic GNU/Linux 2020.2 LTS Turgid Tuba

+

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 text will be center-aligned.

+

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/notesqml/notesqml.qrc b/src/modules/notesqml/notesqml.qrc new file mode 100644 index 000000000..a4aa1909f --- /dev/null +++ b/src/modules/notesqml/notesqml.qrc @@ -0,0 +1,5 @@ + + + notesqml.qml + + diff --git a/src/modules/partition/core/KPMHelpers.h b/src/modules/partition/core/KPMHelpers.h index bca69d1f6..bb510cafb 100644 --- a/src/modules/partition/core/KPMHelpers.h +++ b/src/modules/partition/core/KPMHelpers.h @@ -114,6 +114,31 @@ Partition* createNewEncryptedPartition( PartitionNode* parent, Partition* clonePartition( Device* device, Partition* partition ); QString prettyNameForFileSystemType( FileSystem::Type t ); + +static inline QString +untranslatedFS( FileSystem& fs ) +{ + return fs.name( { QStringLiteral( "C" ) } ); +} + +static inline QString +untranslatedFS( FileSystem* fs ) +{ + return fs ? untranslatedFS( *fs ) : QString(); +} + +static inline QString +userVisibleFS( FileSystem& fs ) +{ + return fs.name(); +} + +static inline QString +userVisibleFS( FileSystem* fs ) +{ + return fs ? userVisibleFS( *fs ) : QString(); +} + } #endif /* KPMHELPERS_H */ diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index 7823d743d..926df03a3 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -2,7 +2,7 @@ * * Copyright 2014, Aurélien Gâteau * Copyright 2016, Teo Mrnjavac - * Copyright 2018, Adriaan de Groot + * Copyright 2018, 2020, Adriaan de Groot * Copyright 2018, Andrius Štikonas * Copyright 2018, Caio Carvalho * @@ -93,11 +93,17 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device, PartitionNode* par else initGptPartitionTypeUi(); - // File system - FileSystem::Type defaultFsType = FileSystem::typeForName( + // File system; the config value is translated (best-effort) to a type + FileSystem::Type defaultFSType; + QString untranslatedFSName = PartUtils::findFS( Calamares::JobQueue::instance()-> globalStorage()-> - value( "defaultFileSystemType" ).toString() ); + value( "defaultFileSystemType" ).toString(), &defaultFSType ); + if ( defaultFSType == FileSystem::Type::Unknown ) + { + defaultFSType = FileSystem::Type::Ext4; + } + int defaultFsIndex = -1; int fsCounter = 0; QStringList fsNames; @@ -106,8 +112,8 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device, PartitionNode* par if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) { - fsNames << fs->name(); - if ( fs->type() == defaultFsType ) + fsNames << KPMHelpers::userVisibleFS( fs ); // This is put into the combobox + if ( fs->type() == defaultFSType ) defaultFsIndex = fsCounter; fsCounter++; } @@ -232,6 +238,7 @@ CreatePartitionDialog::updateMountPointUi() bool enabled = m_ui->primaryRadioButton->isChecked(); if ( enabled ) { + // This maps translated (user-visible) FS names to a type FileSystem::Type type = FileSystem::typeForName( m_ui->fsComboBox->currentText() ); enabled = !s_unmountableFS.contains( type ); diff --git a/src/modules/partition/gui/EditExistingPartitionDialog.cpp b/src/modules/partition/gui/EditExistingPartitionDialog.cpp index 3ad5080b4..6268a2a22 100644 --- a/src/modules/partition/gui/EditExistingPartitionDialog.cpp +++ b/src/modules/partition/gui/EditExistingPartitionDialog.cpp @@ -2,7 +2,7 @@ * * Copyright 2014, Aurélien Gâteau * Copyright 2016, Teo Mrnjavac - * Copyright 2018, Adriaan de Groot + * Copyright 2018, 2020, Adriaan de Groot * * Flags handling originally from KDE Partition Manager, * Copyright 2008-2009, Volker Lanz @@ -22,21 +22,21 @@ * along with Calamares. If not, see . */ -#include +#include "EditExistingPartitionDialog.h" -#include -#include -#include +#include "core/ColorUtils.h" +#include "core/PartitionCoreModule.h" +#include "core/PartitionInfo.h" #include "core/PartUtils.h" -#include +#include "core/KPMHelpers.h" #include "gui/PartitionDialogHelpers.h" -#include +#include "gui/PartitionSizeController.h" -#include +#include "ui_EditExistingPartitionDialog.h" -#include #include "GlobalStorage.h" #include "JobQueue.h" +#include "utils/Logger.h" // KPMcore #include @@ -77,7 +77,7 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device, Partit m_ui->fileSystemComboBox->setEnabled( doFormat ); if ( !doFormat ) - m_ui->fileSystemComboBox->setCurrentText( m_partition->fileSystem().name() ); + m_ui->fileSystemComboBox->setCurrentText( KPMHelpers::userVisibleFS( m_partition->fileSystem() ) ); updateMountPointPicker(); } ); @@ -93,16 +93,25 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device, Partit for ( auto fs : FileSystemFactory::map() ) { if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) - fsNames << fs->name(); + fsNames << KPMHelpers::userVisibleFS( fs ); // For the combo box } m_ui->fileSystemComboBox->addItems( fsNames ); - if ( fsNames.contains( m_partition->fileSystem().name() ) ) - m_ui->fileSystemComboBox->setCurrentText( m_partition->fileSystem().name() ); + FileSystem::Type defaultFSType; + QString untranslatedFSName = PartUtils::findFS( + Calamares::JobQueue::instance()-> + globalStorage()-> + value( "defaultFileSystemType" ).toString(), &defaultFSType ); + if ( defaultFSType == FileSystem::Type::Unknown ) + { + defaultFSType = FileSystem::Type::Ext4; + } + + QString thisFSNameForUser = KPMHelpers::userVisibleFS( m_partition->fileSystem() ); + if ( fsNames.contains( thisFSNameForUser ) ) + m_ui->fileSystemComboBox->setCurrentText( thisFSNameForUser ); else - m_ui->fileSystemComboBox->setCurrentText( Calamares::JobQueue::instance()-> - globalStorage()-> - value( "defaultFileSystemType" ).toString() ); + m_ui->fileSystemComboBox->setCurrentText( FileSystem::nameForType( defaultFSType ) ); m_ui->fileSystemLabel->setEnabled( m_ui->formatRadioButton->isChecked() ); m_ui->fileSystemComboBox->setEnabled( m_ui->formatRadioButton->isChecked() ); diff --git a/src/modules/partition/gui/PartitionViewStep.cpp b/src/modules/partition/gui/PartitionViewStep.cpp index 17ac339e9..1bb7f64fd 100644 --- a/src/modules/partition/gui/PartitionViewStep.cpp +++ b/src/modules/partition/gui/PartitionViewStep.cpp @@ -600,7 +600,7 @@ PartitionViewStep::setConfigurationMap( const QVariantMap& configurationMap ) else if ( fsType != FileSystem::Unknown ) cWarning() << "Partition-module setting *defaultFileSystemType* changed" << fsRealName; else - cWarning() << "Partition-module setting *defaultFileSystemType* is bad (" << fsRealName << ") using ext4."; + cWarning() << "Partition-module setting *defaultFileSystemType* is bad (" << fsName << ") using" << fsRealName << "instead."; gs->insert( "defaultFileSystemType", fsRealName ); diff --git a/src/modules/partition/gui/ReplaceWidget.cpp b/src/modules/partition/gui/ReplaceWidget.cpp index 2ee360ced..3d0711761 100644 --- a/src/modules/partition/gui/ReplaceWidget.cpp +++ b/src/modules/partition/gui/ReplaceWidget.cpp @@ -22,6 +22,7 @@ #include "ui_ReplaceWidget.h" #include "core/DeviceModel.h" +#include "core/KPMHelpers.h" #include "core/PartitionCoreModule.h" #include "core/PartitionActions.h" #include "core/PartitionInfo.h" @@ -192,8 +193,8 @@ ReplaceWidget::onPartitionSelected() return; } - QString prettyName = tr( "Data partition (%1)" ) - .arg( partition->fileSystem().name() ); + QString fsNameForUser = KPMHelpers::userVisibleFS( partition->fileSystem() ); + QString prettyName = tr( "Data partition (%1)" ).arg( fsNameForUser ); for ( const QString& line : osproberLines ) { QStringList lineColumns = line.split( ':' ); @@ -210,13 +211,13 @@ ReplaceWidget::onPartitionSelected() if ( osName.isEmpty() ) { prettyName = tr( "Unknown system partition (%1)" ) - .arg( partition->fileSystem().name() ); + .arg( fsNameForUser ); } else { prettyName = tr ( "%1 system partition (%2)" ) .arg( osName.replace( 0, 1, osName.at( 0 ).toUpper() ) ) - .arg( partition->fileSystem().name() ); + .arg( fsNameForUser ); } break; } diff --git a/src/modules/partition/jobs/CreatePartitionJob.cpp b/src/modules/partition/jobs/CreatePartitionJob.cpp index 9f8a01004..0f9590bfd 100644 --- a/src/modules/partition/jobs/CreatePartitionJob.cpp +++ b/src/modules/partition/jobs/CreatePartitionJob.cpp @@ -2,7 +2,7 @@ * * Copyright 2014, Aurélien Gâteau * Copyright 2015, Teo Mrnjavac - * Copyright 2017, Adriaan de Groot + * Copyright 2017, 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 @@ -20,6 +20,8 @@ #include "jobs/CreatePartitionJob.h" +#include "core/KPMHelpers.h" + #include "utils/Logger.h" #include "utils/Units.h" @@ -32,6 +34,9 @@ #include #include +using KPMHelpers::untranslatedFS; +using KPMHelpers::userVisibleFS; + CreatePartitionJob::CreatePartitionJob( Device* device, Partition* partition ) : PartitionJob( partition ) , m_device( device ) @@ -42,7 +47,7 @@ QString CreatePartitionJob::prettyName() const { return tr( "Create new %2MiB partition on %4 (%3) with file system %1." ) - .arg( m_partition->fileSystem().name() ) + .arg( userVisibleFS( m_partition->fileSystem() ) ) .arg( CalamaresUtils::BytesToMiB( m_partition->capacity() ) ) .arg( m_device->name() ) .arg( m_device->deviceNode() ); @@ -54,7 +59,7 @@ CreatePartitionJob::prettyDescription() const { return tr( "Create new %2MiB partition on %4 " "(%3) with file system %1." ) - .arg( m_partition->fileSystem().name() ) + .arg( userVisibleFS( m_partition->fileSystem() ) ) .arg( CalamaresUtils::BytesToMiB( m_partition->capacity() ) ) .arg( m_device->name() ) .arg( m_device->deviceNode() ); @@ -65,7 +70,7 @@ QString CreatePartitionJob::prettyStatusMessage() const { return tr( "Creating new %1 partition on %2." ) - .arg( m_partition->fileSystem().name() ) + .arg( userVisibleFS( m_partition->fileSystem() ) ) .arg( m_device->deviceNode() ); } diff --git a/src/modules/partition/jobs/FillGlobalStorageJob.cpp b/src/modules/partition/jobs/FillGlobalStorageJob.cpp index 8b981ce3e..12faaf969 100644 --- a/src/modules/partition/jobs/FillGlobalStorageJob.cpp +++ b/src/modules/partition/jobs/FillGlobalStorageJob.cpp @@ -2,7 +2,7 @@ * * Copyright 2014, Aurélien Gâteau * Copyright 2015-2016, Teo Mrnjavac - * Copyright 2017, 2019, Adriaan de Groot + * Copyright 2017, 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 @@ -20,12 +20,13 @@ #include "jobs/FillGlobalStorageJob.h" -#include "GlobalStorage.h" -#include "JobQueue.h" +#include "core/KPMHelpers.h" #include "core/PartitionInfo.h" #include "core/PartitionIterator.h" -#include "core/KPMHelpers.h" + #include "Branding.h" +#include "GlobalStorage.h" +#include "JobQueue.h" #include "utils/Logger.h" // KPMcore @@ -40,16 +41,18 @@ #include #include -typedef QHash UuidForPartitionHash; +using KPMHelpers::untranslatedFS; +using KPMHelpers::userVisibleFS; + +typedef QHash< QString, QString > UuidForPartitionHash; static UuidForPartitionHash -findPartitionUuids( QList < Device* > devices ) +findPartitionUuids( QList< Device* > devices ) { UuidForPartitionHash hash; foreach ( Device* device, devices ) { - for ( auto it = PartitionIterator::begin( device ); - it != PartitionIterator::end( device ); ++it ) + for ( auto it = PartitionIterator::begin( device ); it != PartitionIterator::end( device ); ++it ) { Partition* p = *it; QString path = p->partitionPath(); @@ -59,7 +62,9 @@ findPartitionUuids( QList < Device* > devices ) } if ( hash.isEmpty() ) + { cDebug() << "No UUIDs found for existing partitions."; + } return hash; } @@ -73,7 +78,9 @@ getLuksUuid( const QString& path ) process.start(); process.waitForFinished(); if ( process.exitStatus() != QProcess::NormalExit || process.exitCode() ) + { return QString(); + } QString uuid = QString::fromLocal8Bit( process.readAllStandardOutput() ).trimmed(); return uuid; } @@ -85,22 +92,22 @@ mapForPartition( Partition* partition, const QString& uuid ) QVariantMap map; map[ "device" ] = partition->partitionPath(); map[ "mountPoint" ] = PartitionInfo::mountPoint( partition ); - map[ "fsName" ] = partition->fileSystem().name(); - map[ "fs" ] = partition->fileSystem().name( { QStringLiteral("C") } ); // Untranslated - if ( partition->fileSystem().type() == FileSystem::Luks && - dynamic_cast< FS::luks& >( partition->fileSystem() ).innerFS() ) - map[ "fs" ] = dynamic_cast< FS::luks& >( partition->fileSystem() ).innerFS()->name(); + map[ "fsName" ] = userVisibleFS( partition->fileSystem() ); + map[ "fs" ] = untranslatedFS( partition->fileSystem() ); + if ( partition->fileSystem().type() == FileSystem::Luks + && dynamic_cast< FS::luks& >( partition->fileSystem() ).innerFS() ) + { + map[ "fs" ] = untranslatedFS( dynamic_cast< FS::luks& >( partition->fileSystem() ).innerFS() ); + } map[ "uuid" ] = uuid; // Debugging for inside the loop in createPartitionList(), // so indent a bit Logger::CDebug deb; - using TR = Logger::DebugRow; + using TR = Logger::DebugRow< const char* const, const QString& >; deb << Logger::SubEntry << "mapping for" << partition->partitionPath() << partition->deviceNode() - << TR( "mtpoint:", PartitionInfo::mountPoint( partition ) ) - << TR( "fs:", map[ "fs" ].toString() ) - << TR( "fsname", map[ "fsName" ].toString() ) - << TR( "uuid", uuid ); + << TR( "mtpoint:", PartitionInfo::mountPoint( partition ) ) << TR( "fs:", map[ "fs" ].toString() ) + << TR( "fsName", map[ "fsName" ].toString() ) << TR( "uuid", uuid ); if ( partition->roles().has( PartitionRole::Luks ) ) { @@ -137,7 +144,7 @@ FillGlobalStorageJob::prettyDescription() const QStringList lines; const auto partitionList = createPartitionList().toList(); - for ( const QVariant &partitionItem : partitionList ) + for ( const QVariant& partitionItem : partitionList ) { if ( partitionItem.type() == QVariant::Map ) { @@ -146,32 +153,42 @@ FillGlobalStorageJob::prettyDescription() const QString mountPoint = partitionMap.value( "mountPoint" ).toString(); QString fsType = partitionMap.value( "fs" ).toString(); if ( mountPoint.isEmpty() || fsType.isEmpty() ) + { continue; + } if ( path.isEmpty() ) { if ( mountPoint == "/" ) + { lines.append( tr( "Install %1 on new %2 system partition." ) - .arg( *Calamares::Branding::ShortProductName ) - .arg( fsType ) ); + .arg( *Calamares::Branding::ShortProductName ) + .arg( fsType ) ); + } else + { lines.append( tr( "Set up new %2 partition with mount point " "%1." ) - .arg( mountPoint ) - .arg( fsType ) ); + .arg( mountPoint ) + .arg( fsType ) ); + } } else { if ( mountPoint == "/" ) + { lines.append( tr( "Install %2 on %3 system partition %1." ) - .arg( path ) - .arg( *Calamares::Branding::ShortProductName ) - .arg( fsType ) ); + .arg( path ) + .arg( *Calamares::Branding::ShortProductName ) + .arg( fsType ) ); + } else + { lines.append( tr( "Set up %3 partition %1 with mount point " "%2." ) - .arg( path ) - .arg( mountPoint ) - .arg( fsType ) ); + .arg( path ) + .arg( mountPoint ) + .arg( fsType ) ); + } } } } @@ -179,8 +196,7 @@ FillGlobalStorageJob::prettyDescription() const QVariant bootloaderMap = createBootLoaderMap(); if ( !m_bootLoaderPath.isEmpty() ) { - lines.append( tr( "Install boot loader on %1." ) - .arg( m_bootLoaderPath ) ); + lines.append( tr( "Install boot loader on %1." ).arg( m_bootLoaderPath ) ); } return lines.join( "
" ); } @@ -201,7 +217,9 @@ FillGlobalStorageJob::exec() { QVariant var = createBootLoaderMap(); if ( !var.isValid() ) + { cDebug() << "Failed to find path for boot loader"; + } cDebug() << "FillGlobalStorageJob writing bootLoader path:" << var; storage->insert( "bootLoader", var ); } @@ -222,8 +240,7 @@ FillGlobalStorageJob::createPartitionList() const for ( auto device : m_devices ) { cDebug() << Logger::SubEntry << "partitions on" << device->deviceNode(); - for ( auto it = PartitionIterator::begin( device ); - it != PartitionIterator::end( device ); ++it ) + for ( auto it = PartitionIterator::begin( device ); it != PartitionIterator::end( device ); ++it ) { // Debug-logging is done when creating the map lst << mapForPartition( *it, hash.value( ( *it )->partitionPath() ) ); @@ -241,7 +258,9 @@ FillGlobalStorageJob::createBootLoaderMap() const { Partition* partition = KPMHelpers::findPartitionByMountPoint( m_devices, path ); if ( !partition ) + { return QVariant(); + } path = partition->partitionPath(); } map[ "installPath" ] = path; diff --git a/src/modules/partition/jobs/FormatPartitionJob.cpp b/src/modules/partition/jobs/FormatPartitionJob.cpp index 0d43dfdb3..c877343c3 100644 --- a/src/modules/partition/jobs/FormatPartitionJob.cpp +++ b/src/modules/partition/jobs/FormatPartitionJob.cpp @@ -19,6 +19,8 @@ #include "jobs/FormatPartitionJob.h" +#include "core/KPMHelpers.h" + #include "utils/Logger.h" // KPMcore @@ -29,6 +31,9 @@ #include #include +using KPMHelpers::untranslatedFS; +using KPMHelpers::userVisibleFS; + FormatPartitionJob::FormatPartitionJob( Device* device, Partition* partition ) : PartitionJob( partition ) , m_device( device ) @@ -40,7 +45,7 @@ FormatPartitionJob::prettyName() const { return tr( "Format partition %1 (file system: %2, size: %3 MiB) on %4." ) .arg( m_partition->partitionPath() ) - .arg( m_partition->fileSystem().name() ) + .arg( userVisibleFS( m_partition->fileSystem() ) ) .arg( m_partition->capacity() / 1024 / 1024 ) .arg( m_device->name() ); } @@ -52,7 +57,7 @@ FormatPartitionJob::prettyDescription() const return tr( "Format %3MiB partition %1 with " "file system %2." ) .arg( m_partition->partitionPath() ) - .arg( m_partition->fileSystem().name() ) + .arg( userVisibleFS( m_partition->fileSystem() ) ) .arg( m_partition->capacity() / 1024 / 1024 ); } @@ -63,7 +68,7 @@ FormatPartitionJob::prettyStatusMessage() const return tr( "Formatting partition %1 with " "file system %2." ) .arg( m_partition->partitionPath() ) - .arg( m_partition->fileSystem().name() ); + .arg( userVisibleFS( m_partition->fileSystem() ) ); } diff --git a/src/modules/partition/jobs/SetPartitionFlagsJob.cpp b/src/modules/partition/jobs/SetPartitionFlagsJob.cpp index d79f70479..09380a24c 100644 --- a/src/modules/partition/jobs/SetPartitionFlagsJob.cpp +++ b/src/modules/partition/jobs/SetPartitionFlagsJob.cpp @@ -21,6 +21,8 @@ #include "SetPartitionFlagsJob.h" +#include "core/KPMHelpers.h" + #include "utils/Logger.h" #include "utils/Units.h" @@ -32,6 +34,8 @@ #include using CalamaresUtils::BytesToMiB; +using KPMHelpers::untranslatedFS; +using KPMHelpers::userVisibleFS; SetPartFlagsJob::SetPartFlagsJob( Device* device, Partition* partition, @@ -48,10 +52,11 @@ SetPartFlagsJob::prettyName() const if ( !partition()->partitionPath().isEmpty() ) return tr( "Set flags on partition %1." ).arg( partition()->partitionPath() ); - if ( !partition()->fileSystem().name().isEmpty() ) + QString fsNameForUser = userVisibleFS( partition()->fileSystem() ); + if ( !fsNameForUser.isEmpty() ) return tr( "Set flags on %1MiB %2 partition." ) .arg( BytesToMiB( partition()->capacity() ) ) - .arg( partition()->fileSystem().name() ); + .arg( fsNameForUser ); return tr( "Set flags on new partition." ); } @@ -67,10 +72,11 @@ SetPartFlagsJob::prettyDescription() const return tr( "Clear flags on partition %1." ) .arg( partition()->partitionPath() ); - if ( !partition()->fileSystem().name().isEmpty() ) + QString fsNameForUser = userVisibleFS( partition()->fileSystem() ); + if ( !fsNameForUser.isEmpty() ) return tr( "Clear flags on %1MiB %2 partition." ) .arg( BytesToMiB( partition()->capacity() ) ) - .arg( partition()->fileSystem().name() ); + .arg( fsNameForUser ); return tr( "Clear flags on new partition." ); } @@ -81,11 +87,12 @@ SetPartFlagsJob::prettyDescription() const .arg( partition()->partitionPath() ) .arg( flagsList.join( ", " ) ); - if ( !partition()->fileSystem().name().isEmpty() ) + QString fsNameForUser = userVisibleFS( partition()->fileSystem() ); + if ( !fsNameForUser.isEmpty() ) return tr( "Flag %1MiB %2 partition as " "%3." ) .arg( BytesToMiB( partition()->capacity() ) ) - .arg( partition()->fileSystem().name() ) + .arg( fsNameForUser ) .arg( flagsList.join( ", " ) ); return tr( "Flag new partition as %1." ) @@ -103,10 +110,11 @@ SetPartFlagsJob::prettyStatusMessage() const return tr( "Clearing flags on partition %1." ) .arg( partition()->partitionPath() ); - if ( !partition()->fileSystem().name().isEmpty() ) + QString fsNameForUser = userVisibleFS( partition()->fileSystem() ); + if ( !fsNameForUser.isEmpty() ) return tr( "Clearing flags on %1MiB %2 partition." ) .arg( BytesToMiB( partition()->capacity() ) ) - .arg( partition()->fileSystem().name() ); + .arg( fsNameForUser ); return tr( "Clearing flags on new partition." ); } @@ -117,11 +125,12 @@ SetPartFlagsJob::prettyStatusMessage() const .arg( partition()->partitionPath() ) .arg( flagsList.join( ", " ) ); - if ( !partition()->fileSystem().name().isEmpty() ) + QString fsNameForUser = userVisibleFS( partition()->fileSystem() ); + if ( !fsNameForUser.isEmpty() ) return tr( "Setting flags %3 on " "%1MiB %2 partition." ) .arg( BytesToMiB( partition()->capacity() ) ) - .arg( partition()->fileSystem().name() ) + .arg( fsNameForUser ) .arg( flagsList.join( ", " ) ); return tr( "Setting flags %1 on new partition." ) 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