117 Commits

Author SHA1 Message Date
674bb81358 initial packagechooser loaderqueue implement 2024-01-06 00:09:31 +01:00
demmm
6bd75570de Merge pull request #30 from Undef-a/wip/undef/librem5-screen
mobile: Ensure welcome screen fits on Librem5
2023-09-29 14:19:05 +02:00
undef
acdcdea668 mobile: Ensure welcome screen fits on Librem5
Now that the sizing has changed this line overhangs the screen.
2023-09-29 11:36:34 +00:00
Adriaan de Groot
edb405a4d1 Merge pull request #29 from Undef-a/wip/undef/fix-sizing-with-3.3.x
mobile: Adjust all sizes for Calamares 3.3.x
2023-09-28 23:03:26 +02:00
undef
3ad9c221d4 mobile: Adjust all sizes for Calamares 3.3.x
Element sizing in Calamares 3.3.x seems to have dramatically changed,
with for example a button that was previously fine at width 500 now
being huge to the point of causing the installer to overhang the screen.

This reduces the size of most elements such that they fit on a mobile
screen again.

Fixes: https://github.com/calamares/calamares/issues/2192
2023-09-28 08:56:44 +00:00
Adriaan de Groot
dc3550eccd CMake: post-release housekeeping
- Update CMake requirements to shut KF5 up
- Require the next Calamares main branch
- Bump to 1.4.0 version
2023-08-28 23:52:02 +02:00
Adriaan de Groot
fac16ac6a7 CMake: be more chatty about Calamares version used 2023-08-28 21:04:36 +02:00
Adriaan de Groot
a442ed8c12 CI: remove Linuxisms from release script 2023-08-28 20:34:32 +02:00
Adriaan de Groot
e08d687061 CI: update signing key 2023-08-28 00:33:12 +02:00
Adriaan de Groot
15f6369537 Changes: pre-release housekeeping 2023-08-28 00:31:50 +02:00
Adriaan de Groot
f01b4ed2f8 docs: drop mention of IRC, prefer Matrix for communication 2023-08-27 22:47:08 +02:00
Adriaan de Groot
ea762b0945 Merge pull request #26 from nmschulte/nms/fix-username-prompt
add usermod command about default username
2023-08-06 15:24:10 +02:00
Nathan Schulte
cae8acb4a4 custom username also changes the fullname/comment 2023-08-03 14:52:59 -05:00
Nathan Schulte
ae204d0108 change username before password 2023-07-19 20:49:48 -05:00
Nathan Schulte
a2fae600c4 add usermod command about default username
cmdUsermod supports changing the default username

in case any extra initial state needs updated, cmdUsermod can be amended to run additional commands
2023-07-11 01:29:32 -05:00
Adriaan de Groot
349acad491 Merge pull request #23 from Undef-a/calamares
mobile: Reflow confirm page to fit smaller screens
2023-07-01 22:58:31 +02:00
Adriaan de Groot
9b4c4876bc Merge pull request #24 from ollieparanoid/6-char-pass
mobile: change min pass length to 6 digits
2023-07-01 22:58:18 +02:00
Oliver Smith
cc02baed9a mobile: change min pass length to 6 digits
Require at least 6 characters instead of 8 and mention that digits can
also be used. Most users set something like a 6 digit number on their
lockscreen, it is even what we have by default in the postmarketOS
images when you don't use the installer. So requiring 8 in the installer
does not make sense.

Fixes: https://gitlab.com/postmarketOS/postmarketos-ondev/-/issues/62
2023-06-05 17:49:42 +02:00
undef
dfab089938 mobile: Reflow confirm page to fit smaller screens
When testing on the Librem5 this page hangs slightly off the screen,
causing the "Install" button to be only just selectable on the bottom
edge.

This slightly reflows the page such that on these smaller screens the
button is still in a selectable location.
2023-05-20 09:48:22 +00:00
Adriaan de Groot
2dd9a7ba8a Merge pull request #22 from nmschulte/nms/username-prompt-origin
[mobile] add username prompt, reservedUsernames config, minor cleanup
2023-02-24 22:34:35 +01:00
Nathan Schulte
82f31f3cd9 use reservedUsernames config 2023-02-20 01:24:40 -06:00
Nathan Schulte
c5b01a574d add reservedUsernames config 2023-02-20 01:24:40 -06:00
Nathan Schulte
bc4789d57f add username input field to user_pass module 2023-02-20 01:24:40 -06:00
Nathan Schulte
0962b98494 add username validation routine to mobile module 2022-09-22 00:55:24 -05:00
Nathan Schulte
108476c025 fix typo and cleanup code style 2022-09-22 00:55:24 -05:00
Adriaan de Groot
04a1bc9e2c Merge pull request #21 from demmm/calamares
[KaOS Branding] add navigation & sidebar examples
2022-08-07 14:19:14 +02:00
demmm
bff5e485f4 [KaOS Branding] add navigation & sidebar examples
both set to QML
navigation set vertical, progress set horizontal
include some updates
2022-08-04 18:55:01 +02:00
Adriaan de Groot
3c838436c2 [mobile] Increase timeout for PartitionJob: 10 min 2022-07-16 08:01:53 +02:00
Adriaan de Groot
644c9cf4f3 [unpackfsc] Improve tar support
Enable support for tar option in unpackfsc module for all compression algorithms
2022-07-16 08:00:33 +02:00
Oliver Smith
dcaa378ddd [mobile] Increase timeout for PartitionJob: 10 min
Increase the PartitionJob's timeout from 2 min to 10 min, as there was
an report of hitting the timeout with the PinePhone Pro's 128 GiB eMMC.

Related: https://gitlab.com/postmarketOS/pmaports/-/merge_requests/3280#note_1021536268
2022-07-11 08:25:55 +02:00
sravanpannala
14fd23dcef remove z option from tar so that it works with all 2022-07-06 17:56:28 -04:00
Adriaan de Groot
f4bc7052e0 [unpackfsc] Add (untested) tarball support 2022-05-17 12:30:28 +02:00
Adriaan de Groot
4f0f48d99d [unpackfsc] Unsquash is implemented 2022-05-17 12:21:52 +02:00
Adriaan de Groot
1344880f2e [unpackfsc] Add fsarchiver "fs" mode
- Add "restfs" suppotr for fsarchiver
- Apply coding style from Calamares
2022-05-17 12:12:30 +02:00
Adriaan de Groot
5704e146a0 Changes: pre-release housekeeping 2021-11-16 13:10:36 +01:00
Adriaan de Groot
85eb434b57 Merge branch 'fsarchiver' into calamares 2021-11-16 13:07:14 +01:00
Adriaan de Groot
4b2ea61aa3 [unpackfs] Document that fsarchiver uses savedir/restdir 2021-11-16 13:04:54 +01:00
Adriaan de Groot
39d25eef1c [unpackfsc] Reduce number of status updates in fsarchiver 2021-11-16 13:00:00 +01:00
Adriaan de Groot
e82d32fe66 [unpackfsc] Use restdir instead of restfs 2021-11-16 12:58:17 +01:00
Adriaan de Groot
a50ab49c22 [unpackfsc] Add test-configs for fsarchiver-unpack 2021-11-16 12:52:34 +01:00
Adriaan de Groot
a66ab99ce8 [unpackfsc] Report fsarchiver progress, too 2021-11-16 12:24:33 +01:00
Adriaan de Groot
c3170a9dfb Changes: post-release housekeeping 2021-11-16 12:12:21 +01:00
Adriaan de Groot
88437d3612 CI: fix build-script with new-style version reporting 2021-11-16 01:47:36 +01:00
Adriaan de Groot
6db52a904e [unpackfsc] Fix build with old Qt 2021-11-16 01:41:00 +01:00
Adriaan de Groot
8f0403a3f8 [unpackfsc] Report progress through status message 2021-11-16 00:50:19 +01:00
Adriaan de Groot
caf231b0d1 [unpackfsc] strip() is Python, I guess 2021-11-16 00:46:42 +01:00
Adriaan de Groot
9ad86f81bb [unpackfsc] Avoid newlines in filenames 2021-11-16 00:42:36 +01:00
Adriaan de Groot
4d601f2e6a [unpackfs] Don't reformat table of enum names 2021-11-16 00:36:58 +01:00
Adriaan de Groot
69f68d82e6 [unpackfsc] Get the total number of inodes
- Call run() to actually run the command we've set up; in the
  advice "have you tried turning if off and on again", the "on"
  bit is actually kind of important.
2021-11-16 00:29:18 +01:00
Adriaan de Groot
572a94e493 [unpackfsc] Map destination path to target system 2021-11-15 23:47:18 +01:00
Adriaan de Groot
90454be1b9 [unpackfs] Include a message in progress reports 2021-11-15 23:42:11 +01:00
Adriaan de Groot
6144404bd0 [unpackfsc] Remove unreachable progress 2021-11-15 23:24:56 +01:00
Adriaan de Groot
8f4d8d119c [unpackfsc] Hook up progress 2021-11-15 23:22:52 +01:00
Adriaan de Groot
e8c870205d [unpackfsc] Implement most of unsquash 2021-11-15 23:19:08 +01:00
Adriaan de Groot
081d7d47d1 [unpackfsc] Factor out command-name, it doesn't need translation 2021-11-15 23:19:08 +01:00
Adriaan de Groot
e92ecea3f5 Remove filekeeper job
- the filekeeper was never fully implemented, while core Calamares
  has a preservefiles module that does do all the same things.
2021-11-15 17:20:33 +01:00
Adriaan de Groot
e158402478 [unpackfsc] Use libcalamares version of Runner now 2021-11-09 23:42:45 +01:00
Adriaan de Groot
d242d077db CMake: bump version and Calamares-requirement
This is prep-work for a new release that uses the most
recent Calamares internals.
2021-11-09 23:29:42 +01:00
Adriaan de Groot
5f9b46a820 [os-nixos] Avoid None-concatenate-with-str
When running tests, with no configurations, GS contains none
of the keys the Nix configuration is looking for. Avoid
TypeErrors (but still end up failing the test because we
can't run the nix-config tool at the end).
2021-10-20 15:29:53 +02:00
Adriaan de Groot
9151d0fcee [refind] Fix warning function name 2021-10-20 15:25:43 +02:00
Adriaan de Groot
282f5bfade [filekeeper] Load part of the config file 2021-10-20 15:15:15 +02:00
Adriaan de Groot
eeb264f32e [mobile] Keep the tests happy 2021-10-20 14:58:25 +02:00
Adriaan de Groot
83606aaf9d CI: update to core calamares version of scripts 2021-10-20 14:58:25 +02:00
Adriaan de Groot
322a7a212f [refind] Fix tests
- the file "refind.conf" was interpreted as a **Calamares** configuration
  file for the module; it isn't, it's for rEFInd, so rename it. The
  code does not use it yet, anyway.
- Mark the module as "noconfig" since it only consumes settings from
  the partition module and nothing else.
2021-10-20 14:58:25 +02:00
Adriaan de Groot
40c7496f85 CMake: enable tests
- needs to have loadmodule and test_conf in PATH, which
  means some tweaking is needed to actually *run* them.
2021-10-20 14:58:25 +02:00
Adriaan de Groot
f1fd52e790 [refind] Add module to build 2021-10-20 14:58:25 +02:00
Adriaan de Groot
12f6068622 Merge pull request #17 from Otus9051/patch-1
[os-nixos] Test data - Kolkata is not in Europe
2021-10-20 14:42:44 +02:00
Adriaan de Groot
16a3e2edb2 [refind] Remove checks
- The checks use string manipulations and will always get
  a non-empty string, so the checks can't fail
- Refactor the check that there is an EFI partition mounted
- The refind-install command does not use any of the values
  computed by the checks.
2021-10-20 14:27:55 +02:00
Adriaan de Groot
8fc9b907af [refind] Python stylings
- Use None for unset
- Use logging functions instead of print()
2021-10-20 14:17:43 +02:00
Adriaan de Groot
ecaf2c3076 [refind] Improve device-detection warnings
- Possibly support something other that /boot
- Error message mismatch with what it actually checks
2021-10-20 14:06:55 +02:00
Adriaan de Groot
ea97927997 [refind] Python stylings
- no need for explicit close() in file context
- simplify returns
2021-10-20 14:01:40 +02:00
Adriaan de Groot
6795190216 [refind] kernel_params is a list (converted to space-separated later) 2021-10-20 14:00:50 +02:00
Adriaan de Groot
51149e34d7 Merge pull request #16 from demmm/calamares
[refind] adding a simple rEFInd module
2021-10-20 13:58:21 +02:00
Otus9051
a2674c652d How is Kolkata in Europe? 2021-10-07 08:49:41 +05:30
demmm
d4d6d17efe [refind] apply requested changes
tested on new ISO & installed with refind option
2021-10-04 16:46:38 +02:00
demmm
b74d77a9c5 [refind] adding a simple rEFInd module
takes just 2 options, install in /boot as EFI partition and
uses the refind-install option to install & create needed conf files
no work done to make EFI partiton configurable or choose a manual
option to install.  Those can be added, if there is any interest
2021-09-29 13:36:36 +02:00
Adriaan de Groot
5074bebd32 [os-nixos] Example global-config file, for tests 2021-09-27 14:07:48 +02:00
Adriaan de Groot
76be9988fe SPDX: tag modules 2021-09-27 14:07:27 +02:00
Adriaan de Groot
c85daf8f7b SPDX: tag various 2021-09-27 13:58:33 +02:00
Adriaan de Groot
13e8b1e9b5 SPDX: add dep5-blanket for build artifacts and GH cruft 2021-09-27 13:54:38 +02:00
Adriaan de Groot
e6ddf30512 SPDX: wrangling
- remove / add license texts that are actually used
- the CMakeLists are supposed to be BSD-2-clause
2021-09-27 13:51:41 +02:00
Adriaan de Groot
9938d11ad1 [os-nixos] Replace useless stub implementation
The existing stub just did the same as slow-python, logging
some values and doing nothing useful. It didn't even act
as an example of the original os-nixos plan, which was
to write the config file.

Do a very minimal effort example with variable substitution
from GS.
2021-09-27 13:46:40 +02:00
Adriaan de Groot
def4ee5c7e SPDX: add license texts 2021-09-27 13:44:51 +02:00
Adriaan de Groot
718f73c9c8 CMake: adjust extensions to changes in Calamares core 2021-09-09 10:57:21 +02:00
Adriaan de Groot
720415d8b7 Docs: do not use freenode 2021-06-15 11:26:30 +02:00
Adriaan de Groot
0f0b9aa776 Docs: IRC links to Libera.Chat 2021-05-31 10:43:44 +02:00
Adriaan de Groot
19d508ca2e CI: chase new tag in actions repo 2021-05-28 14:41:07 +02:00
Adriaan de Groot
7af1aeb132 CI: factor out prepatation, chase update to build 2021-05-28 14:35:29 +02:00
Adriaan de Groot
3f7cabe832 CI: use generic-build action instead of expanding it here 2021-05-28 13:39:29 +02:00
Adriaan de Groot
341c9f4a7f CI: switch to shared Calamares actions entirely 2021-05-28 12:50:44 +02:00
Adriaan de Groot
03260efb3a CI: try to use (shared) Calamares notification action 2021-05-28 12:11:12 +02:00
Adriaan de Groot
18060db82d CI: try to use (shared) Calamares notification action 2021-05-28 12:00:10 +02:00
Adriaan de Groot
b978f03618 CI: update to current matrix-notifications 2021-05-26 16:53:23 +02:00
Adriaan de Groot
a4a6194a5d CI: Add Matrix notification action 2021-05-25 16:31:13 +02:00
Adriaan de Groot
93cf06da82 CI: restore the success/fail check, silence curl output 2021-05-21 16:21:24 +02:00
Adriaan de Groot
901211c12d CI: repair the action YAML 2021-05-21 16:12:50 +02:00
Adriaan de Groot
e89a58a34b Merge branch 'add-unpackfsc' into calamares 2021-05-21 15:02:38 +02:00
Adriaan de Groot
fb36765982 Merge branch 'update-release-reporting' into calamares 2021-05-15 17:40:59 +02:00
Adriaan de Groot
f1d2bfacce CI: switch to matrix notifications 2021-05-15 17:40:46 +02:00
Adriaan de Groot
9703bd9058 CMake: bump required Calamares version
We need the latest version with new failure-enum values.
2021-05-15 17:12:15 +02:00
Adriaan de Groot
0e2a178436 Docs: badge for Matrix 2021-05-19 15:21:21 +02:00
Adriaan de Groot
fb70e29e29 Docs: add a CONTRIBUTING document
- just copy the Calamares one and point to it for all technicalities
2021-05-19 14:31:48 +02:00
Adriaan de Groot
fbfa9d8038 Docs: mention the communication channels 2021-05-19 14:28:06 +02:00
Adriaan de Groot
59ab728502 [unpackfsc] Build a proxy for handling process output
- Rearrange sources, give implementation classes their own header
- Add a *RunCommand* class to run the commands for the tools.
  Existing code in System::runCommand() does not expose any
  of the output until the process is done. This means it's
  not useful for reporting progress information. New code
  **does** do that.

Rearrange sources, split to multiple
2021-05-17 14:55:46 +02:00
Adriaan de Groot
476224d34e [unpackfsc] Support functions for Runners 2021-05-17 14:12:02 +02:00
Adriaan de Groot
2aaf440fec [unpackfsc] Stub of the Unsquash runner 2021-05-17 14:12:02 +02:00
Adriaan de Groot
b416caa462 [unpackfsc] Apply coding style 2021-05-17 11:04:34 +02:00
Adriaan de Groot
0c11b864e5 [unpackfsc] Start implementing fsarchive runner 2021-05-15 01:08:05 +02:00
Adriaan de Groot
0f9ffb9e35 CMake: Calamares is C++17, use it in extensions as well. 2021-05-15 00:13:40 +02:00
Adriaan de Groot
813d26ec4e [unpackfsc] Add stubs for calling the external tools.
This isn't the right way to do it: there is no possibility to
report progress or let the tool run asynchronously.
2021-05-15 00:12:48 +02:00
Adriaan de Groot
5d44118b4b [unpackfsc] Simplify to **one** unpack action
The whole getup with multiple unpack actions makes the module
more complicated; instead, use one unpack action only. If there
are more of them, use multiple instances.

Similarly, remove the Config object: The **view** modules need a
Config object, because the config can be used by multiple UIs, and
we don't want to mix UI with business logic. For a single job,
that's different. Just put the job config in the job itself.
2021-05-15 00:11:16 +02:00
Adriaan de Groot
833e33f011 CMake: Calamares is C++17, use it in extensions as well. 2021-05-15 00:11:16 +02:00
Adriaan de Groot
ea3a03a8cc CI: sign release tarball after it's made
- replace this manual step with signing automatically;
  the tag is signes as well so the gpg-agent likely
  still has the key.
2021-05-15 00:02:45 +02:00
Adriaan de Groot
05bbeae2c3 CI: Use new versioning support from CMakeLists.txt 2021-05-15 00:02:45 +02:00
Adriaan de Groot
b9b923e69c CMake: update versioning infrastructure
Be smarter with versioning information: we don't
need to generate a whole build system to query
the version number set in CMake. Use script mode,
and print the version. Unfortunately, `project()`
isn't valid in script mode so we need to lift the
version into a variable and stick in some boilerplate
for printing the (full) version.
2021-05-14 23:59:36 +02:00
Adriaan de Groot
809491c969 [unpackfsc] Load and log configuration 2021-04-06 14:22:06 +02:00
Adriaan de Groot
febeb3281e [unpackfsc] Stub of unpackfsc
This module is supposed to use fsarchiver (or similar tools)
to unpack a filesystem to the target. In many ways it is like
the rawfs module.
2021-04-06 13:23:05 +02:00
100 changed files with 5914 additions and 469 deletions

View File

@@ -8,6 +8,7 @@ AlignEscapedNewlines: DontAlign
AllowAllParametersOfDeclarationOnNextLine: "false" AllowAllParametersOfDeclarationOnNextLine: "false"
AllowShortFunctionsOnASingleLine: Inline AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: "false" AllowShortIfStatementsOnASingleLine: "false"
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: "false" AllowShortLoopsOnASingleLine: "false"
AlwaysBreakAfterReturnType: TopLevelDefinitions AlwaysBreakAfterReturnType: TopLevelDefinitions
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes
@@ -28,7 +29,6 @@ ReflowComments: "false"
SortIncludes: "true" SortIncludes: "true"
SpaceAfterCStyleCast: "false" SpaceAfterCStyleCast: "false"
SpacesBeforeTrailingComments: "2" SpacesBeforeTrailingComments: "2"
# SpaceInEmptyBlock: "true"
SpacesInAngles: "true" SpacesInAngles: "true"
SpacesInParentheses: "true" SpacesInParentheses: "true"
SpacesInSquareBrackets: "true" SpacesInSquareBrackets: "true"

35
.clang-format.base Normal file
View File

@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
BasedOnStyle: WebKit
AlignAfterOpenBracket: Align
AlignEscapedNewlines: DontAlign
AllowAllParametersOfDeclarationOnNextLine: "false"
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: "false"
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: "false"
AlwaysBreakAfterReturnType: TopLevelDefinitions
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: "false"
BinPackParameters: "false"
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: "true"
BreakConstructorInitializers: BeforeComma
ColumnLimit: 120
Cpp11BracedListStyle: "false"
FixNamespaceComments: "true"
IncludeBlocks: Preserve
IndentWidth: "4"
MaxEmptyLinesToKeep: "2"
NamespaceIndentation: None
PointerAlignment: Left
ReflowComments: "false"
SortIncludes: "true"
SpaceAfterCStyleCast: "false"
SpacesBeforeTrailingComments: "2"
SpacesInAngles: "true"
SpacesInParentheses: "true"
SpacesInSquareBrackets: "true"
Standard: Cpp11

View File

@@ -31,70 +31,30 @@ jobs:
path: ${{ env.BUILDDIR }} path: ${{ env.BUILDDIR }}
repo: calamares/calamares repo: calamares/calamares
- name: "prepare env" - name: "prepare env"
run: | uses: calamares/actions/prepare-neon@v2
sudo apt-get update
sudo apt-get -y install git-core
sudo apt-get -y install \
build-essential \
cmake \
extra-cmake-modules \
gettext \
kio-dev \
libatasmart-dev \
libboost-python-dev \
libkf5config-dev \
libkf5coreaddons-dev \
libkf5i18n-dev \
libkf5iconthemes-dev \
libkf5parts-dev \
libkf5service-dev \
libkf5solid-dev \
libkpmcore-dev \
libparted-dev \
libpolkit-qt5-1-dev \
libqt5svg5-dev \
libqt5webkit5-dev \
libyaml-cpp-dev \
os-prober \
pkg-config \
python3-dev \
qtbase5-dev \
qtdeclarative5-dev \
qttools5-dev \
qttools5-dev-tools
- name: "prepare source" - name: "prepare source"
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: "prepare artifacts" - name: "prepare artifacts"
run: tar xvzf "$BUILDDIR/calamares.tar.gz" -C / --strip-components 1 run: tar xvzf "$BUILDDIR/calamares.tar.gz" -C / --strip-components 1
- name: "prepare build" - name: "build"
id: pre_build id: build
run: | uses: calamares/actions/generic-build@v2
test -n "$BUILDDIR" || { echo "! \$BUILDDIR not set" ; exit 1 ; }
mkdir -p $BUILDDIR
test -f $SRCDIR/CMakeLists.txt || { echo "! Missing $SRCDIR/CMakeLists.txt" ; exit 1 ; }
echo "::set-output name=message::"`git log -1 --abbrev-commit --pretty=oneline --no-decorate ${{ github.event.head_commit.id }}`
- name: "Calamares-Extensions: cmake"
working-directory: ${{ env.BUILDDIR }}
run: cmake $CMAKE_ARGS $SRCDIR
- name: "Calamares-Extensions: make"
working-directory: ${{ env.BUILDDIR }}
run: make -j2 VERBOSE=1
- name: "Calamares-Extensions: install"
working-directory: ${{ env.BUILDDIR }}
run: make install VERBOSE=1
- name: "notify: ok" - name: "notify: ok"
uses: rectalogic/notify-irc@v1
if: ${{ success() && github.repository == 'calamares/calamares-extensions' }} if: ${{ success() && github.repository == 'calamares/calamares-extensions' }}
uses: calamares/actions/matrix-notify@v2
with: with:
server: chat.freenode.net token: ${{ secrets.MATRIX_TOKEN }}
nickname: cala-ci room: ${{ secrets.MATRIX_ROOM }}
channel: "#calamares" message: "OK ${{ github.workflow }} in ${{ github.repository }} ${{ steps.build.outputs.git_summary }}"
message: "OK ${{ github.workflow }} in ${{ github.repository }} ${{ steps.pre_build.outputs.message }}"
- name: "notify: fail" - name: "notify: fail"
uses: rectalogic/notify-irc@v1
if: ${{ failure() && github.repository == 'calamares/calamares' }} if: ${{ failure() && github.repository == 'calamares/calamares' }}
uses: calamares/actions/matrix-notify@v2
with: with:
server: chat.freenode.net token: ${{ secrets.MATRIX_TOKEN }}
nickname: cala-ci room: ${{ secrets.MATRIX_ROOM }}
channel: "#calamares" message: "FAIL ${{ github.workflow }} in ${{ github.repository }} ${{ steps.build.outputs.git_summary}}"
message: "FAIL ${{ github.workflow }} in ${{ github.repository }} ${{ steps.pre_build.outputs.message }}"

View File

@@ -37,70 +37,30 @@ jobs:
path: ${{ env.BUILDDIR }} path: ${{ env.BUILDDIR }}
repo: calamares/calamares repo: calamares/calamares
- name: "prepare env" - name: "prepare env"
run: | uses: calamares/actions/prepare-neon@v2
sudo apt-get update
sudo apt-get -y install git-core
sudo apt-get -y install \
build-essential \
cmake \
extra-cmake-modules \
gettext \
kio-dev \
libatasmart-dev \
libboost-python-dev \
libkf5config-dev \
libkf5coreaddons-dev \
libkf5i18n-dev \
libkf5iconthemes-dev \
libkf5parts-dev \
libkf5service-dev \
libkf5solid-dev \
libkpmcore-dev \
libparted-dev \
libpolkit-qt5-1-dev \
libqt5svg5-dev \
libqt5webkit5-dev \
libyaml-cpp-dev \
os-prober \
pkg-config \
python3-dev \
qtbase5-dev \
qtdeclarative5-dev \
qttools5-dev \
qttools5-dev-tools
- name: "prepare source" - name: "prepare source"
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: "prepare artifacts" - name: "prepare artifacts"
run: tar xvzf "$BUILDDIR/calamares.tar.gz" -C / --strip-components 1 run: tar xvzf "$BUILDDIR/calamares.tar.gz" -C / --strip-components 1
- name: "prepare build" - name: "build"
id: pre_build id: build
run: | uses: calamares/actions/generic-build@v2
test -n "$BUILDDIR" || { echo "! \$BUILDDIR not set" ; exit 1 ; }
mkdir -p $BUILDDIR
test -f $SRCDIR/CMakeLists.txt || { echo "! Missing $SRCDIR/CMakeLists.txt" ; exit 1 ; }
echo "::set-output name=message::"`git log -1 --abbrev-commit --pretty=oneline --no-decorate ${{ github.event.head_commit.id }}`
- name: "Calamares-Extensions: cmake"
working-directory: ${{ env.BUILDDIR }}
run: cmake $CMAKE_ARGS $SRCDIR
- name: "Calamares-Extensions: make"
working-directory: ${{ env.BUILDDIR }}
run: make -j2 VERBOSE=1
- name: "Calamares-Extensions: install"
working-directory: ${{ env.BUILDDIR }}
run: make install VERBOSE=1
- name: "notify: ok" - name: "notify: ok"
uses: rectalogic/notify-irc@v1
if: ${{ success() && github.repository == 'calamares/calamares-extensions' }} if: ${{ success() && github.repository == 'calamares/calamares-extensions' }}
uses: calamares/actions/matrix-notify@v2
with: with:
server: chat.freenode.net token: ${{ secrets.MATRIX_TOKEN }}
nickname: cala-ci room: ${{ secrets.MATRIX_ROOM }}
channel: "#calamares" message: |
message: "OK ${{ github.workflow }} in ${{ github.repository }} ${{ github.actor }} on ${{ github.event.ref }}\n.. ${{ steps.pre_build.outputs.message }}" OK ${{ github.workflow }} in ${{ github.repository }} by ${{ github.actor }} on ${{ github.event.ref }}
.. ${{ steps.build.outputs.git-summary }}
- name: "notify: fail" - name: "notify: fail"
uses: rectalogic/notify-irc@v1 if: ${{ failure() && github.repository == 'calamares/calamares-extensions' }}
if: ${{ failure() && github.repository == 'calamares/calamares' }} uses: calamares/actions/matrix-notify@v2
with: with:
server: chat.freenode.net token: ${{ secrets.MATRIX_TOKEN }}
nickname: cala-ci room: ${{ secrets.MATRIX_ROOM }}
channel: "#calamares" message: |
message: "FAIL ${{ github.workflow }} in ${{ github.repository }} ${{ github.actor }} on ${{ github.event.ref }}\n.. ${{ steps.pre_build.outputs.message }}\n.. DIFF ${{ github.event.compare }}" FAIL ${{ github.workflow }} in ${{ github.repository }} by ${{ github.actor }} on ${{ github.event.ref }}
.. ${{ steps.build.outputs.git-summary }}
.. ${{ github.event.compare }}

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
.kdev4/ .kdev4/
build/ build/
*.kdev4 *.kdev4

23
.reuse/dep5 Normal file
View File

@@ -0,0 +1,23 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Calamares-Extensions
Source: https://github.com/calamares/calamares-extensions.git
### BUILD ARTIFACTS / NOT SOURCE
#
# QRC Files are basically build artifacts
#
FILES: modules/*/*.qrc
License: CC0-1.0
Copyright: no
# GitHub issue templates are not part of the source
#
Files: .github/ISSUE_TEMPLATE/*
License: CC0-1.0
Copyright: no
# GitHub actions are not part of the source
Files: .github/workflows/*.yml
License: CC0-1.0
Copyright: no

43
CHANGES
View File

@@ -6,6 +6,49 @@ This is the changelog for Calamares-Extensions. For each release, the major
changes and contributors are listed. Note that Calamares-Extensions does not changes and contributors are listed. Note that Calamares-Extensions does not
have a historical changelog -- this log starts with version 1.0.0. have a historical changelog -- this log starts with version 1.0.0.
# 1.3.2 (2023-08-28)
We skipped a couple of releases in the release-notes, then tagged
1.3.1 without a version bump or release-notes. So 1.3.2 brings us
back to "regular releases".
This release contains contributions from (alphabetically by first name):
- Anke Boersma
- Nathan Schulte
- Oliver Smith
- stravanpannala
- undef
Changes and new modules in this release:
- *mobile* Has new configuration options. (Thanks Nathan, Oliver)
- *unpackfsc* Uses a more portable invocation of tar. (Thanks sravanpannala)
# 1.2.1 (2021-11-16)
The 1.2.0 release had no release-notes for that version, and failed to
credit Anke and Otus.
Changes and new modules in this release:
- *unpackfsc* can use `fsarchiver` and unpack that, instead of squashfs;
a distro might choose one tool or the other. Currently, only *savedir* /
*restdir* mode (i.e. directories, not block-devices) are supported.
# 1.2.0 (2021-11-16)
This release contains contributions from (alphabetically by first name):
- Anke Boersma
- Otus9051
Changes and new modules in this release:
- *refind* is a new module that installs the rEFInd bootloader. It can
be used instead of the *bootloader* module from core Calamares. (Thanks Anke)
- *unpackfsc* is a new module that uses `unsquashfs` directly. This may
be faster or more convenient than the *unpackfs* module from core Calamares.
The configuration file supports only one entry, but is otherwise easy to
adapt from an existing `unpackfs.conf`.
# 1.1.2 (2021-05-14) # 1.1.2 (2021-05-14)
This release contains contributions from (alphabetically by first name): This release contains contributions from (alphabetically by first name):

View File

@@ -1,20 +1,15 @@
# === This file is part of Calamares - <https://github.com/calamares> === # === This file is part of Calamares - <https://github.com/calamares> ===
# #
# Calamares is free software: you can redistribute it and/or modify # SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
# it under the terms of the GNU General Public License as published by # SPDX-License-Identifier: GPL-3.0-or-later
# 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 # Calamares-Examples is Free Software: see the License-Identifier above.
# along with Calamares. If not, see <http://www.gnu.org/licenses/>.
# #
# SPDX-License-Identifier: GPL-3.0+ # Individual files may have different licenses (like the CMake
# License-Filename: LICENSE # infrastructure, which is BSD-2-Clause licensed). Check the SPDX
# identifiers in each file.
# #
### ###
# #
@@ -36,22 +31,44 @@
# USE_* : fills in SKIP_MODULES for modules called *-<something> # USE_* : fills in SKIP_MODULES for modules called *-<something>
# In this repository, there is just one "group" to which USE_* applies: # In this repository, there is just one "group" to which USE_* applies:
# USE_os : operating-system-specific modules. # USE_os : operating-system-specific modules.
cmake_minimum_required(VERSION 3.3 FATAL_ERROR) #
### NOTES
#
# Call this CMake file in script mode, e.g. `cmake -P CMakeLists.txt`
# to print out version information. Use `cmake -DVERSION_STYLE=short`
# to get just the short versioning.
#
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set( CALAMARES_EXTENSIONS_VERSION 1.4.0 )
include( ${CMAKE_CURRENT_LIST_DIR}/CMakeModules/ExtendedVersion.cmake )
if ( CMAKE_SCRIPT_MODE_FILE )
report_version( ${CALAMARES_EXTENSIONS_VERSION} ${CMAKE_CURRENT_LIST_DIR} )
return()
endif()
project(calamares-extensions project(calamares-extensions
VERSION 1.1.2 VERSION ${CALAMARES_EXTENSIONS_VERSION}
LANGUAGES CXX LANGUAGES CXX
) )
set( CMAKE_CXX_STANDARD 17 )
set( CMAKE_CXX_STANDARD_REQUIRED ON )
# On developer's machine, the user package registry breaks # On developer's machine, the user package registry breaks
# consumers by loading the developer's config from a build # consumers by loading the developer's config from a build
# directory (which doesn't have the rest of the config # directory (which doesn't have the rest of the config
# installed inside it). # installed inside it).
find_package(Calamares 3.2.26 REQUIRED NO_CMAKE_PACKAGE_REGISTRY) set( CALAMARES_VERSION_REQUIRED 3.3.0 )
find_package(Calamares ${CALAMARES_VERSION_REQUIRED} NO_CMAKE_PACKAGE_REGISTRY)
if (NOT TARGET Calamares::calamares OR NOT TARGET Calamares::calamaresui) if (NOT TARGET Calamares::calamares OR NOT TARGET Calamares::calamaresui)
message(FATAL_ERROR "Calamares found with missing CMake targets") find_package(Calamares ${CALAMARES_VERSION_REQUIRED} REQUIRED)
endif() endif()
find_package(YAMLCPP REQUIRED) # Needed to untangle some dependencies before Calamares 3.2.36 message(STATUS "Found Calamares version ${Calamares_VERSION}")
message(STATUS " libraries ${Calamares_LIB_DIRS}")
message(STATUS "")
### CMAKE SETUP ### CMAKE SETUP
# #
@@ -71,6 +88,7 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.10.0")
"K_EXPORT_PLASMA_RUNNER" "K_EXPORT_PLASMA_RUNNER"
) )
endif() endif()
include( CTest )
### BRANDING ### BRANDING
@@ -97,28 +115,16 @@ calamares_add_branding_subdirectory( branding/kaos_branding NAME kaos )
# #
set(LIST_SKIPPED_MODULES "") set(LIST_SKIPPED_MODULES "")
calamares_add_module_subdirectory( modules/filekeeper LIST_SKIPPED_MODULES ) # C++ job
calamares_add_module_subdirectory( modules/freebsddisk LIST_SKIPPED_MODULES ) # C++ viewmodule calamares_add_module_subdirectory( modules/freebsddisk LIST_SKIPPED_MODULES ) # C++ viewmodule
calamares_add_module_subdirectory( modules/mobile LIST_SKIPPED_MODULES ) calamares_add_module_subdirectory( modules/mobile LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/os-freebsd LIST_SKIPPED_MODULES ) calamares_add_module_subdirectory( modules/os-freebsd LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/os-nixos LIST_SKIPPED_MODULES ) calamares_add_module_subdirectory( modules/os-nixos LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/refind LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/slowpython LIST_SKIPPED_MODULES ) # Python job calamares_add_module_subdirectory( modules/slowpython LIST_SKIPPED_MODULES ) # Python job
calamares_add_module_subdirectory( modules/unpackfsc LIST_SKIPPED_MODULES )
message(STATUS "Calamares extensions ${CALAMARES_EXTENSIONS_VERSION} for Calamares version ${Calamares_VERSION}")
# If modules cannot be built, they usually call a macro # If modules cannot be built, they usually call a macro
# which builds a list of explanations; show that list. # which builds a list of explanations; show that list.
calamares_explain_skipped_modules( ${LIST_SKIPPED_MODULES} ) calamares_explain_skipped_modules( ${LIST_SKIPPED_MODULES} )
### RELEASE SUPPORT
#
#
set( CALAMARES_VERSION ${calamares-extensions_VERSION_MAJOR}.${calamares-extensions_VERSION_MINOR}.${calamares-extensions_VERSION_PATCH} )
# In rare cases we have hotfix-releases with a tweak
if( calamares-extensions_VERSION_TWEAK )
set( CALAMARES_VERSION "${calamares-extensions_VERSION}.${calamares-extensions_VERSION_TWEAK}" )
endif()
set( CALAMARES_VERSION_SHORT "${CALAMARES_VERSION}" )
add_custom_target(show-version
${CMAKE_COMMAND} -E echo CALAMARES_VERSION=${CALAMARES_VERSION_SHORT}
USES_TERMINAL
)

View File

@@ -0,0 +1,90 @@
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
#
###
#
# This file defines one function for extending a VERSION-like value
# with date and git information (if desired).
#
# - extend_version( version-string short_var long_var )
# Calling this function will copy *version-string* (which would typically
# be a semver-style string, like "3.2.40") into the variable *short_var*.
# The *version-string* plus date and git information (if git is available),
# is copied into the varialbe *long_var*, in the format {version}-{date}-{hash}
#
# A helper function that may be used independently:
#
# - get_git_version_info( out_var )
# If relevant and possible (e.g. it is a git checkout and git is availablle
# in the environment), put git versioning information in *out_var*.
#
# A convenience function for use from script-mode for version reporting:
#
# - report_version( version top_dir )
# Call this with an intended version string (e.g. "1.1") and
# the top-level source directory (e.g. `${CMAKE_CURRENT_LIST_DIR}`
# or `${CMAKE_SOURCE_DIR}` .. in script mode, the latter is not defined).
#
function( get_git_version_info out_var )
set(CMAKE_VERSION_SOURCE "")
if(EXISTS ${CMAKE_SOURCE_DIR}/.git/HEAD)
find_program(GIT_EXECUTABLE NAMES git git.cmd)
mark_as_advanced(GIT_EXECUTABLE)
if(GIT_EXECUTABLE)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --verify -q --short=8 HEAD
OUTPUT_VARIABLE head
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
if(head)
set(CMAKE_VERSION_SOURCE "${head}")
execute_process(
COMMAND ${GIT_EXECUTABLE} update-index -q --refresh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
execute_process(
COMMAND ${GIT_EXECUTABLE} diff-index --name-only HEAD --
OUTPUT_VARIABLE dirty
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
if(dirty)
set(CMAKE_VERSION_SOURCE "${CMAKE_VERSION_SOURCE}-dirty")
endif()
endif()
endif()
endif()
set( ${out_var} "${CMAKE_VERSION_SOURCE}" PARENT_SCOPE )
endfunction()
function( extend_version version short_var long_var )
set( ${short_var} "${version}" PARENT_SCOPE )
# Additional info for non-release builds which want "long" version info
# with date and git information (commit, dirty status).
set( _v "${version}" )
string( TIMESTAMP CALAMARES_VERSION_DATE "%Y%m%d" )
if( CALAMARES_VERSION_DATE GREATER 0 )
set( _v ${_v}.${CALAMARES_VERSION_DATE} )
endif()
get_git_version_info( _gitv )
if( _gitv )
set( _v "${_v}-${_gitv}" )
endif()
set( ${long_var} "${_v}" PARENT_SCOPE )
endfunction()
function( report_version version top_dir )
set( CMAKE_SOURCE_DIR ${top_dir} )
extend_version( ${version} _vshort _vlong )
if ( "x${VERSION_STYLE}" STREQUAL "xshort" )
message( "${_vshort}" )
else()
message( "${_vlong}" )
endif()
endfunction()

40
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,40 @@
<!-- SPDX-FileCopyrightText: no
SPDX-License-Identifier: CC0-1.0
-->
# Contributing to Calamares Extensions
Welcome to Calamares! We're happy that you would like to add
something to Calamares -- by extending it!
This contribution guide is minimal:
all the **technical** parts of contributing to
Calamares Extensions are the same as [contributing to Calamares](https://github.com/calamares/calamares/CONTRIBUTING.md).
## Code of Conduct
The Calamares community -- of developers, translators, and downstream (distro) users --
aims to be courteous, professional, and inclusive. Harrassment, discriminatory
statements and abuse are not tolerated. In general, we apply the
[KDE Code of Conduct](https://www.kde.org/code-of-conduct/) and the
[GNOME Code of Conduct](https://wiki.gnome.org/Foundation/CodeOfConduct) (the
rules of decent behavior in both communities are pretty much the same).
> See the [CoC section on the wiki](https://github.com/calamares/calamares/wiki#code-of-conduct)
> for a longer text. To report a problem, please contact the maintainer,
> Adriaan de Groot, or the KDE Community Working Group.
## Join the Conversation
GitHub Issues are **one** place for discussing Calamares and its extensions if there are concrete
problems or a new feature to discuss.
Issues are not a help channel.
Visit Matrix for help with configuration or compilation.
Regular Calamares development chit-chat happens in a [Matrix](https://matrix.org/)
room, `#calamares:kde.org`. Responsiveness is best during the day
in Europe, but feel free to idle.
Matrix is persistent, and we'll see your message eventually.
* [![Join us on Matrix](https://img.shields.io/badge/Matrix-%23calamares:kde.org-blue)](https://webchat.kde.org/#/room/%23calamares:kde.org)

24
LICENSES/BSD-2-Clause.txt Normal file
View File

@@ -0,0 +1,24 @@
Copyright 2019 Adriaan de Groot <groot@kde.org>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

119
LICENSES/CC0-1.0.txt Normal file
View File

@@ -0,0 +1,119 @@
Creative Commons Legal Code
CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES
NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE
AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE
OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS
LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION
OR WORKS PROVIDED HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer exclusive
Copyright and Related Rights (defined below) upon the creator and subsequent
owner(s) (each and all, an "owner") of an original work of authorship and/or
a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later claims
of infringement build upon, modify, incorporate in other works, reuse and
redistribute as freely as possible in any form whatsoever and for any purposes,
including without limitation commercial purposes. These owners may contribute
to the Commons to promote the ideal of a free culture and the further production
of creative, cultural and scientific works, or to gain reputation or greater
distribution for their Work in part through the use and efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with
a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or
her Copyright and Related Rights in the Work and the meaning and intended
legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be protected
by copyright and related or neighboring rights ("Copyright and Related Rights").
Copyright and Related Rights include, but are not limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work, subject
to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal protection
of databases, and under any national implementation thereof, including any
amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time extensions),
(iii) in any current or future medium and for any number of copies, and (iv)
for any purpose whatsoever, including without limitation commercial, advertising
or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the
benefit of each member of the public at large and to the detriment of Affirmer's
heirs and successors, fully intending that such Waiver shall not be subject
to revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account Affirmer's
express Statement of Purpose. In addition, to the extent the Waiver is so
judged Affirmer hereby grants to each affected person a royalty-free, non
transferable, non sublicensable, non exclusive, irrevocable and unconditional
license to exercise Affirmer's Copyright and Related Rights in the Work (i)
in all territories worldwide, (ii) for the maximum duration provided by applicable
law or treaty (including future time extensions), (iii) in any current or
future medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional purposes
(the "License"). The License shall be deemed effective as of the date CC0
was applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder of
the License, and in such case Affirmer hereby affirms that he or she will
not (i) exercise any of his or her remaining Copyright and Related Rights
in the Work or (ii) assert any associated claims and causes of action with
respect to the Work, in either case contrary to Affirmer's express Statement
of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered,
licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or other
defects, accuracy, or the present or absence of errors, whether or not discoverable,
all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims
responsibility for obtaining any necessary consents, permissions or other
rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a party
to this document and has no duty or obligation with respect to this CC0 or
use of the Work.

View File

@@ -1,3 +1,7 @@
<!-- SPDX-FileCopyrightText: no
SPDX-License-Identifier: CC0-1.0
-->
# Calamares Branding and Module Examples # Calamares Branding and Module Examples
> A *branding component* in Calamares is a description of the > A *branding component* in Calamares is a description of the
@@ -36,7 +40,10 @@ and documentation for the framework that Calamares ships with.
(probably moreso than the default slideshow). (probably moreso than the default slideshow).
- [`kaos_branding/`](branding/kaos_branding/branding.desc) - [`kaos_branding/`](branding/kaos_branding/branding.desc)
is a copy of the KaOS branding component, which is a copy of the KaOS branding component, which
has translations and a bunch of fancy graphics. has translations and a bunch of fancy graphics for the
slideshow. Plus it includes examples of using different
QML options for a vertical navigation bar and horizontal
sidebar.
- [`samegame/` ](branding/default/branding.desc) - [`samegame/` ](branding/default/branding.desc)
is a copy of the Qt Company "Same Game" QML demo. It is a copy of the Qt Company "Same Game" QML demo. It
shows that **any** QML can be used for branding purposes. shows that **any** QML can be used for branding purposes.
@@ -207,3 +214,18 @@ usually called `main.py` which defines a `run()` function.
The API is loosely documented in the The API is loosely documented in the
[developer guide](https://github.com/calamares/calamares/wiki/Develop-Guide). [developer guide](https://github.com/calamares/calamares/wiki/Develop-Guide).
# Join the Conversation
GitHub Issues are **one** place for discussing Calamares (and Calamares Extensions)
if there are concrete
problems or a new feature to discuss.
Issues are not a help channel.
Visit Matrix for help with configuration or compilation.
Regular Calamares development chit-chat happens in a [Matrix](https://matrix.org/)
room, `#calamares:kde.org`. Responsiveness is best during the day
in Europe, but feel free to idle.
Matrix is persistent, and we'll see your message eventually.
* [![Join us on Matrix](https://img.shields.io/badge/Matrix-%23calamares:kde.org-blue)](https://webchat.kde.org/#/room/%23calamares:kde.org)

View File

@@ -0,0 +1,101 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2020 2022 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import QtQuick.Window 2.3
ApplicationWindow {
id: about
visible: true
width: 760
height: 400
title: qsTr("About Calamares")
property var appName: "Calamares"
property var appVersion: "3.3 RC"
Rectangle {
id: textArea
anchors.fill: parent
color: "#f2f2f2"
Column {
id: column
anchors.centerIn: parent
Rectangle {
width: 560
height: 250
radius: 10
border.width: 0
Text {
width: 400
height: 250
anchors.centerIn: parent
text: qsTr("<h1>%1</h1><br/>
<strong>%2<br/>
for %3</strong><br/><br/>
Copyright 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/>
Copyright 2017-2022 Adriaan de Groot &lt;groot@kde.org&gt;<br/>
Thanks to <a href='https://calamares.io/team/'>the Calamares team</a>
and the <a href=\"https://www.transifex.com/kaos/kaos/\">KaOS
translators team</a>.<br/><br/>
<a href='https://calamares.io/'>Calamares</a>
development is sponsored by <br/>
<a href='http://www.blue-systems.com/'>Blue Systems</a> -
Liberating Software." )
.arg(appName)
.arg(appVersion)
.arg(Branding.string(Branding.VersionedName))
onLinkActivated: Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
font.pointSize: 10
anchors.verticalCenterOffset: 10
anchors.horizontalCenterOffset: 40
wrapMode: Text.WordWrap
}
Image {
id: image
x: 8
y: 12
height: 100
fillMode: Image.PreserveAspectFit
source: "squid.png"
}
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
icon.name: "window-close"
text: qsTr("Close")
hoverEnabled: true
onClicked: about.close();
}
}
}

View File

@@ -1,22 +1,32 @@
--- ---
componentName: kaos componentName: kaos
welcomeStyleCalamares: false
# Should the welcome image (productWelcome, below) be scaled # Should the welcome image (productWelcome, below) be scaled
# up beyond its natural size? # up beyond its natural size?
welcomeExpandingLogo: true welcomeExpandingLogo: true
windowExpanding: normal
windowSize: 920px,630px
windowPlacement: center
sidebar: qml,bottom
navigation: qml,right
strings: strings:
productName: KaOS productName: KaOS
shortProductName: KaOS shortProductName: KaOS
version: 2018.03 version: 2022.08
shortVersion: KaOS shortVersion: KaOS
versionedName: KaOS 2018.03 versionedName: KaOS 2022.08
shortVersionedName: KaOS 2018.03 shortVersionedName: KaOS 2018.03
bootloaderEntryName: KaOS bootloaderEntryName: KaOS
productUrl: https://kaosx.us/ productUrl: https://kaosx.us/
supportUrl: https://kaosx.us/docs/ supportUrl: https://kaosx.us/docs/
knownIssuesUrl: https://kaosx.us/pages/download/#known-issues knownIssuesUrl: https://kaosx.us/pages/download/#known-issues
releaseNotesUrl: https://kaosx.us/pages/release_notes releaseNotesUrl: https://kaosx.us/pages/release_notes
donateUrl: https://kaosx.us/about/donors
images: images:
productLogo: "kaos.png" productLogo: "kaos.png"
@@ -27,6 +37,7 @@ slideshow: "show.qml"
slideshowAPI: 1 slideshowAPI: 1
style: style:
sidebarBackground: "#bdc3c7" SidebarBackground: "#bdc3c7"
sidebarText: "#1F1F1F" SidebarText: "#1F1F1F"
sidebarTextSelect: "#3498DB" SidebarTextCurrent: "#3498DB"
SidebarBackgroundCurrent: "#eff0f1"

View File

@@ -0,0 +1,224 @@
/* Sample of QML navigation.
SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
SPDX-FileCopyrightText: 2021 - 2022 Anke Boersma <demm@kaosx.us>
SPDX-License-Identifier: GPL-3.0-or-later
This navigation panel is for a "vertical" layout, with
mouse areas for next and previous and it includes the logo
plus About & Debug buttons.
*/
import io.calamares.ui 1.0
import io.calamares.core 1.0
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
Rectangle {
id: navigationBar;
color: Branding.styleString( Branding.SidebarBackground );
height: parent.height;
width:64;
ColumnLayout {
id: buttonBar
anchors.fill: parent;
spacing: 1
Image {
Layout.topMargin: 1;
Layout.bottomMargin:parent.height / 7;
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
id: logo;
width: 62;
height: width; // square
source: "file:/" + Branding.imagePath(Branding.ProductLogo);
sourceSize.width: width;
sourceSize.height: height;
}
Rectangle {
id: backArea
Layout.fillWidth: true;
Layout.preferredHeight: parent.height / 7;
color: mouseBack.containsMouse ? "#e6e9ea" : "#d9dcde";
enabled: ViewManager.backEnabled;
visible: ViewManager.backAndNextVisible;
MouseArea {
id: mouseBack
anchors.fill: parent;
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
Text {
anchors.centerIn: parent
text: qsTr("Back")
color: Branding.styleString( !backArea.enabled ? Branding.SidebarBackground : (mouseBack.containsMouse ? Branding.SidebarTextCurrent : Branding.SidebarText ));
font.pointSize : 8
}
Image {
source: "pan-start-symbolic.svg"
anchors.centerIn: parent
anchors.verticalCenterOffset : 18
fillMode: Image.PreserveAspectFit
height: 32
opacity: backArea.enabled ? 1 : 0.2
}
onClicked: { ViewManager.back(); }
}
}
Rectangle {
id: nextArea
Layout.preferredHeight: parent.height / 7;
Layout.fillWidth: true
color: mouseNext.containsMouse ? "#f4f5f6" : "#e6e9ea";
enabled: ViewManager.nextEnabled;
visible: ViewManager.backAndNextVisible;
MouseArea {
id: mouseNext
anchors.fill: parent;
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
Text {
anchors.centerIn: parent
text: qsTr("Next")
color: Branding.styleString( !nextArea.enabled ? Branding.SidebarBackground : (mouseNext.containsMouse ? Branding.SidebarTextCurrent : Branding.SidebarText ));
font.pointSize : 8
}
Image {
source: "pan-end-symbolic.svg"
anchors.centerIn: parent
anchors.verticalCenterOffset : 18
fillMode: Image.PreserveAspectFit
height: 32
opacity: nextArea.enabled ? 1 : 0.2
}
onClicked: { ViewManager.next(); }
}
}
Rectangle {
id: cancelArea
height: parent.height / 7;
Layout.fillWidth: true
color: mouseCancel.containsMouse ? "#e6e9ea" : "#d9dcde";
/*
* The ViewManager has settings -- user-controlled via the
* branding component, and party based on program state --
* whether the quit button should be enabled and visible.
*
* QML navigation *should* follow this pattern, but can also
* add other qualifications. For instance, you may have a
* "finished" module that handles quit in its own way, and
* want to hide the quit button then. The ViewManager has a
* current step and a total count, so compare them:
*
* visible: ViewManager.quitVisible && ( ViewManager.currentStepIndex < ViewManager.rowCount()-1);
*/
enabled: ViewManager.quitEnabled;
visible: ViewManager.quitVisible && ( ViewManager.currentStepIndex < ViewManager.rowCount()-1);
ToolTip {
width: 59
visible: mouseCancel.containsMouse
timeout: 5000
delay: 1000
text: ViewManager.quitTooltip;
}
MouseArea {
id: mouseCancel
anchors.fill: parent;
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
Text {
anchors.centerIn: parent
text: qsTr("Cancel")
color: Branding.styleString( !cancelArea.enabled ? Branding.SidebarBackground : (mouseCancel.containsMouse ? Branding.SidebarTextCurrent : Branding.SidebarText ));
font.pointSize : 8
}
Image {
source: "draw-rectangle.svg"
anchors.centerIn: parent
anchors.verticalCenterOffset : 18
fillMode: Image.PreserveAspectFit
height: 9
opacity: cancelArea.enabled ? 1 : 0.2
}
onClicked: { ViewManager.quit(); }
}
}
Item {
Layout.fillHeight: true;
}
Rectangle {
id: debugArea
Layout.fillWidth: true;
height: 35
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
color: Branding.styleString( mouseAreaDebug.containsMouse ? Branding.SidebarBackgroundCurrent : Branding.SidebarBackground);
visible: debug.enabled
MouseArea {
id: mouseAreaDebug
anchors.fill: parent;
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
Text {
anchors.centerIn: parent
text: qsTr("Debug")
color: Branding.styleString( mouseAreaDebug.containsMouse ? Branding.SidebarTextCurrent : Branding.SidebarBackground );
font.pointSize : 8
}
onClicked: debug.toggle()
}
}
Rectangle {
id: aboutArea
Layout.fillWidth: true;
height: 35
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
color: Branding.styleString( mouseAreaAbout.containsMouse ? Branding.SidebarBackgroundCurrent : Branding.SidebarBackground);
visible: true
MouseArea {
id: mouseAreaAbout
anchors.fill: parent;
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
Text {
anchors.centerIn: parent
text: qsTr("About")
ToolTip {
visible: mouseAreaAbout.containsMouse
delay: 1000
text: qsTr("Info about Calamares")
}
color: Branding.styleString( mouseAreaAbout.containsMouse ? Branding.SidebarTextCurrent : Branding.SidebarBackgroundCurrent );
font.pointSize : 8
}
property variant window;
onClicked: {
var component = Qt.createComponent("about.qml");
window = component.createObject();
window.show();
}
}
}
}
}

View File

@@ -0,0 +1,72 @@
/* Sample of QML progress tree.
SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
SPDX-FileCopyrightText: 2021 - 2022 Anke Boersma <demm@kaosx.us>
SPDX-License-Identifier: GPL-3.0-or-later
The progress tree (actually a list) is "horizontal" in this example,
with the steps going to the right.
*/
import io.calamares.ui 1.0
import io.calamares.core 1.0
import QtQuick 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15
Rectangle {
id: sideBar;
color: Branding.styleString( Branding.SidebarBackground );
height: 48;
width: parent.width
RowLayout {
anchors.fill: parent;
spacing: 2;
Item {
Layout.fillHeight: true;
}
Repeater {
model: ViewManager
Rectangle {
Layout.leftMargin: 0;
Layout.fillWidth: true;
Layout.alignment: Qt.AlignTop;
height: 42;
radius: 0;
color: Branding.styleString( index == ViewManager.currentStepIndex ? Branding.SidebarBackgroundCurrent : Branding.SidebarBackground );
Text {
anchors.verticalCenter: parent.verticalCenter;
anchors.horizontalCenter: parent.horizontalCenter
x: parent.x + 12;
color: Branding.styleString( index == ViewManager.currentStepIndex ? Branding.SidebarTextCurrent : Branding.SidebarText );
text: display;
font.pointSize : index == ViewManager.currentStepIndex ? 10 : 9
}
Rectangle {
height: 2
width: 800
anchors.bottom: parent.bottom;
border.color: Branding.styleString(ViewManager.currentStepIndex === index ? Branding.SidebarTextCurrent : (ViewManager.currentStepIndex >= index ? Branding.SidebarTextCurrent : Branding.SidebarBackgroundCurrent))
border.width: 3
Image {
source: "pan-up-symbolic.svg"
id: image
anchors.verticalCenter: parent.verticalCenter;
anchors.verticalCenterOffset : -3
x: parent.x + 35;
fillMode: Image.PreserveAspectFit
height: 32
visible: index == ViewManager.currentStepIndex ? true : false
}
}
}
}
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><path d="m2 1034.36v16h16v-16z" fill="#566060" transform="translate(1-1031.36)"/></svg>

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
foreach( lang ast ca cs de en es es_AR fr hu id_ID nl_NL pl pt_BR pt_PT ro_RO ru sr_RS tr_TR zh_CN ) foreach( lang ast ca cs de en es es_AR fr hu id_ID nl_NL pl pt_BR pt_PT ro_RO ru sr_RS tr_TR zh_CN )
list( APPEND TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/calamares-${COMPONENT_NAME}_${lang}.ts" ) list( APPEND TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/calamares-${COMPONENT_NAME}_${lang}.ts" )
endforeach() endforeach()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -0,0 +1,15 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<svg height="16" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" version="1.1" width="16" xmlns="http://www.w3.org/2000/svg" enable-background="new">
<metadata id="metadata90"/>
<defs id="defs7386">
<linearGradient id="linearGradient5606" osb:paint="solid">
<stop id="stop5608"/>
</linearGradient>
<filter inkscape:collect="always" color-interpolation-filters="sRGB" id="filter7554">
<feBlend inkscape:collect="always" id="feBlend7556" in2="BackgroundImage" mode="darken"/>
</filter>
</defs>
<g inkscape:groupmode="layer" id="layer12" inkscape:label="actions" transform="translate(-445.0002,-129)">
<path inkscape:connector-curvature="0" d="m 451.0002,142 5,-5 -5,-5 z" id="path6412" sodipodi:nodetypes="cccc" fill="#555555"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 896 B

View File

@@ -0,0 +1,15 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<svg height="16" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" version="1.1" width="16" xmlns="http://www.w3.org/2000/svg" enable-background="new">
<metadata id="metadata90"/>
<defs id="defs7386">
<linearGradient id="linearGradient5606" osb:paint="solid">
<stop id="stop5608"/>
</linearGradient>
<filter inkscape:collect="always" color-interpolation-filters="sRGB" id="filter7554">
<feBlend inkscape:collect="always" id="feBlend7556" in2="BackgroundImage" mode="darken"/>
</filter>
</defs>
<g inkscape:groupmode="layer" id="layer12" inkscape:label="actions" transform="translate(-425.0002,-129)">
<path inkscape:connector-curvature="0" d="m 435.0002,142 -5,-5 5,-5 z" id="path6400-8" sodipodi:nodetypes="cccc" fill="#555555"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 898 B

View File

@@ -0,0 +1,15 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<svg height="16" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" version="1.1" width="16" xmlns="http://www.w3.org/2000/svg" enable-background="new">
<metadata id="metadata90"/>
<defs id="defs7386">
<linearGradient id="linearGradient5606" osb:paint="solid">
<stop id="stop5608"/>
</linearGradient>
<filter inkscape:collect="always" color-interpolation-filters="sRGB" id="filter7554">
<feBlend inkscape:collect="always" id="feBlend7556" in2="BackgroundImage" mode="darken"/>
</filter>
</defs>
<g inkscape:groupmode="layer" id="layer12" inkscape:label="actions" transform="translate(-465.0002,-129.00001)">
<path inkscape:connector-curvature="0" d="m 478.0002,139 -5,-5 -5,5 z" id="path6418" sodipodi:nodetypes="cccc" fill="#3498DB"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 902 B

View File

@@ -1,6 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> === /* === This file is part of Calamares - <https://github.com/calamares> ===
* *
* Copyright 2015, Teo Mrnjavac <teo@kde.org> * Copyright 2015, Teo Mrnjavac <teo@kde.org>
* Copyright 2015-2018, Anke Boersma <demm@kaosx.us>
* *
* Calamares is free software: you can redistribute it and/or modify * Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -24,13 +25,12 @@ Presentation
id: presentation id: presentation
Timer { Timer {
id: advanceTimer
interval: 5000 interval: 5000
running: false running: false
repeat: true repeat: true
onTriggered: presentation.goToNextSlide() onTriggered: presentation.goToNextSlide()
} }
Slide { Slide {
anchors.fill: parent anchors.fill: parent
@@ -38,7 +38,7 @@ Presentation
id: background id: background
source: "1.svg" source: "1.svg"
anchors.fill: parent anchors.fill: parent
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: 0 anchors.verticalCenterOffset: 0
@@ -96,15 +96,15 @@ Presentation
anchors.horizontalCenterOffset: -100 anchors.horizontalCenterOffset: -100
font.pixelSize: parent.width *.015 font.pixelSize: parent.width *.015
color: 'white' color: 'white'
text: qsTr("The default Office Suite is Calligra.<br/>"+ text: qsTr("The default Office Suite is LibreOffice.<br/>"+
"LibreOffice is available in the repositories. <br/>") "Calligra is available in the repositories. <br/>")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: 450 width: 450
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
} }
} }
} }
Slide { Slide {
anchors.fill: parent anchors.fill: parent
@@ -120,7 +120,7 @@ Presentation
font.pixelSize: parent.width *.015 font.pixelSize: parent.width *.015
color: 'white' color: 'white'
text: qsTr("Qt/KDE specific internet applications include the <br/>"+ text: qsTr("Qt/KDE specific internet applications include the <br/>"+
"Qupzilla web-browser and kde-telepathy for <br/>"+ "Falkon web-browser and kde-telepathy for <br/>"+
"chat and Instant Messaging. <br/>") "chat and Instant Messaging. <br/>")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: 450 width: 450
@@ -128,7 +128,7 @@ Presentation
} }
} }
} }
Slide { Slide {
anchors.fill: parent anchors.fill: parent
@@ -150,7 +150,7 @@ Presentation
} }
} }
} }
Slide { Slide {
anchors.fill: parent anchors.fill: parent
@@ -172,6 +172,4 @@ Presentation
} }
} }
} }
Component.onCompleted: advanceTimer.running = true
} }

View File

@@ -10,7 +10,7 @@
# NOTE: this is largely a copy of the release script for Calamares, # NOTE: this is largely a copy of the release script for Calamares,
# with not-applicable parts (such as translation-freeze) either # with not-applicable parts (such as translation-freeze) either
# commented-out, or skipped with if(false). # commented-out, or skipped with if(false).
# NOTE: this script contains Linuxisms (in particular, expects GNU mktemp(1)) # NOTE: this script may contain Linuxisms
# #
# This attempts to perform the different steps of the RELEASE.md # This attempts to perform the different steps of the RELEASE.md
# document automatically. It's not tested on other machines or # document automatically. It's not tested on other machines or
@@ -93,7 +93,7 @@ fi
### Setup ### Setup
# #
# #
BUILDDIR=$(mktemp -d --suffix=-build --tmpdir=.) BUILDDIR=$(mktemp -d -p . -t build.XXXXX)
### Build with default compiler ### Build with default compiler
# #
@@ -140,30 +140,34 @@ fi
### Get version number for this release ### Get version number for this release
# #
# #
V=$( cd "$BUILDDIR" && make show-version | grep ^CALAMARES_VERSION | sed s/^[A-Z_]*=// ) V=$( cmake -DVERSION_STYLE=short -P CMakeLists.txt 2>&1 )
test -n "$V" || { echo "Could not obtain version in $BUILDDIR ." ; exit 1 ; } test -n "$V" || { echo "Could not obtain version in $BUILDDIR ." ; exit 1 ; }
### Create signed tag ### Create signed tag
# #
# This is the signing key ID associated with the GitHub account adriaandegroot, # This is the signing key ID associated with the GitHub account adriaandegroot,
# which is used to create all "verified" tags in the Calamares repo. # which is used to create all "verified" tags in the Calamares repo.
KEY_ID="CFDDC96F12B1915C" #
KEY_ID="328D742D8807A435"
git tag -u "$KEY_ID" -m "Release v$V" "v$V" || { echo "Could not sign tag v$V." ; exit 1 ; } git tag -u "$KEY_ID" -m "Release v$V" "v$V" || { echo "Could not sign tag v$V." ; exit 1 ; }
### Create the tarball ### Create the tarball
# #
# Create the tarball, compute SHA256 for later reporting, and
# sign the tarball so the signature can be uploaded separately.
# #
TAR_V="$TARBALL_PREFIX-$V" TAR_V="$TARBALL_PREFIX-$V"
TAR_FILE="$TAR_V.tar.gz" TAR_FILE="$TAR_V.tar.gz"
git archive -o "$TAR_FILE" --prefix "$TAR_V/" "v$V" || { echo "Could not create tarball." ; exit 1 ; } git archive -o "$TAR_FILE" --prefix "$TAR_V/" "v$V" || { echo "Could not create tarball." ; exit 1 ; }
test -f "$TAR_FILE" || { echo "Tarball was not created." ; exit 1 ; } test -f "$TAR_FILE" || { echo "Tarball was not created." ; exit 1 ; }
SHA256=$(sha256sum "$TAR_FILE" | cut -d" " -f1) SHA256=$(sha256sum "$TAR_FILE" | cut -d" " -f1)
gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball
### Build the tarball ### Build the tarball
# #
# #
D=$(date +%Y%m%d-%H%M%S) D=$(date +%Y%m%d-%H%M%S)
TMPDIR=$(mktemp -d --suffix="-calamares-$D") TMPDIR=$(mktemp -d -p . -t calamares.XXXXX)
test -d "$TMPDIR" || { echo "Could not create tarball-build directory." ; exit 1 ; } test -d "$TMPDIR" || { echo "Could not create tarball-build directory." ; exit 1 ; }
tar xzf "$TAR_FILE" -C "$TMPDIR" || { echo "Could not unpack tarball." ; exit 1 ; } tar xzf "$TAR_FILE" -C "$TMPDIR" || { echo "Could not unpack tarball." ; exit 1 ; }
test -d "$TMPDIR/$TAR_V" || { echo "Tarball did not contain source directory." ; exit 1 ; } test -d "$TMPDIR/$TAR_V" || { echo "Tarball did not contain source directory." ; exit 1 ; }
@@ -183,7 +187,6 @@ rm -rf "$TMPDIR" # From tarball
cat <<EOF cat <<EOF
# Next steps for this release: # Next steps for this release:
git push origin v$V git push origin v$V
gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball
# Upload tarball $TAR_FILE and the signature $TAR_FILE.asc # Upload tarball $TAR_FILE and the signature $TAR_FILE.asc
# Announce via https://github.com/calamares/$PROJECT_NAME/releases/new # Announce via https://github.com/calamares/$PROJECT_NAME/releases/new
# SHA256: $SHA256 # SHA256: $SHA256

View File

@@ -5,12 +5,7 @@
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
# #
# Calls astyle with settings matching Calamares coding style # Calls astyle with settings matching Calamares coding style
# Requires astyle >= 2.04 and clang-format-7 -8 or -9 # Requires astyle >= 2.04 and clang-format-8 or later
#
# Clang-format-10 is **not** supported, since it changes a default
# that re-introduces a space into empty function bodies; this
# can be turned off with a style setting, but that breaks
# older format versions which don't recognize the setting.
# #
# You can pass in directory names, in which case the files # You can pass in directory names, in which case the files
# in that directory (NOT below it) are processed. # in that directory (NOT below it) are processed.
@@ -20,9 +15,16 @@ LC_ALL=C
LC_NUMERIC=C LC_NUMERIC=C
export LANG LC_ALL LC_NUMERIC export LANG LC_ALL LC_NUMERIC
BASEDIR=$(dirname $0)
TOPDIR=$( cd $BASEDIR/.. && pwd -P )
test -d "$BASEDIR" || { echo "! Could not determine base for $0" ; exit 1 ; }
test -d "$TOPDIR" || { echo "! Cound not determine top-level source dir" ; exit 1 ; }
test -f "$TOPDIR/.clang-format.base" || { echo "! No .clang-format support files in $TOPDIR" ; exit 1 ; }
AS=$( which astyle ) AS=$( which astyle )
CF_VERSIONS="clang-format-7 clang-format-8 clang-format70 clang-format80 clang-format90 clang-format-9.0.1 clang-format" # Allow specifying CF_VERSIONS outside already
CF_VERSIONS="$CF_VERSIONS clang-format-8 clang-format80 clang-format90 clang-format-9.0.1 clang-format"
for _cf in $CF_VERSIONS for _cf in $CF_VERSIONS
do do
# Not an error if this particular clang-format isn't found # Not an error if this particular clang-format isn't found
@@ -35,14 +37,39 @@ test -n "$CF" || { echo "! No clang-format ($CF_VERSIONS) found in PATH"; exit 1
test -x "$AS" || { echo "! $AS is not executable."; exit 1 ; } test -x "$AS" || { echo "! $AS is not executable."; exit 1 ; }
test -x "$CF" || { echo "! $CF is not executable."; exit 1 ; } test -x "$CF" || { echo "! $CF is not executable."; exit 1 ; }
unmangle_clang_format="" ### CLANG-FORMAT-WRANGLING
if expr `"$CF" --version | tr -dc '[^.0-9]' | cut -d . -f 1` '<' 10 > /dev/null ; then #
: # Version 7 and earlier doesn't understand all the options we would like
else # Version 8 is ok
unmangle_clang_format=$( dirname $0 )/../.clang-format # Version 9 is ok
echo "SpaceInEmptyBlock: false" >> "$unmangle_clang_format" # Later versions change some defaults so need extra wrangling.
fi # .. there are extra files that are appended to the settings, per
# .. clang-format version.
format_version=`"$CF" --version | tr -dc '[^.0-9]' | cut -d . -f 1`
case "$format_version" in
[0-7] )
echo "! Clang-format version 8+ required"
exit 1
;;
[89] )
:
;;
* )
echo "! Clang-format version '$format_version' unsupported."
exit 1
;;
esac
_fmt="$TOPDIR/.clang-format"
cp "$_fmt.base" "$_fmt"
for f in "$extra_settings" ; do
test -f "$_fmt.$f" && cat "$_fmt.$f" >> "$_fmt"
done
### FILE PROCESSING
#
#
set -e set -e
any_dirs=no any_dirs=no
@@ -54,7 +81,7 @@ done
style_some() style_some()
{ {
if test -n "$*" ; then if test -n "$*" ; then
$AS --options=$(dirname $0)/astylerc --quiet "$@" $AS --options=$BASEDIR/astylerc --quiet "$@"
$CF -i -style=file "$@" $CF -i -style=file "$@"
fi fi
} }
@@ -72,6 +99,7 @@ else
style_some "$@" style_some "$@"
fi fi
if test -n "$unmangle_clang_format" ; then ### CLANG-FORMAT-WRANGLING
sed -i.bak '/^SpaceInEmptyBlock/d' "$unmangle_clang_format" #
fi # Restore the original .clang-format
cp "$_fmt.base" "$_fmt"

View File

@@ -1,11 +0,0 @@
# The FileKeeper plugin preserves files from the live system
# to the target system. It is particularly suited for copying
# the log file(s) for post-installation or post-mortem examination.
calamares_add_plugin( filekeeper
TYPE job
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
FileKeeper.cpp
SHARED_LIB
)

View File

@@ -1,58 +0,0 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "FileKeeper.h"
#include <QDateTime>
#include <QProcess>
#include <QThread>
#include <GlobalStorage.h>
#include <JobQueue.h>
#include <utils/Logger.h>
FileKeeperJob::FileKeeperJob( QObject* parent )
: Calamares::CppJob( parent )
{
}
FileKeeperJob::~FileKeeperJob() {}
QString
FileKeeperJob::prettyName() const
{
return tr( "File keeper Job" );
}
Calamares::JobResult
FileKeeperJob::exec()
{
return Calamares::JobResult::ok();
}
void
FileKeeperJob::setConfigurationMap( const QVariantMap& configurationMap )
{
Q_UNUSED( configurationMap );
}
CALAMARES_PLUGIN_FACTORY_DEFINITION( FileKeeperJobFactory, registerPlugin< FileKeeperJob >(); )

View File

@@ -1,46 +0,0 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef FILEKEEPER_H
#define FILEKEEPER_H
#include <CppJob.h>
#include <DllMacro.h>
#include <utils/PluginFactory.h>
#include <QObject>
#include <QVariantMap>
class PLUGINDLLEXPORT FileKeeperJob : public Calamares::CppJob
{
Q_OBJECT
public:
explicit FileKeeperJob( QObject* parent = nullptr );
virtual ~FileKeeperJob() override;
QString prettyName() const override;
Calamares::JobResult exec() override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( FileKeeperJobFactory )
#endif

View File

@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
if( NOT Calamares_WITH_QML ) if( NOT Calamares_WITH_QML )
calamares_skip_module( "freebsddisk (QML is not supported in this build)" ) calamares_skip_module( "freebsddisk (QML is not supported in this build)" )
return() return()

View File

@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The *freebsddisk* module can be used to pick a disk # The *freebsddisk* module can be used to pick a disk
# as an installer step. This module supports ZFSroot # as an installer step. This module supports ZFSroot
# on one whole disk, and UFSroot on one whole disk. # on one whole disk, and UFSroot on one whole disk.

View File

@@ -5,6 +5,7 @@
#include "UsersJob.h" #include "UsersJob.h"
#include "ViewManager.h" #include "ViewManager.h"
#include "utils/Logger.h"
#include "utils/Variant.h" #include "utils/Variant.h"
#include <QVariant> #include <QVariant>
@@ -19,11 +20,23 @@ Config::setConfigurationMap( const QVariantMap& cfgMap )
{ {
using namespace CalamaresUtils; using namespace CalamaresUtils;
if ( getBool( cfgMap, "bogus", false ) )
{
cWarning() << "Configuration key \"bogus\" is still set for *mobile*";
}
m_osName = getString( cfgMap, "osName", "(unknown)" ); m_osName = getString( cfgMap, "osName", "(unknown)" );
m_arch = getString( cfgMap, "arch", "(unknown)" ); m_arch = getString( cfgMap, "arch", "(unknown)" );
m_device = getString( cfgMap, "device", "(unknown)" ); m_device = getString( cfgMap, "device", "(unknown)" );
m_userInterface = getString( cfgMap, "userInterface", "(unknown)" ); m_userInterface = getString( cfgMap, "userInterface", "(unknown)" );
m_version = getString( cfgMap, "version", "(unknown)" ); m_version = getString( cfgMap, "version", "(unknown)" );
m_reservedUsernames = getStringList( cfgMap, "reservedUsernames", QStringList { "adm", "at ", "bin", "colord",
"cron", "cyrus", "daemon", "ftp", "games", "geoclue", "guest", "halt", "lightdm", "lp", "mail", "man",
"messagebus", "news", "nobody", "ntp", "operator", "polkitd", "postmaster", "pulse", "root", "shutdown",
"smmsp", "squid", "sshd", "sync", "uucp", "vpopmail", "xfs" } );
// ensure m_cmdUsermod matches m_username
m_username = getString( cfgMap, "username", "user" ); m_username = getString( cfgMap, "username", "user" );
m_userPasswordNumeric = getBool( cfgMap, "userPasswordNumeric", true ); m_userPasswordNumeric = getBool( cfgMap, "userPasswordNumeric", true );
@@ -48,6 +61,8 @@ Config::setConfigurationMap( const QVariantMap& cfgMap )
m_cmdInternalStoragePrepare = getString( cfgMap, "cmdInternalStoragePrepare", "ondev-internal-storage-prepare" ); m_cmdInternalStoragePrepare = getString( cfgMap, "cmdInternalStoragePrepare", "ondev-internal-storage-prepare" );
m_cmdPasswd = getString( cfgMap, "cmdPasswd", "passwd" ); m_cmdPasswd = getString( cfgMap, "cmdPasswd", "passwd" );
m_cmdUsermod = getString( cfgMap, "cmdUsermod", "xargs -I{} -n1 usermod -m -d /home/{} -l {} -c {} user");
m_cmdSshdEnable = getString( cfgMap, "cmdSshdEnable", "systemctl enable sshd.service" ); m_cmdSshdEnable = getString( cfgMap, "cmdSshdEnable", "systemctl enable sshd.service" );
m_cmdSshdDisable = getString( cfgMap, "cmdSshdDisable", "systemctl disable sshd.service" ); m_cmdSshdDisable = getString( cfgMap, "cmdSshdDisable", "systemctl disable sshd.service" );
m_cmdSshdUseradd = getString( cfgMap, "cmdSshdUseradd", "useradd -G wheel -m" ); m_cmdSshdUseradd = getString( cfgMap, "cmdSshdUseradd", "useradd -G wheel -m" );
@@ -62,6 +77,7 @@ Config::createJobs()
/* Put users job in queue (should run after unpackfs) */ /* Put users job in queue (should run after unpackfs) */
Calamares::Job* j = new UsersJob( m_featureSshd, Calamares::Job* j = new UsersJob( m_featureSshd,
m_cmdPasswd, m_cmdPasswd,
m_cmdUsermod,
cmdSshd, cmdSshd,
m_cmdSshdUseradd, m_cmdSshdUseradd,
m_isSshEnabled, m_isSshEnabled,
@@ -126,6 +142,13 @@ Config::runPartitionJobThenLeave( bool b )
} }
} }
void
Config::setUsername( const QString& username )
{
m_username = username;
emit usernameChanged( m_username );
}
void void
Config::setUserPassword( const QString& userPassword ) Config::setUserPassword( const QString& userPassword )
{ {

View File

@@ -20,8 +20,11 @@ class Config : public QObject
Q_PROPERTY( QString userInterface READ userInterface CONSTANT FINAL ) Q_PROPERTY( QString userInterface READ userInterface CONSTANT FINAL )
Q_PROPERTY( QString version READ version CONSTANT FINAL ) Q_PROPERTY( QString version READ version CONSTANT FINAL )
/* reserved usernames (user_pass, ssh_credentials )*/
Q_PROPERTY( QStringList reservedUsernames READ reservedUsernames CONSTANT FINAL )
/* default user */ /* default user */
Q_PROPERTY( QString username READ username CONSTANT FINAL ) Q_PROPERTY( QString username READ username WRITE setUsername NOTIFY usernameChanged )
Q_PROPERTY( QString userPassword READ userPassword WRITE setUserPassword NOTIFY userPasswordChanged ) Q_PROPERTY( QString userPassword READ userPassword WRITE setUserPassword NOTIFY userPasswordChanged )
Q_PROPERTY( bool userPasswordNumeric READ userPasswordNumeric CONSTANT FINAL ) Q_PROPERTY( bool userPasswordNumeric READ userPasswordNumeric CONSTANT FINAL )
@@ -72,13 +75,17 @@ public:
QString userInterface() const { return m_userInterface; } QString userInterface() const { return m_userInterface; }
QString version() const { return m_version; } QString version() const { return m_version; }
/* default user */ /* reserved usernames (user_pass, ssh_credentials) */
QStringList reservedUsernames() const { return m_reservedUsernames; };
/* user */
QString username() const { return m_username; } QString username() const { return m_username; }
QString userPassword() const { return m_userPassword; } QString userPassword() const { return m_userPassword; }
void setUsername( const QString& username );
void setUserPassword( const QString& userPassword ); void setUserPassword( const QString& userPassword );
bool userPasswordNumeric() const { return m_userPasswordNumeric; } bool userPasswordNumeric() const { return m_userPasswordNumeric; }
/* ssh server + credetials */ /* ssh server + credentials */
bool featureSshd() { return m_featureSshd; } bool featureSshd() { return m_featureSshd; }
QString sshdUsername() const { return m_sshdUsername; } QString sshdUsername() const { return m_sshdUsername; }
QString sshdPassword() const { return m_sshdPassword; } QString sshdPassword() const { return m_sshdPassword; }
@@ -120,6 +127,7 @@ public:
/* users job */ /* users job */
QString cmdPasswd() const { return m_cmdPasswd; } QString cmdPasswd() const { return m_cmdPasswd; }
QString cmdUsermod() const { return m_cmdUsermod; }
QString cmdSshdEnable() const { return m_cmdSshdEnable; } QString cmdSshdEnable() const { return m_cmdSshdEnable; }
QString cmdSshdDisable() const { return m_cmdSshdDisable; } QString cmdSshdDisable() const { return m_cmdSshdDisable; }
QString cmdSshdUseradd() const { return m_cmdSshdUseradd; } QString cmdSshdUseradd() const { return m_cmdSshdUseradd; }
@@ -135,6 +143,9 @@ private:
QString m_userInterface; QString m_userInterface;
QString m_version; QString m_version;
/* reserved usernames (user_pass, ssh_credentials) */
QStringList m_reservedUsernames;
/* default user */ /* default user */
QString m_username; QString m_username;
QString m_userPassword; QString m_userPassword;
@@ -172,6 +183,7 @@ private:
/* users job */ /* users job */
QString m_cmdPasswd; QString m_cmdPasswd;
QString m_cmdUsermod;
QString m_cmdSshdEnable; QString m_cmdSshdEnable;
QString m_cmdSshdDisable; QString m_cmdSshdDisable;
QString m_cmdSshdUseradd; QString m_cmdSshdUseradd;
@@ -181,6 +193,7 @@ signals:
/* default user */ /* default user */
void userPasswordChanged( QString userPassword ); void userPasswordChanged( QString userPassword );
void usernameChanged( QString username );
/* ssh server + credentials */ /* ssh server + credentials */
void sshdUsernameChanged( QString sshdUsername ); void sshdUsernameChanged( QString sshdUsername );

View File

@@ -2,16 +2,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later */ * SPDX-License-Identifier: GPL-3.0-or-later */
#include "MobileQmlViewStep.h" #include "MobileQmlViewStep.h"
#include "Branding.h"
#include "GlobalStorage.h" #include "GlobalStorage.h"
#include "locale/TranslationsModel.h"
#include "locale/LabelModel.h" #include "modulesystem/ModuleManager.h"
#include "utils/Dirs.h" #include "utils/Dirs.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "utils/Variant.h" #include "utils/Variant.h"
#include "Branding.h"
#include "modulesystem/ModuleManager.h"
#include <QProcess> #include <QProcess>
CALAMARES_PLUGIN_FACTORY_DEFINITION( MobileQmlViewStepFactory, registerPlugin< MobileQmlViewStep >(); ) CALAMARES_PLUGIN_FACTORY_DEFINITION( MobileQmlViewStepFactory, registerPlugin< MobileQmlViewStep >(); )

View File

@@ -118,7 +118,7 @@ PartitionJob::exec()
const QString pathRoot = "/"; const QString pathRoot = "/";
ProcessResult res ProcessResult res
= System::runCommand( System::RunLocation::RunInHost, args, pathRoot, stdInput, chrono::seconds( 120 ) ); = System::runCommand( System::RunLocation::RunInHost, args, pathRoot, stdInput, chrono::seconds( 600 ) );
if ( res.getExitCode() ) if ( res.getExitCode() )
{ {
return JobResult::error( "Command failed:<br><br>" return JobResult::error( "Command failed:<br><br>"

View File

@@ -14,6 +14,7 @@
UsersJob::UsersJob( bool featureSshd, UsersJob::UsersJob( bool featureSshd,
const QString& cmdPasswd, const QString& cmdPasswd,
const QString& cmdUsermod,
const QString& cmdSshd, const QString& cmdSshd,
const QString& cmdSshdUseradd, const QString& cmdSshdUseradd,
bool isSshEnabled, bool isSshEnabled,
@@ -24,6 +25,7 @@ UsersJob::UsersJob( bool featureSshd,
: Calamares::Job() : Calamares::Job()
, m_featureSshd( featureSshd ) , m_featureSshd( featureSshd )
, m_cmdPasswd( cmdPasswd ) , m_cmdPasswd( cmdPasswd )
, m_cmdUsermod( cmdUsermod )
, m_cmdSshd( cmdSshd ) , m_cmdSshd( cmdSshd )
, m_cmdSshdUseradd( cmdSshdUseradd ) , m_cmdSshdUseradd( cmdSshdUseradd )
, m_isSshEnabled( isSshEnabled ) , m_isSshEnabled( isSshEnabled )
@@ -49,9 +51,11 @@ UsersJob::exec()
using namespace std; using namespace std;
QList< QPair< QStringList, QString > > commands = { QList< QPair< QStringList, QString > > commands = {
{ { "sh", "-c", m_cmdPasswd + " " + m_username }, m_password + "\n" + m_password + "\n" }, { { "sh", "-c", m_cmdUsermod }, m_username + "\n" }
}; };
commands.append( { { "sh", "-c", m_cmdPasswd + " " + m_username }, m_password + "\n" + m_password + "\n" } );
if ( m_featureSshd ) if ( m_featureSshd )
{ {
commands.append( { { "sh", "-c", m_cmdSshd }, QString() } ); commands.append( { { "sh", "-c", m_cmdSshd }, QString() } );

View File

@@ -10,6 +10,7 @@ class UsersJob : public Calamares::Job
public: public:
UsersJob( bool featureSshd, UsersJob( bool featureSshd,
const QString& cmdPasswd, const QString& cmdPasswd,
const QString& cmdUsermod,
const QString& cmdSshd, const QString& cmdSshd,
const QString& cmdSshdUseradd, const QString& cmdSshdUseradd,
bool isSshEnabled, bool isSshEnabled,
@@ -26,6 +27,7 @@ public:
private: private:
bool m_featureSshd; bool m_featureSshd;
QString m_cmdPasswd; QString m_cmdPasswd;
QString m_cmdUsermod;
QString m_cmdSshd; QString m_cmdSshd;
QString m_cmdSshdUseradd; QString m_cmdSshdUseradd;
bool m_isSshEnabled; bool m_isSshEnabled;

View File

@@ -22,7 +22,7 @@ Item {
id: mainText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 10
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: "To protect your data in case your device gets stolen," + text: "To protect your data in case your device gets stolen," +
@@ -33,15 +33,15 @@ Item {
" boot your device or access any data on it. Make sure that" + " boot your device or access any data on it. Make sure that" +
" you don't lose this password!" " you don't lose this password!"
width: 500 width: 200
} }
Button { Button {
id: firstButton id: firstButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Enable") text: qsTr("Enable")
onClicked: { onClicked: {
@@ -53,8 +53,8 @@ Item {
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom anchors.top: firstButton.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Disable") text: qsTr("Disable")
onClicked: { onClicked: {

View File

@@ -36,7 +36,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 50
width: 500 width: 200
} }
TextField { TextField {
@@ -49,8 +49,8 @@ Item {
text: config.fdePassword text: config.fdePassword
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 10
width: 500 width: 200
} }
Text { Text {
@@ -59,15 +59,15 @@ Item {
visible: false visible: false
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 10
width: 500 width: 200
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: errorText.bottom anchors.top: errorText.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Continue") text: qsTr("Continue")
onClicked: { onClicked: {

View File

@@ -22,21 +22,21 @@ Item {
id: mainText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 10
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: "Select the filesystem for root partition. If unsure, leave the default." text: "Select the filesystem for root partition. If unsure, leave the default."
width: 500 width: 200
} }
ComboBox { ComboBox {
id: fsTypeCB id: fsTypeCB
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 150
height: 60 height: 30
editable: false editable: false
model: config.fsList model: config.fsList
/* Save the current state on selection so it is there when the back button is pressed */ /* Save the current state on selection so it is there when the back button is pressed */
@@ -47,8 +47,8 @@ Item {
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: fsTypeCB.bottom anchors.top: fsTypeCB.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Continue") text: qsTr("Continue")
onClicked: { onClicked: {

View File

@@ -22,13 +22,13 @@ Item {
id: mainText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 10
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: (function() { text: (function() {
var ret = "Once you hit 'install', the installation will begin." + var ret = "Once you hit 'install', the installation will begin." +
" It will typically take a few minutes. Do not power off the" + " It will typically take a few minutes. Do not power off the" +
" device until it is done.<br><br>"; " device until it is done.<br>";
if (config.installFromExternalToInternal) { if (config.installFromExternalToInternal) {
ret += "<b>After the installation, your device will shutdown" + ret += "<b>After the installation, your device will shutdown" +
@@ -44,15 +44,15 @@ Item {
return ret; return ret;
}()) }())
width: 500 width: 200
} }
Button { Button {
id: firstButton id: firstButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Install") text: qsTr("Install")
onClicked: navFinish() onClicked: navFinish()

View File

@@ -22,7 +22,7 @@ Item {
id: mainText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 10
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: "The installation was started from an external storage medium." + text: "The installation was started from an external storage medium." +
@@ -32,15 +32,15 @@ Item {
"<br>" + "<br>" +
"Where would you like to install " + config.osName + "?" "Where would you like to install " + config.osName + "?"
width: 500 width: 200
} }
Button { Button {
id: firstButton id: firstButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Internal (eMMC)") text: qsTr("Internal (eMMC)")
onClicked: { onClicked: {
@@ -52,8 +52,8 @@ Item {
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom anchors.top: firstButton.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("External (SD card)") text: qsTr("External (SD card)")
onClicked: { onClicked: {

View File

@@ -22,21 +22,21 @@ Item {
id: mainText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 10
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: "Are you sure that you want to overwrite the internal storage?" + text: "Are you sure that you want to overwrite the internal storage?" +
"<br><br>" + "<br><br>" +
"<b>All existing data on the device will be lost!</b>" "<b>All existing data on the device will be lost!</b>"
width: 500 width: 200
} }
Button { Button {
id: firstButton id: firstButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Yes") text: qsTr("Yes")
onClicked: { onClicked: {
@@ -47,8 +47,8 @@ Item {
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom anchors.top: firstButton.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("No") text: qsTr("No")
onClicked: { onClicked: {

View File

@@ -1,6 +1,13 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Commented out values are defaults. # Commented out values are defaults.
# All commands are running with 'sh -c'. # All commands are running with 'sh -c'.
--- ---
# This entry exists only to keep the tests happy, remove it in
# any production configuration.
bogus: true
####### #######
### Target OS information ### Target OS information
####### #######
@@ -19,8 +26,45 @@
# version: "(unknown)" # version: "(unknown)"
## Default username (for which the password will be set) ## Default username (for which the password will be set)
## Ensure also cmdUsermod command matches the default user, so it can be changed if desired.
# username: "user" # username: "user"
## reserved usernames (for user_pass username prompt and ssh_credentials)
# reservedUsernames:
# - adm
# - at
# - bin
# - colord
# - cron
# - cyrus
# - daemon
# - ftp
# - games
# - geoclue
# - guest
# - halt
# - lightdm
# - lp
# - mail
# - man
# - messagebus
# - news
# - nobody
# - ntp
# - operator
# - polkitd
# - postmaster
# - pulse
# - root
# - shutdown
# - smmsp
# - squid
# - sshd
# - sync
# - uucp
# - vpopmail
# - xfs
####### #######
### Target device information ### Target device information
####### #######
@@ -111,6 +155,10 @@
### Commands running in the target OS (chroot) ### Commands running in the target OS (chroot)
####### #######
## Change the username for the default user
## Stdin: username with \n
# cmdUsermod: "xargs -I{} -n1 usermod -m -d /home/{} -l {} -c {} user"
## Set the password for default user and sshd user ## Set the password for default user and sshd user
## Arguments: <username> ## Arguments: <username>
## Stdin: password twice, each time with \n ## Stdin: password twice, each time with \n

View File

@@ -77,7 +77,7 @@ Page
Rectangle { Rectangle {
id: mobileNavigation id: mobileNavigation
width: parent.width width: parent.width
height: 60 height: 30
color: "#e6e4e1" color: "#e6e4e1"
Layout.fillWidth: true Layout.fillWidth: true
@@ -98,8 +98,8 @@ Page
text: "<" text: "<"
background: Rectangle { background: Rectangle {
implicitWidth: 32 implicitWidth: 10
implicitHeight: 30 implicitHeight: 7
border.color: "#c1bab5" border.color: "#c1bab5"
border.width: 1 border.width: 1
radius: 4 radius: 4
@@ -109,7 +109,7 @@ Page
onClicked: navBack() onClicked: navBack()
} }
Rectangle { Rectangle {
implicitHeight: 30 implicitHeight: 10
Layout.fillWidth: true Layout.fillWidth: true
color: "#e6e4e1" color: "#e6e4e1"
@@ -253,7 +253,7 @@ Page
return true; return true;
} }
/* Input validation: user-screens (user_pass, ssh_credentials) */ /* Input validation: user-screens (fde_pass, user_pass, ssh_credentials) */
function validatePin(userPin, userPinRepeat, errorText) { function validatePin(userPin, userPinRepeat, errorText) {
var pin = userPin.text; var pin = userPin.text;
var repeat = userPinRepeat.text; var repeat = userPinRepeat.text;
@@ -278,47 +278,12 @@ Page
return validationFailureClear(errorText); return validationFailureClear(errorText);
} }
function validateSshdUsername(username, errorText) { function validateUsername(username, errorText, extraReservedUsernames = []) {
var name = username.text; var name = username.text;
var reserved = [ /* FIXME: make configurable */ var reserved = config.reservedUsernames.concat(extraReservedUsernames);
config.username,
"adm",
"at ",
"bin",
"colord",
"cron",
"cyrus",
"daemon",
"ftp",
"games",
"geoclue",
"guest",
"halt",
"lightdm",
"lp",
"mail",
"man",
"messagebus",
"news",
"nobody",
"ntp",
"operator",
"polkitd",
"postmaster",
"pulse",
"root",
"shutdown",
"smmsp",
"squid",
"sshd",
"sync",
"uucp",
"vpopmail",
"xfs",
]
/* Validate characters */ /* Validate characters */
for (var i=0; i<name.length; i++) { for (var i = 0; i < name.length; i++) {
if (i) { if (i) {
if (!name[i].match(/^[a-z0-9_-]$/)) if (!name[i].match(/^[a-z0-9_-]$/))
return validationFailure(errorText, return validationFailure(errorText,
@@ -335,16 +300,20 @@ Page
} }
/* Validate against reserved usernames */ /* Validate against reserved usernames */
for (var i=0;i<reserved.length;i++) { for (var i = 0; i < reserved.length; i++) {
if (name == reserved[i]) if (name == reserved[i])
return validationFailure(errorText, "Username '" + return validationFailure(errorText, "Username '" +
reserved[i] + reserved[i] +
"' is reserved.") "' is reserved.");
} }
/* Passed */ /* Passed */
return validationFailureClear(errorText); return validationFailureClear(errorText);
} }
function validateSshdUsername(username, errorText) {
return validateUsername(username, errorText, [config.username]);
}
function validateSshdPassword(password, passwordRepeat, errorText) { function validateSshdPassword(password, passwordRepeat, errorText) {
var pass = password.text; var pass = password.text;
var repeat = passwordRepeat.text; var repeat = passwordRepeat.text;
@@ -352,10 +321,10 @@ Page
if (pass == "") if (pass == "")
return validationFailure(errorText); return validationFailure(errorText);
if (pass.length < 8) if (pass.length < 6)
return validationFailure(errorText, return validationFailure(errorText,
"Too short: needs at least 8" + "Too short: needs at least 6" +
" characters."); " digits/characters.");
if (repeat == "") if (repeat == "")
return validationFailure(errorText); return validationFailure(errorText);
@@ -365,8 +334,6 @@ Page
return validationFailureClear(errorText); return validationFailureClear(errorText);
} }
/* Input validation: fde_pass */
function check_chars(input) { function check_chars(input) {
for (var i = 0; i < input.length; i++) { for (var i = 0; i < input.length; i++) {
if (allowed_chars.indexOf(input[i]) == -1) if (allowed_chars.indexOf(input[i]) == -1)
@@ -407,10 +374,10 @@ Page
"\n" + "\n" +
allowed_chars_multiline()); allowed_chars_multiline());
if (pass.length < 8) if (pass.length < 6)
return validationFailure(errorText, return validationFailure(errorText,
"Too short: needs at least 8" + "Too short: needs at least 6" +
" characters."); " digits/characters.");
if (repeat == "") if (repeat == "")
return validationFailure(errorText); return validationFailure(errorText);

View File

@@ -7,7 +7,7 @@
<file>install_target.qml</file> <!-- install from external to internal? --> <file>install_target.qml</file> <!-- install from external to internal? -->
<file>install_target_confirm.qml</file> <!-- overwrite internal storage? --> <file>install_target_confirm.qml</file> <!-- overwrite internal storage? -->
<file>user_pass.qml</file> <!-- default user: password --> <file>user_pass.qml</file> <!-- default user: username, password -->
<file>ssh_confirm.qml</file> <!-- sshd: enable or not? --> <file>ssh_confirm.qml</file> <!-- sshd: enable or not? -->
<file>ssh_credentials.qml</file> <!-- sshd user: username, password --> <file>ssh_credentials.qml</file> <!-- sshd user: username, password -->
<file>fs_selection.qml</file> <!-- filesystem selection --> <file>fs_selection.qml</file> <!-- filesystem selection -->

View File

@@ -36,7 +36,7 @@ Item {
"More information:<br>" + "More information:<br>" +
"https://postmarketos.org/ssh" "https://postmarketos.org/ssh"
width: 500 width: 200
} }
Button { Button {
@@ -44,7 +44,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 40
width: 500 width: 200
text: qsTr("Enable") text: qsTr("Enable")
onClicked: { onClicked: {
@@ -57,7 +57,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom anchors.top: firstButton.bottom
anchors.topMargin: 40 anchors.topMargin: 40
width: 500 width: 200
text: qsTr("Disable") text: qsTr("Disable")
onClicked: { onClicked: {

View File

@@ -34,7 +34,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 50
width: 500 width: 200
} }
Text { Text {
@@ -45,7 +45,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 50
width: 500 width: 200
} }
TextField { TextField {
@@ -59,7 +59,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 50
width: 500 width: 200
} }
TextField { TextField {
@@ -73,7 +73,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 50
width: 500 width: 200
} }
Text { Text {
@@ -84,13 +84,13 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 50
width: 500 width: 200
} }
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: errorTextPassword.bottom anchors.top: errorTextPassword.bottom
anchors.topMargin: 40 anchors.topMargin: 40
width: 500 width: 200
text: qsTr("Continue") text: qsTr("Continue")
onClicked: { onClicked: {

View File

@@ -12,16 +12,18 @@ import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1 import QtQuick.VirtualKeyboard 2.1
Item { Item {
property var placeholder: (config.userPasswordNumeric property var passPlaceholder: (config.userPasswordNumeric
? "PIN" ? "PIN"
: "Password") : "Password")
property var hints: (config.userPasswordNumeric property var hints: (config.userPasswordNumeric
? Qt.ImhDigitsOnly ? Qt.ImhDigitsOnly
: Qt.ImhPreferLowercase) : Qt.ImhPreferLowercase)
property var validateFunc: (config.userPasswordNumeric property var validatePassFunc: (config.userPasswordNumeric
? validatePin ? validatePin
: validatePassword); : validatePassword);
property var validateNameFunc: validateUsername;
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
@@ -30,10 +32,36 @@ Item {
height: parent.height height: parent.height
Text { Text {
id: description id: usernameDescription
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 10
wrapMode: Text.WordWrap
text: (function() {
return "Set the username of your user. The default" +
" username is \"" + config.username + "\".";
}())
width: 200
}
TextField {
id: username
anchors.top: usernameDescription.bottom
placeholderText: qsTr("Username")
onTextChanged: validateNameFunc(username, errorText)
text: config.username
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: 200
}
Text {
id: userPassDescription
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: username.bottom
anchors.topMargin: 10
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: (function() { text: (function() {
@@ -48,15 +76,15 @@ Item {
} }
}()) }())
width: 500 width: 200
} }
TextField { TextField {
id: userPass id: userPass
anchors.top: description.bottom anchors.top: userPassDescription.bottom
placeholderText: qsTr(placeholder) placeholderText: qsTr(passPlaceholder)
echoMode: TextInput.Password echoMode: TextInput.Password
onTextChanged: validateFunc(userPass, userPassRepeat, errorText) onTextChanged: validatePassFunc(userPass, userPassRepeat, errorText)
text: config.userPassword text: config.userPassword
/* Let the virtual keyboard change to digits only */ /* Let the virtual keyboard change to digits only */
@@ -68,22 +96,22 @@ Item {
} }
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 10
width: 500 width: 200
} }
TextField { TextField {
id: userPassRepeat id: userPassRepeat
anchors.top: userPass.bottom anchors.top: userPass.bottom
placeholderText: qsTr(placeholder + " (repeat)") placeholderText: qsTr(passPlaceholder + " (repeat)")
inputMethodHints: hints inputMethodHints: hints
echoMode: TextInput.Password echoMode: TextInput.Password
onTextChanged: validateFunc(userPass, userPassRepeat, errorText) onTextChanged: validatePassFunc(userPass, userPassRepeat, errorText)
text: config.userPassword text: config.userPassword
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 10
width: 500 width: 200
} }
Text { Text {
@@ -93,20 +121,21 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50 anchors.topMargin: 10
width: 500 width: 200
} }
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: errorText.bottom anchors.top: errorText.bottom
anchors.topMargin: 40 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Continue") text: qsTr("Continue")
onClicked: { onClicked: {
if (validateFunc(userPass, userPassRepeat, errorText)) { if (validatePassFunc(userPass, userPassRepeat, errorText) && validateNameFunc(username, errorText)) {
config.userPassword = userPass.text; config.userPassword = userPass.text;
config.username = username.text;
navNext(); navNext();
} }
} }

View File

@@ -26,8 +26,8 @@ Page
id: logo id: logo
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 50 anchors.topMargin: 10
height: 250 height: 50
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: "file:///usr/share/calamares/branding/default-mobile/logo.png" source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
} }
@@ -35,11 +35,11 @@ Page
id: waitText id: waitText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: logo.bottom anchors.top: logo.bottom
anchors.topMargin: 150 anchors.topMargin: 50
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: "Formatting and mounting target partition. This may" + text: "Formatting and mounting target partition. This may" +
" take up to two minutes, please be patient." " take up to ten minutes, please be patient."
width: 500 width: 200
} }
} }
} }

View File

@@ -28,8 +28,8 @@ Page
id: logo id: logo
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 50 anchors.topMargin: 10
height: 250 height: 50
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: "file:///usr/share/calamares/branding/default-mobile/logo.png" source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
} }
@@ -37,7 +37,7 @@ Page
id: mainText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: logo.bottom anchors.top: logo.bottom
anchors.topMargin: 50 anchors.topMargin: 10
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
text: "You are about to install<br>" + text: "You are about to install<br>" +
"<b>" + config.osName + "<b>" + config.osName +
@@ -46,16 +46,16 @@ Page
"<b>" + config.userInterface + "</b><br>" + "<b>" + config.userInterface + "</b><br>" +
"architecture " + "architecture " +
"<b>" + config.arch + "</b><br>" + "<b>" + config.arch + "</b><br>" +
"on your " + "on your <br>" +
"<b>" + config.device + "</b><br>" "<b>" + config.device + "</b><br>"
width: 500 width: 200
} }
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom anchors.top: mainText.bottom
anchors.topMargin: 50 anchors.topMargin: 10
width: 500 width: 200
text: qsTr("Continue") text: qsTr("Continue")
onClicked: navNext() onClicked: navNext()

View File

@@ -18,7 +18,6 @@
# #
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org> # SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# License-Filename: LICENSE
# #
""" """
@@ -44,39 +43,233 @@ _ = gettext.translation("calamares-python",
fallback=True).gettext fallback=True).gettext
# The following long **long** string is the entire text of
# a nix-configuration file. It is cribbed from, and adapted from,
# the sample file in https://github.com/itamar567/dotnix .
#
# We are going to substitute values into this text. However,
# Python's .format() function wants parens { } around variable
# names, and Nix's config file wants to use parens { } for block
# structure. So we have a compromise format here:
#
# - Write the config file as you would normally,
# - Write @@variable@@ instead of {variable}
#
# Some minor trickery later will massage this and substitute variables.
#
configuration_nix_sample = """# Nix configuration file
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
./command-not-found/command-not-found.nix
];
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# Use Zen Kernel
boot.kernelPackages = pkgs.linuxPackages_zen;
networking.hostName = "@@hostname@@"; # Define your hostname.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
# Set your time zone.
time.timeZone = "@@timezone@@";
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
networking.useDHCP = false;
networking.interfaces.enp42s0.useDHCP = true;
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
# Configure X11
services.xserver = {
enable = true;
windowManager.i3 = {
enable = true;
package = pkgs.i3-gaps;
};
# Set i3 to the default session in the display manager
displayManager.defaultSession = "none+i3";
};
# SSH fix
programs.ssh.askPassword = pkgs.lib.mkForce "";
# Enable CUPS to print documents.
services.printing.enable = true;
# Enable sound.
sound.enable = true;
hardware.pulseaudio.enable = true;
# Define a user account. Don't forget to set a password with passwd.
users.users.username = {
isNormalUser = true;
extraGroups = [ "wheel" "libvirtd" ];
};
# Disable password for sudo
security.sudo.extraRules= [{
groups = [ "wheel" ];
commands = [{
command = "ALL" ;
options= [ "NOPASSWD" ];
}];
}];
# Set ZSH as the default shell
users.defaultUserShell = pkgs.zsh;
# clean /tmp on boot
boot.cleanTmpDir=true;
# Config packages
nixpkgs.config = {
allowUnfree = true;
chromium = {
enableWideVine = true;
};
};
# Automatically upgrade the system
# This service is a modified version of https://github.com/NixOS/nixpkgs/blob/nixos-21.05/nixos/modules/tasks/auto-upgrade.nix#L122
systemd = {
services.nixos-upgrade = {
description = "NixOS Upgrade";
# We use --upgrade, so we need internet access
wants = [ "network-online.target" ];
restartIfChanged = false;
unitConfig.X-StopOnRemoval = false;
serviceConfig.Type = "oneshot";
environment = config.nix.envVars // {
inherit (config.environment.sessionVariables) NIX_PATH;
HOME = "/root";
} // config.networking.proxy.envVars;
path = with pkgs; [
coreutils
gnutar
xz.bin
gzip
gitMinimal
config.nix.package.out
];
script = let
nixos-rebuild =
"${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
in ''
${nixos-rebuild} boot --upgrade
booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})"
built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
if [ "$booted" = "$built" ]; then
${nixos-rebuild} switch
fi
'';
};
# To start the service at boot, we will use a systemd timer
timers.nixos-upgrade = {
wantedBy = [ "timers.target" ];
partOf = [ "nixos-upgrade.service" ];
timerConfig.OnBootSec = "5s";
};
};
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
programs.mtr.enable = true;
programs.gnupg.agent = {
enable = true;
enableSSHSupport = true;
};
# Enable the OpenSSH daemon.
services.openssh.enable = true;
# Open ports in the firewall.
networking.firewall.enable = false;
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "20.09"; # Did you read the comment?
}
"""
def pretty_name(): def pretty_name():
return _("NixOS Configuration.") return _("NixOS Configuration.")
def catenate(d, key, *values):
"""
Sets @p d[key] to the string-concatenation of @p values
if none of the values are None.
This can be used to set keys conditionally based on
the values being found.
"""
if [v for v in values if v is None]:
return
d[key] = "".join(values)
def run(): def run():
"""NixOS Configuration.""" """NixOS Configuration."""
libcalamares.utils.debug("LocaleDir=" +
str(libcalamares.utils.gettext_path()))
libcalamares.utils.debug("Languages=" +
str(libcalamares.utils.gettext_languages()))
# TODO: probably want to use the job configuration gs = libcalamares.globalstorage
# with a key "stage" to distinguish generate-config text = configuration_nix_sample
# from execute-config; maybe it wants an "all" as well
# to do both.
accumulator = "*** Job configuration\n"
accumulator += str(libcalamares.job.configuration)
libcalamares.utils.debug(accumulator)
accumulator = "*** GlobalStorage configuration\n" # Collect variables to substitute into the main text
accumulator += "count: " + str(libcalamares.globalstorage.count()) + "\n" variables = dict()
accumulator += "keys: {}\n".format(str(libcalamares.globalstorage.keys())) catenate(variables, "hostname", gs.value("hostname"))
libcalamares.utils.debug(accumulator) catenate(variables, "timezone", gs.value("locationRegion"), "/", gs.value("locationZone"))
libcalamares.utils.debug("Run NixOS tools.") # Check that all variables are used
for key in variables.keys():
pattern = "@@{key}@@".format(key=key)
if not pattern in text:
libcalamares.utils.warning("Variable '{key}' is not used.".format(key=key))
libcalamares.job.setprogress( 0.1 ) # Check that all patterns exist
sleep(1) import re
libcalamares.job.setprogress( 0.5 ) variable_pattern = re.compile("@@\w+@@")
sleep(1) for match in variable_pattern.finditer(text):
libcalamares.job.setprogress( 1.0 ) variable_name = text[match.start()+2:match.end()-2]
if not variable_name in variables:
libcalamares.utils.warning("Variable '{key}' is used but not defined.".format(key=variable_name))
sleep(3) # Do the substitutions
for key in variables.keys():
pattern = "@@{key}@@".format(key=key)
text = text.replace(pattern, str(variables[key]))
# Write the result to a temp-file, then run the main tool.
# There is no progress reporting from the tool, so it's going
# to seem like the module is hanging (see issue #1740).
configuration_filename = "/tmp/configuration.nix"
with open(configuration_filename, "w") as f:
f.write(text)
libcalamares.job.setprogress(0.1)
libcalamares.utils.check_target_env_call(["nix", configuration_filename])
# To indicate an error, return a tuple of: # To indicate an error, return a tuple of:
# (message, detailed-error-message) # (message, detailed-error-message)

View File

@@ -1,3 +1,9 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The NixOS module writes a nix-configuration file and then calls
# the Nix configuration tool to do the actual work of building
# the target system.
--- ---
type: "job" type: "job"
name: "os-nixos" name: "os-nixos"

View File

@@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
hostname: my-nix-host
locationRegion: Asia
locationZone: Kolkata

View File

@@ -0,0 +1,58 @@
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
#
find_package(${qtname} COMPONENTS Core Gui Widgets REQUIRED)
set(_extra_libraries "")
set(_extra_src "")
### OPTIONAL AppData XML support in PackageModel
#
#
option(BUILD_APPDATA "Support appdata: items in PackageChooser (requires QtXml)" OFF)
if(BUILD_APPDATA)
find_package(${qtname} REQUIRED COMPONENTS Xml)
if(TARGET ${qtname}::Xml)
add_definitions(-DHAVE_APPDATA)
list(APPEND _extra_libraries ${qtname}::Xml)
list(APPEND _extra_src ItemAppData.cpp)
endif()
endif()
### OPTIONAL AppStream support in PackageModel
#
#
# include(AppStreamHelper)
#
calamares_add_plugin(packagechooser
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
Config.cpp
PackageChooserPage.cpp
PackageChooserViewStep.cpp
PackageModel.cpp
LoaderQueue.cpp
PackageTreeItem.cpp
${_extra_src}
RESOURCES
packagechooser.qrc
UI
page_package.ui
LINK_PRIVATE_LIBRARIES
${_extra_libraries}
SHARED_LIB
)
# if(AppStreamQt_FOUND)
# target_link_libraries(calamares_viewmodule_packagechooser PRIVATE calamares::appstreamqt)
# target_sources(calamares_viewmodule_packagechooser PRIVATE ItemAppStream.cpp)
# endif()
# calamares_add_test(
# packagechoosertest
# GUI
# SOURCES Tests.cpp
# LIBRARIES calamares_viewmodule_packagechooser ${_extra_libraries}
# )

View File

@@ -0,0 +1,444 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Config.h"
#include "LoaderQueue.h"
#ifdef HAVE_APPDATA
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM_VERSION
#include "ItemAppStream.h"
#include <memory>
#endif
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "compat/Variant.h"
#include "packages/Globals.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include "network/Manager.h"
#include <QNetworkReply>
/** @brief This removes any values from @p groups that match @p source
*
* This is used to remove duplicates from the netinstallAdd structure
* It iterates over @p groups and for each map in the list, if the
* "source" element matches @p source, it is removed from the returned
* list.
*/
static QVariantList
pruneNetinstallAdd( const QString& source, const QVariant& groups )
{
QVariantList newGroupList;
const QVariantList groupList = groups.toList();
for ( const QVariant& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( groupMap.value( "source", "" ).toString() != source )
{
newGroupList.append( groupMap );
}
}
return newGroupList;
}
const NamedEnumTable< PackageChooserMode >&
packageChooserModeNames()
{
static const NamedEnumTable< PackageChooserMode > names {
{ "optional", PackageChooserMode::Optional },
{ "required", PackageChooserMode::Required },
{ "optionalmultiple", PackageChooserMode::OptionalMultiple },
{ "requiredmultiple", PackageChooserMode::RequiredMultiple },
// and a bunch of aliases
{ "zero-or-one", PackageChooserMode::Optional },
{ "radio", PackageChooserMode::Required },
{ "one", PackageChooserMode::Required },
{ "set", PackageChooserMode::OptionalMultiple },
{ "zero-or-more", PackageChooserMode::OptionalMultiple },
{ "multiple", PackageChooserMode::RequiredMultiple },
{ "one-or-more", PackageChooserMode::RequiredMultiple }
};
return names;
}
const NamedEnumTable< PackageChooserMethod >&
PackageChooserMethodNames()
{
static const NamedEnumTable< PackageChooserMethod > names {
{ "legacy", PackageChooserMethod::Legacy },
{ "custom", PackageChooserMethod::Legacy },
{ "contextualprocess", PackageChooserMethod::Legacy },
{ "packages", PackageChooserMethod::Packages },
{ "netinstall-add", PackageChooserMethod::NetAdd },
{ "netinstall-select", PackageChooserMethod::NetSelect },
};
return names;
}
Config::Config( QObject* parent )
: Calamares::ModuleSystem::Config( parent )
, m_model( new PackageListModel( this ) )
, m_netmodel( new PackageNetModel( this ) )
, m_mode( PackageChooserMode::Required )
{
}
Config::~Config() {}
QString
Config::status() const
{
switch ( m_status )
{
case Status::Ok:
return QString();
case Status::FailedBadConfiguration:
return tr( "Network Installation. (Disabled: Incorrect configuration)" );
case Status::FailedBadData:
return tr( "Network Installation. (Disabled: Received invalid groups data)" );
case Status::FailedInternalError:
return tr( "Network Installation. (Disabled: Internal error)" );
case Status::FailedNetworkError:
return tr( "Network Installation. (Disabled: Unable to fetch package lists, check your network connection)" );
case Status::FailedNoData:
return tr( "Network Installation. (Disabled: No package list)" );
}
__builtin_unreachable();
}
void
Config::setStatus( Status s )
{
m_status = s;
emit statusChanged( status() );
}
void
Config::loadGroupList( const QVariantList& groupData )
{
m_netmodel->setupModelData( groupData );
if ( m_netmodel->rowCount() < 1 )
{
cWarning() << "NetInstall groups data was empty.";
setStatus( Status::FailedNoData );
}
else
{
setStatus( Status::Ok );
}
}
void
Config::loadingDone()
{
if ( m_queue )
{
m_queue->deleteLater();
m_queue = nullptr;
}
emit statusReady();
}
const PackageItem&
Config::introductionPackage() const
{
for ( int i = 0; i < m_model->packageCount(); ++i )
{
const auto& package = m_model->packageData( i );
if ( package.isNonePackage() )
{
return package;
}
}
static PackageItem* defaultIntroduction = nullptr;
if ( !defaultIntroduction )
{
const auto name = QT_TR_NOOP( "Package Selection" );
const auto description
= QT_TR_NOOP( "Please pick a product from the list. The selected product will be installed." );
defaultIntroduction = new PackageItem( QString(), name, description );
defaultIntroduction->screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) );
defaultIntroduction->name = Calamares::Locale::TranslatedString( name, metaObject()->className() );
defaultIntroduction->description
= Calamares::Locale::TranslatedString( description, metaObject()->className() );
}
return *defaultIntroduction;
}
static inline QString
make_gs_key( const Calamares::ModuleSystem::InstanceKey& key )
{
return QStringLiteral( "packagechooser_" ) + key.id();
}
void
Config::updateGlobalStorage( const QStringList& selected ) const
{
if ( m_packageChoice.has_value() )
{
cWarning() << "Inconsistent package choices -- both model and single-selection QML";
}
if ( m_method == PackageChooserMethod::Legacy )
{
QString value = selected.join( ',' );
Calamares::JobQueue::instance()->globalStorage()->insert( make_gs_key( m_defaultId ), value );
cDebug() << m_defaultId << "selected" << value;
}
else if ( m_method == PackageChooserMethod::Packages )
{
QStringList packageNames = m_model->getInstallPackagesForNames( selected );
cDebug() << m_defaultId << "packages to install" << packageNames;
Calamares::Packages::setGSPackageAdditions(
Calamares::JobQueue::instance()->globalStorage(), m_defaultId, packageNames );
}
else if ( m_method == PackageChooserMethod::NetAdd )
{
QVariantList netinstallDataList = m_model->getNetinstallDataForNames( selected );
if ( netinstallDataList.isEmpty() )
{
cWarning() << "No netinstall information found for " << selected;
}
else
{
// If an earlier packagechooser instance added this data to global storage, combine them
auto* gs = Calamares::JobQueue::instance()->globalStorage();
if ( gs->contains( "netinstallAdd" ) )
{
netinstallDataList
+= pruneNetinstallAdd( QStringLiteral( "packageChooser" ), gs->value( "netinstallAdd" ) );
}
gs->insert( "netinstallAdd", netinstallDataList );
}
}
else if ( m_method == PackageChooserMethod::NetSelect )
{
cDebug() << m_defaultId << "groups to select in netinstall" << selected;
QStringList newSelected = selected;
auto* gs = Calamares::JobQueue::instance()->globalStorage();
// If an earlier packagechooser instance added this data to global storage, combine them
if ( gs->contains( "netinstallSelect" ) )
{
auto selectedOrig = gs->value( "netinstallSelect" );
if ( selectedOrig.canConvert< QStringList >() )
{
newSelected += selectedOrig.toStringList();
}
else
{
cWarning() << "Invalid NetinstallSelect data in global storage. Earlier selections purged";
}
gs->remove( "netinstallSelect" );
}
gs->insert( "netinstallSelect", newSelected );
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );
}
}
void
Config::updateGlobalStorage() const
{
if ( m_model->packageCount() > 0 )
{
cWarning() << "Inconsistent package choices -- both model and single-selection QML";
}
if ( m_method == PackageChooserMethod::Legacy )
{
auto* gs = Calamares::JobQueue::instance()->globalStorage();
if ( m_packageChoice.has_value() )
{
gs->insert( make_gs_key( m_defaultId ), m_packageChoice.value() );
}
else
{
gs->remove( make_gs_key( m_defaultId ) );
}
}
else if ( m_method == PackageChooserMethod::Packages )
{
cWarning() << "Unsupported single-selection packagechooser method 'Packages'";
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );
}
}
void
Config::setPackageChoice( const QString& packageChoice )
{
if ( packageChoice.isEmpty() )
{
m_packageChoice.reset();
}
else
{
m_packageChoice = packageChoice;
}
emit packageChoiceChanged( m_packageChoice.value_or( QString() ) );
}
QString
Config::prettyName() const
{
return m_stepName ? m_stepName->get() : tr( "Packages" );
}
QString
Config::prettyStatus() const
{
return tr( "Install option: <strong>%1</strong>" ).arg( m_packageChoice.value_or( tr( "None" ) ) );
}
static void
fillModel( PackageListModel* model, const QVariantList& items )
{
if ( items.isEmpty() )
{
cWarning() << "No *items* for PackageChooser module.";
return;
}
#ifdef HAVE_APPSTREAM_VERSION
std::unique_ptr< AppStream::Pool > pool;
bool poolOk = false;
#endif
cDebug() << "Loading PackageChooser model items from config";
int item_index = 0;
for ( const auto& item_it : items )
{
++item_index;
QVariantMap item_map = item_it.toMap();
if ( item_map.isEmpty() )
{
cWarning() << "PackageChooser entry" << item_index << "is not valid.";
continue;
}
if ( item_map.contains( "appdata" ) )
{
#ifdef HAVE_XML
model->addPackage( fromAppData( item_map ) );
#else
cWarning() << "Loading AppData XML is not supported.";
#endif
}
else if ( item_map.contains( "appstream" ) )
{
#ifdef HAVE_APPSTREAM_VERSION
if ( !pool )
{
pool = std::make_unique< AppStream::Pool >();
pool->setLocale( QStringLiteral( "ALL" ) );
poolOk = pool->load();
}
if ( pool && poolOk )
{
model->addPackage( fromAppStream( *pool, item_map ) );
}
#else
cWarning() << "Loading AppStream data is not supported.";
#endif
}
else
{
model->addPackage( PackageItem( item_map ) );
}
}
cDebug() << Logger::SubEntry << "Loaded PackageChooser with" << model->packageCount() << "entries.";
}
void
Config::setConfigurationMap( const QVariantMap& configurationMap )
{
m_mode = packageChooserModeNames().find( Calamares::getString( configurationMap, "mode" ),
PackageChooserMode::Required );
m_method = PackageChooserMethodNames().find( Calamares::getString( configurationMap, "method" ),
PackageChooserMethod::Legacy );
if ( m_method == PackageChooserMethod::Legacy )
{
cDebug() << "Using module ID" << m_defaultId;
}
if ( configurationMap.contains( "items" ) )
{
fillModel( m_model, configurationMap.value( "items" ).toList() );
// Lastly, load the groups data
// const QString key = QStringLiteral( "groupsUrl" );
//
//
// const auto& groupsUrlVariant = configurationMap.value( key );
// m_queue = new LoaderQueue( this );
// if ( Calamares::typeOf( groupsUrlVariant ) == Calamares::StringVariantType )
// {
// m_queue->append( SourceItem::makeSourceItem( groupsUrlVariant.toString(), configurationMap ) );
// }
// else if ( Calamares::typeOf( groupsUrlVariant ) == Calamares::ListVariantType )
// {
// for ( const auto& s : groupsUrlVariant.toStringList() )
// {
// m_queue->append( SourceItem::makeSourceItem( s, configurationMap ) );
// }
// }
//
// setStatus( required() ? Status::FailedNoData : Status::Ok );
// cDebug() << "Loading netinstall from" << m_queue->count() << "alternate sources.";
// connect( m_queue, &LoaderQueue::done, this, &Config::loadingDone );
// m_queue->load();
QString default_item_id = Calamares::getString( configurationMap, "default" );
if ( !default_item_id.isEmpty() )
{
for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n )
{
QModelIndex item_idx = m_model->index( item_n, 0 );
QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole );
if ( item_id.toString() == default_item_id )
{
m_defaultModelIndex = item_idx;
break;
}
}
}
}
else
{
setPackageChoice( Calamares::getString( configurationMap, "packageChoice" ) );
if ( m_method != PackageChooserMethod::Legacy )
{
cWarning() << "Single-selection QML module must use 'Legacy' method.";
}
}
bool labels_ok = false;
auto labels = Calamares::getSubMap( configurationMap, "labels", labels_ok );
if ( labels_ok )
{
if ( labels.contains( "step" ) )
{
m_stepName = new Calamares::Locale::TranslatedString( labels, "step" );
}
}
}

View File

@@ -0,0 +1,171 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSER_CONFIG_H
#define PACKAGECHOOSER_CONFIG_H
#include "PackageModel.h"
#include "modulesystem/Config.h"
#include "modulesystem/InstanceKey.h"
#include <QObject>
#include <QVariantMap>
#include <memory>
#include <optional>
class LoaderQueue;
enum class PackageChooserMode
{
Optional, // zero or one
Required, // exactly one
OptionalMultiple, // zero or more
RequiredMultiple // one or more
};
const NamedEnumTable< PackageChooserMode >& packageChooserModeNames();
enum class PackageChooserMethod
{
Legacy, // use contextualprocess or other custom
Packages, // use the packages module
NetAdd, // adds packages to the netinstall module
NetSelect, // makes selections in the netinstall module
};
const NamedEnumTable< PackageChooserMethod >& PackageChooserMethodNames();
class Config : public Calamares::ModuleSystem::Config
{
Q_OBJECT
/** @brief This is the single-select package-choice
*
* For (QML) modules that support only a single selection and
* just want to do things in a straightforward pick-this-one
* way, the packageChoice property is a (the) way to go.
*
* Writing to this property means that any other form of package-
* choice or selection is ignored.
*/
Q_PROPERTY( QString packageChoice READ packageChoice WRITE setPackageChoice NOTIFY packageChoiceChanged )
Q_PROPERTY( QString prettyStatus READ prettyStatus NOTIFY prettyStatusChanged FINAL )
Q_PROPERTY( PackageNetModel* PackageNetModel MEMBER m_netmodel FINAL )
Q_PROPERTY( QString status READ status NOTIFY statusChanged FINAL )
public:
Config( QObject* parent = nullptr );
~Config() override;
/** @brief Sets the default Id for this Config
*
* The default Id is the (owning) module identifier for the config,
* and it is used when the Id read from the config file is empty.
* The **usual** configuration when using method *packages* is
* to rely on the default Id.
*/
void setDefaultId( const Calamares::ModuleSystem::InstanceKey& defaultId ) { m_defaultId = defaultId; }
void setConfigurationMap( const QVariantMap& ) override;
PackageChooserMode mode() const { return m_mode; }
PackageListModel* model() const { return m_model; }
QModelIndex defaultSelectionIndex() const { return m_defaultModelIndex; }
PackageNetModel* netmodel() const { return m_netmodel; }
void loadGroupList( const QVariantList& groupData );
/** @brief Returns an "introductory package" which describes packagechooser
*
* If the model contains a "none" package, returns that one on
* the assumption that it is one to describe the whole; otherwise
* returns a totally generic description.
*/
const PackageItem& introductionPackage() const;
/** @brief Write selection to global storage
*
* Updates the GS keys for this packagechooser, marking all
* (and only) the packages in @p selected as selected.
*/
void updateGlobalStorage( const QStringList& selected ) const;
/** @brief Write selection to global storage
*
* Updates the GS keys for this packagechooser, marking **only**
* the package choice as selected. This assumes that the single-
* selection QML code is in use.
*/
void updateGlobalStorage() const;
QString packageChoice() const { return m_packageChoice.value_or( QString() ); }
void setPackageChoice( const QString& packageChoice );
QString prettyName() const;
QString prettyStatus() const;
enum class Status
{
Ok,
FailedBadConfiguration,
FailedInternalError,
FailedNetworkError,
FailedBadData,
FailedNoData
};
/// Human-readable, translated representation of the status
QString status() const;
/// Internal code for the status
Status statusCode() const { return m_status; }
void setStatus( Status s );
signals:
void packageChoiceChanged( QString packageChoice );
void prettyStatusChanged();
Q_SIGNALS:
void statusChanged( QString status ); ///< Something changed
void sidebarLabelChanged( QString label );
void titleLabelChanged( QString label );
void statusReady(); ///< Loading groups is complete
private Q_SLOTS:
void loadingDone();
private:
PackageListModel* m_model = nullptr;
QModelIndex m_defaultModelIndex;
PackageNetModel* m_netmodel = nullptr;
LoaderQueue* m_queue = nullptr;
Status m_status = Status::Ok;
/// Selection mode for this module
PackageChooserMode m_mode = PackageChooserMode::Optional;
/// How this module stores to GS
PackageChooserMethod m_method = PackageChooserMethod::Legacy;
/// Value to use for id if none is set in the config file
Calamares::ModuleSystem::InstanceKey m_defaultId;
/** @brief QML selection (for single-selection approaches)
*
* If there is no value, then there has been no selection.
* Reading the property will return an empty QString.
*/
std::optional< QString > m_packageChoice;
Calamares::Locale::TranslatedString* m_stepName; // As it appears in the sidebar
};
#endif

View File

@@ -0,0 +1,225 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
/** @brief Loading items from AppData XML files.
*
* Only used if QtXML is found, implements PackageItem::fromAppData().
*/
#include "PackageModel.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QDomDocument>
#include <QDomNodeList>
#include <QFile>
/** @brief try to load the given @p fileName XML document
*
* Returns a QDomDocument, which will be valid iff the file can
* be read and contains valid XML data.
*/
static inline QDomDocument
loadAppData( const QString& fileName )
{
QFile file( fileName );
if ( !file.open( QIODevice::ReadOnly ) )
{
return QDomDocument();
}
QDomDocument doc( "AppData" );
if ( !doc.setContent( &file ) )
{
file.close();
return QDomDocument();
}
file.close();
return doc;
}
/** @brief gets the text of child element @p tagName
*/
static inline QString
getChildText( const QDomNode& n, const QString& tagName )
{
QDomElement e = n.firstChildElement( tagName );
return e.isNull() ? QString() : e.text();
}
/** @brief Gets a suitable screenshot path
*
* The <screenshots> element contains zero or more <screenshot>
* elements, which can have a *type* associated with them.
* Scan the screenshot elements, return the <image> path
* for the one labeled with type=default or, if there is no
* default, the first element.
*/
static inline QString
getScreenshotPath( const QDomNode& n )
{
QDomElement shotsNode = n.firstChildElement( "screenshots" );
if ( shotsNode.isNull() )
{
return QString();
}
const QDomNodeList shotList = shotsNode.childNodes();
int firstScreenshot = -1; // Use which screenshot node?
for ( int i = 0; i < shotList.count(); ++i )
{
if ( !shotList.at( i ).isElement() )
{
continue;
}
QDomElement e = shotList.at( i ).toElement();
if ( e.tagName() != "screenshot" )
{
continue;
}
// If none has the "type=default" attribute, use the first one
if ( firstScreenshot < 0 )
{
firstScreenshot = i;
}
// But type=default takes precedence.
if ( e.hasAttribute( "type" ) && e.attribute( "type" ) == "default" )
{
firstScreenshot = i;
break;
}
}
if ( firstScreenshot >= 0 )
{
return shotList.at( firstScreenshot ).firstChildElement( "image" ).text();
}
return QString();
}
/** @brief Returns language of the given element @p e
*
* Transforms the attribute value for xml:lang to something
* suitable for TranslatedString (e.g. [lang]).
*/
static inline QString
getLanguage( const QDomElement& e )
{
QString language = e.attribute( "xml:lang" );
if ( !language.isEmpty() )
{
language.replace( '-', '_' );
language.prepend( '[' );
language.append( ']' );
}
return language;
}
/** @brief Scan the list of @p children for @p tagname elements and add them to the map
*
* Uses @p mapname instead of @p tagname for the entries in map @p m
* to allow renaming from XML to map keys (in particular for
* TranslatedString). Also transforms xml:lang attributes to suitable
* key-decorations on @p mapname.
*/
static inline void
fillMap( QVariantMap& m, const QDomNodeList& children, const QString& tagname, const QString& mapname )
{
for ( int i = 0; i < children.count(); ++i )
{
if ( !children.at( i ).isElement() )
{
continue;
}
QDomElement e = children.at( i ).toElement();
if ( e.tagName() != tagname )
{
continue;
}
m[ mapname + getLanguage( e ) ] = e.text();
}
}
/** @brief gets the <name> and <description> elements
*
* Builds up a map of the <name> elements (which may have a *lang*
* attribute to indicate translations and paragraphs of the
* <description> element (also with lang). Uses the <summary>
* elements to supplement the description if no description
* is available for a given language.
*
* Returns a map with keys suitable for use by TranslatedString.
*/
static inline QVariantMap
getNameAndSummary( const QDomNode& n )
{
QVariantMap m;
const QDomNodeList children = n.childNodes();
fillMap( m, children, "name", "name" );
fillMap( m, children, "summary", "description" );
const QDomElement description = n.firstChildElement( "description" );
if ( !description.isNull() )
{
fillMap( m, description.childNodes(), "p", "description" );
}
return m;
}
PackageItem
fromAppData( const QVariantMap& item_map )
{
QString fileName = Calamares::getString( item_map, "appdata" );
if ( fileName.isEmpty() )
{
cWarning() << "Can't load AppData without a suitable key.";
return PackageItem();
}
cDebug() << "Loading AppData XML from" << fileName;
QDomDocument doc = loadAppData( fileName );
if ( doc.isNull() )
{
return PackageItem();
}
QDomElement componentNode = doc.documentElement();
if ( !componentNode.isNull() && componentNode.tagName() == "component" )
{
// An "id" entry in the Calamares config overrides ID in the AppData
QString id = Calamares::getString( item_map, "id" );
if ( id.isEmpty() )
{
id = getChildText( componentNode, "id" );
}
if ( id.isEmpty() )
{
return PackageItem();
}
// A "screenshot" entry in the Calamares config overrides AppData
QString screenshotPath = Calamares::getString( item_map, "screenshot" );
if ( screenshotPath.isEmpty() )
{
screenshotPath = getScreenshotPath( componentNode );
}
QVariantMap map = getNameAndSummary( componentNode );
map.insert( "id", id );
map.insert( "screenshot", screenshotPath );
return PackageItem( map );
}
return PackageItem();
}

View File

@@ -0,0 +1,28 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef ITEMAPPDATA_H
#define ITEMAPPDATA_H
#include "PackageModel.h"
/** @brief Loads an AppData XML file and returns a PackageItem
*
* The @p map must have a key *appdata*. That is used as the
* primary source of information, but keys *id* and *screenshotPath*
* may be used to override parts of the AppData -- so that the
* ID is under the control of Calamares, and the screenshot can be
* forced to a local path available on the installation medium.
*
* Requires XML support in libcalamares, if not present will
* return invalid PackageItems.
*/
PackageItem fromAppData( const QVariantMap& map );
#endif

View File

@@ -0,0 +1,165 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
/** @brief Loading items from AppStream database.
*
* Only used if AppStreamQt is found, implements PackageItem::fromAppStream().
*/
#include "ItemAppStream.h"
#include "locale/TranslationsModel.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
/// @brief Return number of pixels in a size, for < ordering purposes
static inline quint64
sizeOrder( const QSize& size )
{
return static_cast<quint64>(size.width()) * static_cast<quint64>(size.height());
}
/// @brief Sets a screenshot in @p map from @p screenshot, if a usable one is found
static void
setScreenshot( QVariantMap& map, const AppStream::Screenshot& screenshot )
{
if ( screenshot.images().count() < 1 )
{
return;
}
// Pick the smallest
QUrl url;
quint64 size = sizeOrder( screenshot.images().first().size() );
for ( const auto& img : screenshot.images() )
{
if ( sizeOrder( img.size() ) <= size )
{
url = img.url();
}
}
if ( url.isValid() )
{
map.insert( "screenshot", url.toString() );
}
}
/// @brief Interpret an AppStream Component
static PackageItem
fromComponent( AppStream::Pool& pool, AppStream::Component& component )
{
#if HAVE_APPSTREAM_VERSION == 0
auto setActiveLocale = [&component](const QString & locale)
{
component.setActiveLocale( locale );
};
#else
auto setActiveLocale = [&pool](const QString & locale)
{
pool.setLocale( locale );
};
#endif
QVariantMap map;
map.insert( "id", component.id() );
map.insert( "package", component.packageNames().join( "," ) );
// Assume that the pool has loaded "ALL" locales, but it might be set
// to any of them; get the en_US locale as "untranslated" and then
// loop over Calamares locales (since there is no way to query for
// available locales in the Component) to see if there's anything else.
setActiveLocale( QStringLiteral( "en_US" ) );
QString en_name = component.name();
QString en_description = component.description();
map.insert( "name", en_name );
map.insert( "description", en_description );
for ( const QString& locale : Calamares::Locale::availableTranslations()->localeIds() )
{
setActiveLocale( locale );
QString name = component.name();
if ( name != en_name )
{
map.insert( QStringLiteral( "name[%1]" ).arg( locale ), name );
}
QString description = component.description();
if ( description != en_description )
{
map.insert( QStringLiteral( "description[%1]" ).arg( locale ), description );
}
}
#if HAVE_APPSTREAM_VERSION == 0
auto screenshots = component.screenshots();
#else
auto screenshots = component.screenshotsAll();
#endif
if ( screenshots.count() > 0 )
{
bool done = false;
for ( const auto& s : screenshots )
{
if ( s.isDefault() )
{
setScreenshot( map, s );
done = true;
break;
}
}
if ( !done )
{
setScreenshot( map, screenshots.first() );
}
}
return PackageItem( map );
}
PackageItem
fromAppStream( AppStream::Pool& pool, const QVariantMap& item_map )
{
QString appstreamId = Calamares::getString( item_map, "appstream" );
if ( appstreamId.isEmpty() )
{
cWarning() << "Can't load AppStream without a suitable appstreamId.";
return PackageItem();
}
cDebug() << "Loading AppStream data for" << appstreamId;
#if HAVE_APPSTREAM_VERSION == 0
auto itemList = pool.componentsById( appstreamId );
#else
auto itemList = pool.componentsById( appstreamId ).toList();
#endif
if ( itemList.count() < 1 )
{
cWarning() << "No AppStream data for" << appstreamId;
return PackageItem();
}
if ( itemList.count() > 1 )
{
cDebug() << "Multiple AppStream data for" << appstreamId << "using first.";
}
auto r = fromComponent( pool, itemList.first() );
if ( r.isValid() )
{
QString id = Calamares::getString( item_map, "id" );
QString screenshotPath = Calamares::getString( item_map, "screenshot" );
if ( !id.isEmpty() )
{
r.id = id;
}
if ( !screenshotPath.isEmpty() )
{
r.screenshot = screenshotPath;
}
}
return r;
}

View File

@@ -0,0 +1,52 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef ITEMAPPSTREAM_H
#define ITEMAPPSTREAM_H
#include "PackageModel.h"
/*
* This weird include mechanism is because an #include line is allowed
* to consist of preprocessor-tokens, which are expanded, and then
* the #include is *re*processed. But if it starts with < or ", then
* preprocessor tokens are not expanded. So we build up a #include <>
* style line with a suitable path -- if we are given a value for
* HAVE_APPSTREAM_HEADERS, that is the directory that the AppStreamQt
* headers live in.
*/
#define CALAMARES_LT <
#define CALAMARES_GT >
#ifndef HAVE_APPSTREAM_HEADERS
#define HAVE_APPSTREAM_HEADERS AppStreamQt
#endif
#include CALAMARES_LT HAVE_APPSTREAM_HEADERS/pool.h CALAMARES_GT
#include CALAMARES_LT HAVE_APPSTREAM_HEADERS/image.h CALAMARES_GT
#include CALAMARES_LT HAVE_APPSTREAM_HEADERS/screenshot.h CALAMARES_GT
#undef CALAMARES_LT
#undef CALAMARES_GT
/** @brief Loads an item from AppStream data.
*
* The @p map must have a key *appstream*. That is used as the
* primary source of information from the AppStream cache, but
* keys *id* and *screenshotPath* may be used to override parts
* of the AppStream data -- so that the ID is under the control
* of Calamares, and the screenshot can be forced to a local path
* available on the installation medium.
*
* Requires AppStreamQt, if not present will return invalid
* PackageItems.
*/
PackageItem fromAppStream( AppStream::Pool& pool, const QVariantMap& map );
#endif

View File

@@ -0,0 +1,205 @@
/*
* SPDX-FileCopyrightText: 2016 Luca Giambonini <almack@chakraos.org>
* SPDX-FileCopyrightText: 2016 Lisa Vitolo <shainer@chakraos.org>
* SPDX-FileCopyrightText: 2017 Kyle Robbertze <krobbertze@gmail.com>
* SPDX-FileCopyrightText: 2017-2018 2020, Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "LoaderQueue.h"
#include "Config.h"
#include "network/Manager.h"
#include "utils/Logger.h"
#include "utils/RAII.h"
#include "utils/Yaml.h"
#include <QNetworkReply>
#include <QTimer>
/** @brief Call fetchNext() on the queue if it can
*
* On destruction, a new call to fetchNext() is queued, so that
* the queue continues loading. Calling release() before the
* destructor skips the fetchNext(), ending the queue-loading.
*
* Calling done(b) is a conditional release: if @p b is @c true,
* queues a call to done() on the queue and releases it; otherwise,
* does nothing.
*/
class FetchNextUnless
{
public:
FetchNextUnless( LoaderQueue* q )
: m_q( q )
{
}
~FetchNextUnless()
{
if ( m_q )
{
QMetaObject::invokeMethod( m_q, "fetchNext", Qt::QueuedConnection );
}
}
void release() { m_q = nullptr; }
void done( bool b )
{
if ( b )
{
if ( m_q )
{
QMetaObject::invokeMethod( m_q, "done", Qt::QueuedConnection );
}
release();
}
}
private:
LoaderQueue* m_q = nullptr;
};
SourceItem
SourceItem::makeSourceItem( const QString& groupsUrl, const QVariantMap& configurationMap )
{
if ( groupsUrl == QStringLiteral( "local" ) )
{
return SourceItem { QUrl(), configurationMap.value( "groups" ).toList() };
}
else
{
return SourceItem { QUrl { groupsUrl }, QVariantList() };
}
}
LoaderQueue::LoaderQueue( Config* parent )
: QObject( parent )
, m_config( parent )
{
}
void
LoaderQueue::append( SourceItem&& i )
{
m_queue.append( std::move( i ) );
}
void
LoaderQueue::load()
{
QMetaObject::invokeMethod( this, "fetchNext", Qt::QueuedConnection );
}
void
LoaderQueue::fetchNext()
{
if ( m_queue.isEmpty() )
{
emit done();
return;
}
auto source = m_queue.takeFirst();
if ( source.isLocal() )
{
m_config->loadGroupList( source.data );
emit done();
}
else
{
fetch( source.url );
}
}
void
LoaderQueue::fetch( const QUrl& url )
{
FetchNextUnless next( this );
if ( !url.isValid() )
{
m_config->setStatus( Config::Status::FailedBadConfiguration );
cDebug() << "Invalid URL" << url;
return;
}
using namespace Calamares::Network;
cDebug() << "NetInstall loading groups from" << url;
QNetworkReply* reply = Manager().asynchronousGet(
url,
RequestOptions( RequestOptions::FakeUserAgent | RequestOptions::FollowRedirect, std::chrono::seconds( 30 ) ) );
if ( !reply )
{
cDebug() << Logger::SubEntry << "Request failed immediately.";
// If nobody sets a different status, this will remain
m_config->setStatus( Config::Status::FailedBadConfiguration );
}
else
{
// When the network request is done, **then** we might
// do the next item from the queue, so don't call fetchNext() now.
next.release();
m_reply = reply;
connect( reply, &QNetworkReply::finished, this, &LoaderQueue::dataArrived );
}
}
void
LoaderQueue::dataArrived()
{
FetchNextUnless next( this );
if ( !m_reply || !m_reply->isFinished() )
{
cWarning() << "NetInstall data called too early.";
m_config->setStatus( Config::Status::FailedInternalError );
return;
}
cDebug() << "NetInstall group data received" << m_reply->size() << "bytes from" << m_reply->url();
cqDeleter< QNetworkReply > d { m_reply };
// If m_required is *false* then we still say we're ready
// even if the reply is corrupt or missing.
if ( m_reply->error() != QNetworkReply::NoError )
{
cWarning() << "unable to fetch netinstall package lists.";
cDebug() << Logger::SubEntry << "Netinstall reply error: " << m_reply->error();
cDebug() << Logger::SubEntry << "Request for url: " << m_reply->url().toString()
<< " failed with: " << m_reply->errorString();
m_config->setStatus( Config::Status::FailedNetworkError );
return;
}
QByteArray yamlData = m_reply->readAll();
try
{
auto groups = ::YAML::Load( yamlData.constData() );
if ( groups.IsSequence() )
{
m_config->loadGroupList( Calamares::YAML::sequenceToVariant( groups ) );
next.done( m_config->statusCode() == Config::Status::Ok );
}
else if ( groups.IsMap() )
{
auto map = Calamares::YAML::mapToVariant( groups );
m_config->loadGroupList( map.value( "groups" ).toList() );
next.done( m_config->statusCode() == Config::Status::Ok );
}
else
{
cWarning() << "NetInstall groups data does not form a sequence.";
}
}
catch ( ::YAML::Exception& e )
{
Calamares::YAML::explainException( e, yamlData, "netinstall groups data" );
m_config->setStatus( Config::Status::FailedBadData );
}
}

View File

@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2016 Luca Giambonini <almack@chakraos.org>
* SPDX-FileCopyrightText: 2016 Lisa Vitolo <shainer@chakraos.org>
* SPDX-FileCopyrightText: 2017 Kyle Robbertze <krobbertze@gmail.com>
* SPDX-FileCopyrightText: 2017-2018 2020, Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef NETINSTALL_LOADERQUEUE_H
#define NETINSTALL_LOADERQUEUE_H
#include <QQueue>
#include <QUrl>
#include <QVariantList>
class Config;
class QNetworkReply;
/** @brief Data about an entry in *groupsUrl*
*
* This can be a specific URL, or "local" which uses data stored
* in the configuration file itself.
*/
struct SourceItem
{
QUrl url;
QVariantList data;
bool isUrl() const { return url.isValid(); }
bool isLocal() const { return !data.isEmpty(); }
bool isValid() const { return isUrl() || isLocal(); }
/** @brief Create a SourceItem
*
* If the @p groupsUrl is @c "local" then the *groups* key in
* the @p configurationMap is used as the source; otherwise the
* string is used as an actual URL.
*/
static SourceItem makeSourceItem( const QString& groupsUrl, const QVariantMap& configurationMap );
};
/** @brief Queue of source items to load
*
* Queue things up by calling append() and then kick things off
* by calling load(). This will try to load the items, in order;
* the first one that succeeds will end the loading process.
*
* Signal done() is emitted when done (also when all of the items fail).
*/
class LoaderQueue : public QObject
{
Q_OBJECT
public:
LoaderQueue( Config* parent );
void append( SourceItem&& i );
int count() const { return m_queue.count(); }
public Q_SLOTS:
void load();
void fetchNext();
void fetch( const QUrl& url );
void dataArrived();
Q_SIGNALS:
void done();
private:
QQueue< SourceItem > m_queue;
Config* m_config = nullptr;
QNetworkReply* m_reply = nullptr;
};
#endif

View File

@@ -0,0 +1,145 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageChooserPage.h"
#include "ui_page_package.h"
#include "utils/Gui.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
#include <QLabel>
PackageChooserPage::PackageChooserPage( PackageChooserMode mode, QWidget* parent )
: QWidget( parent )
, ui( new Ui::PackageChooserPage )
, m_introduction( QString(),
QString(),
tr( "Package Selection" ),
tr( "Please pick a product from the list. The selected product will be installed." ) )
{
m_introduction.screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) );
ui->setupUi( this );
CALAMARES_RETRANSLATE( updateLabels(); );
switch ( mode )
{
case PackageChooserMode::Optional:
[[fallthrough]];
case PackageChooserMode::Required:
ui->products->setSelectionMode( QAbstractItemView::SingleSelection );
break;
case PackageChooserMode::OptionalMultiple:
[[fallthrough]];
case PackageChooserMode::RequiredMultiple:
ui->products->setSelectionMode( QAbstractItemView::ExtendedSelection );
}
ui->products->setMinimumWidth( 10 * Calamares::defaultFontHeight() );
}
void
PackageChooserPage::currentChanged( const QModelIndex& index )
{
if ( !index.isValid() || !ui->products->selectionModel()->hasSelection() )
{
ui->productName->setText( m_introduction.name.get() );
ui->productScreenshot->setPixmap( m_introduction.screenshot );
ui->productDescription->setText( m_introduction.description.get() );
}
else
{
const auto* model = ui->products->model();
ui->productName->setText( model->data( index, PackageListModel::NameRole ).toString() );
ui->productDescription->setText( model->data( index, PackageListModel::DescriptionRole ).toString() );
QPixmap currentScreenshot = model->data( index, PackageListModel::ScreenshotRole ).value< QPixmap >();
if ( currentScreenshot.isNull() )
{
ui->productScreenshot->setPixmap( m_introduction.screenshot );
}
else
{
ui->productScreenshot->setPixmap( currentScreenshot );
}
}
}
void
PackageChooserPage::updateLabels()
{
if ( ui && ui->products && ui->products->selectionModel() )
{
currentChanged( ui->products->selectionModel()->currentIndex() );
}
else
{
currentChanged( QModelIndex() );
}
emit selectionChanged();
}
void
PackageChooserPage::setModel( QAbstractItemModel* model )
{
ui->products->setModel( model );
currentChanged( QModelIndex() );
connect( ui->products->selectionModel(),
&QItemSelectionModel::selectionChanged,
this,
&PackageChooserPage::updateLabels );
}
void
PackageChooserPage::setSelection( const QModelIndex& index )
{
if ( index.isValid() )
{
ui->products->selectionModel()->select( index, QItemSelectionModel::Select );
}
currentChanged( index );
}
bool
PackageChooserPage::hasSelection() const
{
return ui && ui->products && ui->products->selectionModel() && ui->products->selectionModel()->hasSelection();
}
QStringList
PackageChooserPage::selectedPackageIds() const
{
if ( !( ui && ui->products && ui->products->selectionModel() ) )
{
return QStringList();
}
const auto* model = ui->products->model();
QStringList ids;
for ( const auto& index : ui->products->selectionModel()->selectedIndexes() )
{
QString pid = model->data( index, PackageListModel::IdRole ).toString();
if ( !pid.isEmpty() )
{
ids.append( pid );
}
}
return ids;
}
void
PackageChooserPage::setIntroduction( const PackageItem& item )
{
m_introduction.name = item.name;
m_introduction.description = item.description;
m_introduction.screenshot = item.screenshot;
}

View File

@@ -0,0 +1,57 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERPAGE_H
#define PACKAGECHOOSERPAGE_H
#include "Config.h"
#include "PackageModel.h"
#include <QAbstractItemModel>
#include <QWidget>
namespace Ui
{
class PackageChooserPage;
} // namespace Ui
class PackageChooserPage : public QWidget
{
Q_OBJECT
public:
explicit PackageChooserPage( PackageChooserMode mode, QWidget* parent = nullptr );
/// @brief Sets the data model for the listview
void setModel( QAbstractItemModel* model );
/// @brief Sets the introductory (no-package-selected) texts
void setIntroduction( const PackageItem& item );
/// @brief Selects a listview item
void setSelection( const QModelIndex& index );
/// @brief Is anything selected?
bool hasSelection() const;
/** @brief Get the list of selected ids
*
* This list may be empty (if none is selected).
*/
QStringList selectedPackageIds() const;
public slots:
void currentChanged( const QModelIndex& index );
void updateLabels();
signals:
void selectionChanged();
private:
Ui::PackageChooserPage* ui;
PackageItem m_introduction;
};
#endif // PACKAGECHOOSERPAGE_H

View File

@@ -0,0 +1,158 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageChooserViewStep.h"
#include "Config.h"
#include "PackageChooserPage.h"
#include "PackageModel.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/Logger.h"
#include "utils/System.h"
#include "utils/Variant.h"
#include <QDesktopServices>
#include <QVariantMap>
CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlugin< PackageChooserViewStep >(); )
PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_config( new Config( this ) )
, m_widget( nullptr )
{
emit nextStatusChanged( false );
}
PackageChooserViewStep::~PackageChooserViewStep()
{
if ( m_widget && m_widget->parent() == nullptr )
{
m_widget->deleteLater();
}
}
QString
PackageChooserViewStep::prettyName() const
{
return m_config->prettyName();
}
QWidget*
PackageChooserViewStep::widget()
{
if ( !m_widget )
{
m_widget = new PackageChooserPage( m_config->mode(), nullptr );
connect( m_widget,
&PackageChooserPage::selectionChanged,
[ = ]() { emit nextStatusChanged( this->isNextEnabled() ); } );
hookupModel();
}
return m_widget;
}
bool
PackageChooserViewStep::isNextEnabled() const
{
if ( !m_widget )
{
// No way to have changed anything
return true;
}
switch ( m_config->mode() )
{
case PackageChooserMode::Optional:
case PackageChooserMode::OptionalMultiple:
// zero or one OR zero or more
return true;
case PackageChooserMode::Required:
case PackageChooserMode::RequiredMultiple:
// exactly one OR one or more
return m_widget->hasSelection();
}
__builtin_unreachable();
}
bool
PackageChooserViewStep::isBackEnabled() const
{
return true;
}
bool
PackageChooserViewStep::isAtBeginning() const
{
return true;
}
bool
PackageChooserViewStep::isAtEnd() const
{
return true;
}
void
PackageChooserViewStep::onActivate()
{
if ( !m_widget->hasSelection() )
{
m_widget->setSelection( m_config->defaultSelectionIndex() );
}
}
void
PackageChooserViewStep::onLeave()
{
m_config->updateGlobalStorage( m_widget->selectedPackageIds() );
}
Calamares::JobList
PackageChooserViewStep::jobs() const
{
Calamares::JobList l;
return l;
}
void
PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
m_config->setDefaultId( moduleInstanceKey() );
m_config->setConfigurationMap( configurationMap );
if ( m_widget )
{
hookupModel();
}
}
void
PackageChooserViewStep::hookupModel()
{
if ( !m_config->model() || !m_widget )
{
cError() << "Can't hook up model until widget and model both exist.";
return;
}
m_widget->setModel( m_config->model() );
m_widget->setIntroduction( m_config->introductionPackage() );
}

View File

@@ -0,0 +1,57 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERVIEWSTEP_H
#define PACKAGECHOOSERVIEWSTEP_H
#include "DllMacro.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/PluginFactory.h"
#include "viewpages/ViewStep.h"
#include <QVariantMap>
class Config;
class PackageChooserPage;
class PLUGINDLLEXPORT PackageChooserViewStep : public Calamares::ViewStep
{
Q_OBJECT
public:
explicit PackageChooserViewStep( QObject* parent = nullptr );
~PackageChooserViewStep() override;
QString prettyName() const override;
QWidget* widget() override;
bool isNextEnabled() const override;
bool isBackEnabled() const override;
bool isAtBeginning() const override;
bool isAtEnd() const override;
void onActivate() override;
void onLeave() override;
Calamares::JobList jobs() const override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
void hookupModel();
Config* m_config;
PackageChooserPage* m_widget;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory )
#endif // PACKAGECHOOSERVIEWSTEP_H

View File

@@ -0,0 +1,575 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageModel.h"
#include "Branding.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include "compat/Variant.h"
#include "utils/Yaml.h"
#include <QFileInfo>
/** @brief A wrapper for Calamares::getSubMap that excludes the success param
*/
static QVariantMap
getSubMap( const QVariantMap& map, const QString& key )
{
bool success;
return Calamares::getSubMap( map, key, success );
}
static QPixmap
loadScreenshot( const QString& path )
{
if ( QFileInfo::exists( path ) )
{
return QPixmap( path );
}
const auto* branding = Calamares::Branding::instance();
if ( !branding )
{
return QPixmap();
}
return QPixmap( branding->componentDirectory() + QStringLiteral( "/" ) + path );
}
PackageItem::PackageItem() {}
PackageItem::PackageItem( const QString& a_id, const QString& a_name, const QString& a_description )
: id( a_id )
, name( a_name )
, description( a_description )
{
}
PackageItem::PackageItem( const QString& a_id,
const QString& a_name,
const QString& a_description,
const QString& screenshotPath )
: id( a_id )
, name( a_name )
, description( a_description )
, screenshot( screenshotPath )
{
}
PackageItem::PackageItem( const QVariantMap& item_map )
: id( Calamares::getString( item_map, "id" ) )
, name( Calamares::Locale::TranslatedString( item_map, "name" ) )
, description( Calamares::Locale::TranslatedString( item_map, "description" ) )
, screenshot( loadScreenshot( Calamares::getString( item_map, "screenshot" ) ) )
, packageNames( Calamares::getStringList( item_map, "packages" ) )
, netinstallData( getSubMap( item_map, "netinstall" ) )
{
if ( name.isEmpty() && id.isEmpty() )
{
name = QObject::tr( "No product" );
}
else if ( name.isEmpty() )
{
cWarning() << "PackageChooser item" << id << "has an empty name.";
}
if ( description.isEmpty() )
{
description = QObject::tr( "No description provided." );
}
}
PackageListModel::PackageListModel( QObject* parent )
: QAbstractListModel( parent )
{
}
PackageListModel::PackageListModel( PackageList&& items, QObject* parent )
: QAbstractListModel( parent )
, m_packages( std::move( items ) )
{
}
PackageListModel::~PackageListModel() {}
void
PackageListModel::addPackage( PackageItem&& p )
{
// Only add valid packages
if ( p.isValid() )
{
int c = m_packages.count();
beginInsertRows( QModelIndex(), c, c );
m_packages.append( p );
endInsertRows();
}
}
QStringList
PackageListModel::getInstallPackagesForName( const QString& id ) const
{
for ( const auto& p : qAsConst( m_packages ) )
{
if ( p.id == id )
{
return p.packageNames;
}
}
return QStringList();
}
QStringList
PackageListModel::getInstallPackagesForNames( const QStringList& ids ) const
{
QStringList l;
for ( const auto& p : qAsConst( m_packages ) )
{
if ( ids.contains( p.id ) )
{
l.append( p.packageNames );
}
}
return l;
}
QVariantList
PackageListModel::getNetinstallDataForNames( const QStringList& ids ) const
{
QVariantList l;
for ( auto& p : m_packages )
{
if ( ids.contains( p.id ) )
{
if ( !p.netinstallData.isEmpty() )
{
QVariantMap newData = p.netinstallData;
newData[ "source" ] = QStringLiteral( "packageChooser" );
l.append( newData );
}
}
}
return l;
}
int
PackageListModel::rowCount( const QModelIndex& index ) const
{
// For lists, valid indexes have zero children; only the root index has them
return index.isValid() ? 0 : m_packages.count();
}
QVariant
PackageListModel::data( const QModelIndex& index, int role ) const
{
if ( !index.isValid() )
{
return QVariant();
}
int row = index.row();
if ( row >= m_packages.count() || row < 0 )
{
return QVariant();
}
if ( role == Qt::DisplayRole /* Also PackageNameRole */ )
{
return m_packages[ row ].name.get();
}
else if ( role == DescriptionRole )
{
return m_packages[ row ].description.get();
}
else if ( role == ScreenshotRole )
{
return m_packages[ row ].screenshot;
}
else if ( role == IdRole )
{
return m_packages[ row ].id;
}
return QVariant();
}
/// Recursive helper for setSelections()
static void
setSelections( const QStringList& selectNames, PackageTreeItem* item )
{
for ( int i = 0; i < item->childCount(); i++ )
{
auto* child = item->child( i );
setSelections( selectNames, child );
}
if ( item->isGroup() && selectNames.contains( item->name() ) )
{
item->setSelected( Qt::CheckState::Checked );
}
}
/** @brief Collects all the "source" values from @p groupList
*
* Iterates over @p groupList and returns all nonempty "source"
* values from the maps.
*
*/
static QStringList
collectSources( const QVariantList& groupList )
{
QStringList sources;
for ( const QVariant& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( !groupMap[ "source" ].toString().isEmpty() )
{
sources.append( groupMap[ "source" ].toString() );
}
}
return sources;
}
PackageNetModel::PackageNetModel( QObject* parent )
: QAbstractItemModel( parent )
{
}
PackageNetModel::~PackageNetModel()
{
delete m_rootItem;
}
QModelIndex
PackageNetModel::index( int row, int column, const QModelIndex& parent ) const
{
if ( !m_rootItem || !hasIndex( row, column, parent ) )
{
return QModelIndex();
}
PackageTreeItem* parentItem;
if ( !parent.isValid() )
{
parentItem = m_rootItem;
}
else
{
parentItem = static_cast< PackageTreeItem* >( parent.internalPointer() );
}
PackageTreeItem* childItem = parentItem->child( row );
if ( childItem )
{
return createIndex( row, column, childItem );
}
else
{
return QModelIndex();
}
}
QModelIndex
PackageNetModel::parent( const QModelIndex& index ) const
{
if ( !m_rootItem || !index.isValid() )
{
return QModelIndex();
}
PackageTreeItem* child = static_cast< PackageTreeItem* >( index.internalPointer() );
PackageTreeItem* parent = child->parentItem();
if ( parent == m_rootItem )
{
return QModelIndex();
}
return createIndex( parent->row(), 0, parent );
}
int
PackageNetModel::rowCount( const QModelIndex& parent ) const
{
if ( !m_rootItem || ( parent.column() > 0 ) )
{
return 0;
}
PackageTreeItem* parentItem;
if ( !parent.isValid() )
{
parentItem = m_rootItem;
}
else
{
parentItem = static_cast< PackageTreeItem* >( parent.internalPointer() );
}
return parentItem->childCount();
}
int
PackageNetModel::columnCount( const QModelIndex& ) const
{
return 2;
}
QVariant
PackageNetModel::data( const QModelIndex& index, int role ) const
{
if ( !m_rootItem || !index.isValid() )
{
return QVariant();
}
PackageTreeItem* item = static_cast< PackageTreeItem* >( index.internalPointer() );
switch ( role )
{
case Qt::CheckStateRole:
return index.column() == NameColumn ? ( item->isImmutable() ? QVariant() : item->isSelected() ) : QVariant();
case Qt::DisplayRole:
return item->isHidden() ? QVariant() : item->data( index.column() );
case MetaExpandRole:
return item->isHidden() ? false : item->expandOnStart();
default:
return QVariant();
}
}
bool
PackageNetModel::setData( const QModelIndex& index, const QVariant& value, int role )
{
if ( !m_rootItem )
{
return false;
}
if ( role == Qt::CheckStateRole && index.isValid() )
{
PackageTreeItem* item = static_cast< PackageTreeItem* >( index.internalPointer() );
item->setSelected( static_cast< Qt::CheckState >( value.toInt() ) );
emit dataChanged( this->index( 0, 0 ),
index.sibling( index.column(), index.row() + 1 ),
QVector< int >( Qt::CheckStateRole ) );
}
return true;
}
Qt::ItemFlags
PackageNetModel::flags( const QModelIndex& index ) const
{
if ( !m_rootItem || !index.isValid() )
{
return Qt::ItemFlags();
}
if ( index.column() == NameColumn )
{
PackageTreeItem* item = static_cast< PackageTreeItem* >( index.internalPointer() );
if ( item->isImmutable() || item->isNoncheckable() )
{
return QAbstractItemModel::flags( index ); //Qt::NoItemFlags;
}
return Qt::ItemIsUserCheckable | QAbstractItemModel::flags( index );
}
return QAbstractItemModel::flags( index );
}
QVariant
PackageNetModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
{
return ( section == NameColumn ) ? tr( "Name" ) : tr( "Description" );
}
return QVariant();
}
void
PackageNetModel::setSelections( const QStringList& selectNames )
{
if ( m_rootItem )
{
::setSelections( selectNames, m_rootItem );
}
}
PackageTreeItem::List
PackageNetModel::getPackages() const
{
if ( !m_rootItem )
{
return PackageTreeItem::List();
}
auto items = getItemPackages( m_rootItem );
for ( auto package : m_hiddenItems )
{
if ( package->hiddenSelected() )
{
items.append( getItemPackages( package ) );
}
}
return items;
}
PackageTreeItem::List
PackageNetModel::getItemPackages( PackageTreeItem* item ) const
{
PackageTreeItem::List selectedPackages;
for ( int i = 0; i < item->childCount(); i++ )
{
auto* child = item->child( i );
if ( child->isSelected() == Qt::Unchecked )
{
continue;
}
if ( child->isPackage() ) // package
{
selectedPackages.append( child );
}
else
{
selectedPackages.append( getItemPackages( child ) );
}
}
return selectedPackages;
}
void
PackageNetModel::setupModelData( const QVariantList& groupList, PackageTreeItem* parent )
{
for ( const auto& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( groupMap.isEmpty() )
{
continue;
}
PackageTreeItem* item = new PackageTreeItem( groupMap, PackageTreeItem::GroupTag { parent } );
if ( groupMap.contains( "selected" ) )
{
item->setSelected( Calamares::getBool( groupMap, "selected", false ) ? Qt::Checked : Qt::Unchecked );
}
if ( groupMap.contains( "packages" ) )
{
for ( const auto& packageName : groupMap.value( "packages" ).toList() )
{
if ( Calamares::typeOf( packageName ) == Calamares::StringVariantType )
{
item->appendChild( new PackageTreeItem( packageName.toString(), item ) );
}
else
{
QVariantMap m = packageName.toMap();
if ( !m.isEmpty() )
{
item->appendChild( new PackageTreeItem( m, PackageTreeItem::PackageTag { item } ) );
}
}
}
if ( !item->childCount() )
{
cWarning() << "*packages* under" << item->name() << "is empty.";
}
}
if ( groupMap.contains( "subgroups" ) )
{
bool haveWarned = false;
const auto& subgroupValue = groupMap.value( "subgroups" );
if ( !subgroupValue.canConvert< QVariantList >() )
{
cWarning() << "*subgroups* under" << item->name() << "is not a list.";
haveWarned = true;
}
QVariantList subgroups = groupMap.value( "subgroups" ).toList();
if ( !subgroups.isEmpty() )
{
setupModelData( subgroups, item );
// The children might be checked while the parent isn't (yet).
// Children are added to their parent (below) without affecting
// the checked-state -- do it manually. Items with subgroups
// but no children have only hidden children -- those get
// handled specially.
if ( item->childCount() > 0 )
{
item->updateSelected();
}
}
else
{
if ( !haveWarned )
{
cWarning() << "*subgroups* list under" << item->name() << "is empty.";
}
}
}
if ( item->isHidden() )
{
m_hiddenItems.append( item );
if ( !item->isSelected() )
{
cWarning() << "Item" << ( item->parentItem() ? item->parentItem()->name() : QString() ) << '.'
<< item->name() << "is hidden, but not selected.";
}
}
else
{
item->setCheckable( true );
parent->appendChild( item );
}
}
}
void
PackageNetModel::setupModelData( const QVariantList& l )
{
beginResetModel();
delete m_rootItem;
m_rootItem = new PackageTreeItem();
setupModelData( l, m_rootItem );
endResetModel();
}
void
PackageNetModel::appendModelData( const QVariantList& groupList )
{
if ( m_rootItem )
{
beginResetModel();
const QStringList sources = collectSources( groupList );
if ( !sources.isEmpty() )
{
// Prune any existing data from the same source
QList< int > removeList;
for ( int i = 0; i < m_rootItem->childCount(); i++ )
{
PackageTreeItem* child = m_rootItem->child( i );
if ( sources.contains( child->source() ) )
{
removeList.insert( 0, i );
}
}
for ( const int& item : qAsConst( removeList ) )
{
m_rootItem->removeChild( item );
}
}
// Add the new data to the model
setupModelData( groupList, m_rootItem );
endResetModel();
}
}

View File

@@ -0,0 +1,214 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGEMODEL_H
#define PACKAGEMODEL_H
#include "locale/TranslatableConfiguration.h"
#include "utils/NamedEnum.h"
#include "PackageTreeItem.h"
#include <QAbstractItemModel>
#include <QString>
#include <QAbstractListModel>
#include <QObject>
#include <QPixmap>
#include <QVector>
struct PackageItem
{
QString id;
Calamares::Locale::TranslatedString name;
Calamares::Locale::TranslatedString description;
QPixmap screenshot;
QStringList packageNames;
QVariantMap netinstallData;
/// @brief Create blank PackageItem
PackageItem();
/** @brief Creates a PackageItem from given strings
*
* This constructor sets all the text members,
* but leaves the screenshot blank. Set that separately.
*/
PackageItem( const QString& id, const QString& name, const QString& description );
/** @brief Creates a PackageItem from given strings.
*
* Set all the text members and load the screenshot from the given
* @p screenshotPath, which may be a QRC path (:/path/in/qrc) or
* a filesystem path, whatever QPixmap understands.
*/
PackageItem( const QString& id, const QString& name, const QString& description, const QString& screenshotPath );
/** @brief Creates a PackageItem from a QVariantMap
*
* This is intended for use when loading PackageItems from a
* configuration map. It will look up the various keys in the map
* and handle translation strings as well.
*
* The following keys are used:
* - *id*: the identifier for this item; if it is the empty string
* then this is the special "no-package".
* - *name* (and *name[lang]*): for the name and its translations
* - *description* (and *description[lang]*)
* - *screenshot*: a path to a screenshot for this package
* - *packages*: a list of package names
*/
PackageItem( const QVariantMap& map );
/** @brief Is this item valid?
*
* A valid item has an untranslated name available.
*/
bool isValid() const { return !name.isEmpty(); }
/** @brief Is this a (the) No-Package package?
*
* There should be at most one No-Package item in a collection
* of PackageItems. That one will be used to describe a
* "no package" situation.
*/
bool isNonePackage() const { return id.isEmpty(); }
};
using PackageList = QVector< PackageItem >;
class PackageListModel : public QAbstractListModel
{
public:
PackageListModel( PackageList&& items, QObject* parent );
PackageListModel( QObject* parent );
~PackageListModel() override;
/** @brief Add a package @p to the model
*
* Only valid packages are added -- that is, they must have a name.
*/
void addPackage( PackageItem&& p );
int rowCount( const QModelIndex& index ) const override;
QVariant data( const QModelIndex& index, int role ) const override;
/// @brief Direct (non-abstract) access to package data
const PackageItem& packageData( int r ) const { return m_packages[ r ]; }
/// @brief Direct (non-abstract) count of package data
int packageCount() const { return m_packages.count(); }
/** @brief Does a name lookup (based on id) and returns the packages member
*
* If there is a package with the given @p id, returns its packages
* (e.g. the names of underlying packages to install for it); returns
* an empty list if the id is not found.
*/
QStringList getInstallPackagesForName( const QString& id ) const;
/** @brief Name-lookup all the @p ids and returns the packages members
*
* Concatenates installPackagesForName() for each id in @p ids.
*/
QStringList getInstallPackagesForNames( const QStringList& ids ) const;
/** @brief Does a name lookup (based on id) and returns the netinstall data
*
* If there is a package with an id in @p ids, returns their netinstall data
*
* returns a list of netinstall data or an emply list if none is found
*/
QVariantList getNetinstallDataForNames( const QStringList& ids ) const;
enum Roles : int
{
NameRole = Qt::DisplayRole,
DescriptionRole = Qt::UserRole,
ScreenshotRole,
IdRole
};
private:
PackageList m_packages;
};
namespace YAML
{
class Node;
} // namespace YAML
class PackageNetModel : public QAbstractItemModel
{
Q_OBJECT
public:
// Names for columns (unused in the code)
static constexpr const int NameColumn = 0;
static constexpr const int DescriptionColumn = 1;
/* The only interesting roles are DisplayRole (with text depending
* on the column, and MetaExpandRole which tells if an index
* should be initially expanded.
*/
static constexpr const int MetaExpandRole = Qt::UserRole + 1;
explicit PackageNetModel( QObject* parent = nullptr );
~PackageNetModel() override;
void setupModelData( const QVariantList& l );
QVariant data( const QModelIndex& index, int role ) const override;
bool setData( const QModelIndex& index, const QVariant& value, int role = Qt::EditRole ) override;
Qt::ItemFlags flags( const QModelIndex& index ) const override;
QModelIndex index( int row, int column, const QModelIndex& parent = QModelIndex() ) const override;
QModelIndex parent( const QModelIndex& index ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
int rowCount( const QModelIndex& parent = QModelIndex() ) const override;
int columnCount( const QModelIndex& parent = QModelIndex() ) const override;
/** @brief Sets the checked flag on matching groups in the tree
*
* Recursively traverses the tree pointed to by m_rootItem and
* checks if a group name matches any of the items in @p selectNames.
* If a match is found, set check the box for that group and it's children.
*
* Individual packages will not be matched.
*
*/
void setSelections( const QStringList& selectNames );
PackageTreeItem::List getPackages() const;
PackageTreeItem::List getItemPackages( PackageTreeItem* item ) const;
/** @brief Appends groups to the tree
*
* Uses the data from @p groupList to add elements to the
* existing tree that m_rootItem points to. If m_rootItem
* is not valid, it does nothing
*
* Before adding anything to the model, it ensures that there
* is no existing data from the same source. If there is, that
* data is pruned first
*
*/
void appendModelData( const QVariantList& groupList );
private:
friend class ItemTests;
void setupModelData( const QVariantList& l, PackageTreeItem* parent );
PackageTreeItem* m_rootItem = nullptr;
PackageTreeItem::List m_hiddenItems;
};
#endif

View File

@@ -0,0 +1,311 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2017 Kyle Robbertze <kyle@aims.ac.za>
* SPDX-FileCopyrightText: 2017 2020, Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageTreeItem.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
/** @brief Should a package be selected, given its parent's state? */
static Qt::CheckState
parentCheckState( PackageTreeItem* parent )
{
if ( parent )
{
// Avoid partially-checked .. a package can't be partial
return parent->isSelected() == Qt::Unchecked ? Qt::Unchecked : Qt::Checked;
}
else
{
return Qt::Unchecked;
}
}
/** @brief Should a subgroup be marked critical?
*
* If set explicitly, then use that, otherwise use the parent's critical-ness.
*/
static bool
parentCriticality( const QVariantMap& groupData, PackageTreeItem* parent )
{
if ( groupData.contains( "critical" ) )
{
return Calamares::getBool( groupData, "critical", false );
}
return parent ? parent->isCritical() : false;
}
PackageTreeItem::PackageTreeItem( const QString& packageName, PackageTreeItem* parent )
: m_parentItem( parent )
, m_packageName( packageName )
, m_selected( parentCheckState( parent ) )
, m_isGroup( false )
, m_isCritical( parent ? parent->isCritical() : false )
, m_showReadOnly( parent ? parent->isImmutable() : false )
, m_showNoncheckable( false )
{
}
PackageTreeItem::PackageTreeItem( const QVariantMap& groupData, PackageTag&& parent )
: m_parentItem( parent.parent )
, m_packageName( Calamares::getString( groupData, "name" ) )
, m_selected( parentCheckState( parent.parent ) )
, m_description( Calamares::getString( groupData, "description" ) )
, m_isGroup( false )
, m_isCritical( parent.parent ? parent.parent->isCritical() : false )
, m_showReadOnly( parent.parent ? parent.parent->isImmutable() : false )
, m_showNoncheckable( false )
{
}
PackageTreeItem::PackageTreeItem( const QVariantMap& groupData, GroupTag&& parent )
: m_parentItem( parent.parent )
, m_name( Calamares::getString( groupData, "name" ) )
, m_selected( parentCheckState( parent.parent ) )
, m_description( Calamares::getString( groupData, "description" ) )
, m_preScript( Calamares::getString( groupData, "pre-install" ) )
, m_postScript( Calamares::getString( groupData, "post-install" ) )
, m_source( Calamares::getString( groupData, "source" ) )
, m_isGroup( true )
, m_isCritical( parentCriticality( groupData, parent.parent ) )
, m_isHidden( Calamares::getBool( groupData, "hidden", false ) )
, m_showReadOnly( Calamares::getBool( groupData, "immutable", false ) )
, m_showNoncheckable( Calamares::getBool( groupData, "noncheckable", false ) )
, m_startExpanded( Calamares::getBool( groupData, "expanded", false ) )
{
}
PackageTreeItem::PackageTreeItem::PackageTreeItem()
: m_parentItem( nullptr )
, m_name( QStringLiteral( "<root>" ) )
, m_selected( Qt::Checked )
, m_isGroup( true )
{
}
PackageTreeItem::~PackageTreeItem()
{
qDeleteAll( m_childItems );
}
void
PackageTreeItem::appendChild( PackageTreeItem* child )
{
m_childItems.append( child );
}
PackageTreeItem*
PackageTreeItem::child( int row )
{
return m_childItems.value( row );
}
int
PackageTreeItem::childCount() const
{
return m_childItems.count();
}
int
PackageTreeItem::row() const
{
if ( m_parentItem )
{
return m_parentItem->m_childItems.indexOf( const_cast< PackageTreeItem* >( this ) );
}
return 0;
}
QVariant
PackageTreeItem::data( int column ) const
{
switch ( column )
{
case 0:
// packages have a packagename, groups don't
return QVariant( isPackage() ? packageName() : name() );
case 1:
// packages often have a blank description
return QVariant( description() );
default:
return QVariant();
}
}
PackageTreeItem*
PackageTreeItem::parentItem()
{
return m_parentItem;
}
const PackageTreeItem*
PackageTreeItem::parentItem() const
{
return m_parentItem;
}
bool
PackageTreeItem::hiddenSelected() const
{
if ( !m_isHidden )
{
return m_selected != Qt::Unchecked;
}
if ( m_selected == Qt::Unchecked )
{
return false;
}
const PackageTreeItem* currentItem = parentItem();
while ( currentItem != nullptr )
{
if ( !currentItem->isHidden() )
{
return currentItem->isSelected() != Qt::Unchecked;
}
currentItem = currentItem->parentItem();
}
/* Has no non-hidden parents */
return m_selected != Qt::Unchecked;
}
void
PackageTreeItem::setSelected( Qt::CheckState isSelected )
{
if ( parentItem() == nullptr )
{
// This is the root, it is always checked so don't change state
return;
}
m_selected = isSelected;
setChildrenSelected( isSelected );
// Look for suitable parent item which may change checked-state
// when one of its children changes.
PackageTreeItem* currentItem = parentItem();
while ( ( currentItem != nullptr ) && ( currentItem->childCount() == 0 ) )
{
currentItem = currentItem->parentItem();
}
if ( currentItem == nullptr )
{
// Reached the root .. don't bother
return;
}
currentItem->updateSelected();
}
void
PackageTreeItem::updateSelected()
{
// Figure out checked-state based on the children
int childrenSelected = 0;
int childrenPartiallySelected = 0;
for ( int i = 0; i < childCount(); i++ )
{
if ( child( i )->isSelected() == Qt::Checked )
{
childrenSelected++;
}
if ( child( i )->isSelected() == Qt::PartiallyChecked )
{
childrenPartiallySelected++;
}
}
if ( !childrenSelected && !childrenPartiallySelected )
{
setSelected( Qt::Unchecked );
}
else if ( childrenSelected == childCount() )
{
setSelected( Qt::Checked );
}
else
{
setSelected( Qt::PartiallyChecked );
}
}
void
PackageTreeItem::setChildrenSelected( Qt::CheckState isSelected )
{
if ( isSelected != Qt::PartiallyChecked )
{
// Children are never root; don't need to use setSelected on them.
for ( auto child : m_childItems )
{
child->m_selected = isSelected;
child->setChildrenSelected( isSelected );
}
}
}
void
PackageTreeItem::removeChild( int row )
{
if ( 0 <= row && row < m_childItems.count() )
{
m_childItems.removeAt( row );
}
else
{
cWarning() << "Attempt to remove invalid child in removeChild() at row " << row;
}
}
int
PackageTreeItem::type() const
{
return QStandardItem::UserType;
}
QVariant
PackageTreeItem::toOperation() const
{
// If it's a package with a pre- or post-script, replace
// with the more complicated datastructure.
if ( !m_preScript.isEmpty() || !m_postScript.isEmpty() )
{
QMap< QString, QVariant > sdetails;
sdetails.insert( "pre-script", m_preScript );
sdetails.insert( "package", m_packageName );
sdetails.insert( "post-script", m_postScript );
return sdetails;
}
else
{
return m_packageName;
}
}
bool
PackageTreeItem::operator==( const PackageTreeItem& rhs ) const
{
if ( isGroup() != rhs.isGroup() )
{
// Different kinds
return false;
}
if ( isGroup() )
{
return name() == rhs.name() && description() == rhs.description() && preScript() == rhs.preScript()
&& postScript() == rhs.postScript() && isCritical() == rhs.isCritical() && isHidden() == rhs.isHidden()
&& m_showReadOnly == rhs.m_showReadOnly && expandOnStart() == rhs.expandOnStart();
}
else
{
return packageName() == rhs.packageName();
}
}

View File

@@ -0,0 +1,179 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2017 Kyle Robbertze <kyle@aims.ac.za>
* SPDX-FileCopyrightText: 2017 2020, Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGETREEITEM_H
#define PACKAGETREEITEM_H
#include <QList>
#include <QStandardItem>
#include <QVariant>
class PackageTreeItem : public QStandardItem
{
public:
using List = QList< PackageTreeItem* >;
///@brief A tag class to distinguish package-from-map from group-from-map
struct PackageTag
{
PackageTreeItem* parent;
};
///@brief A tag class to distinguish group-from-map from package-from-map
struct GroupTag
{
PackageTreeItem* parent;
};
///@brief A package (individual package)
explicit PackageTreeItem( const QString& packageName, PackageTreeItem* parent = nullptr );
///@brief A package (individual package with description)
explicit PackageTreeItem( const QVariantMap& packageData, PackageTag&& parent );
///@brief A group (sub-items and sub-groups are ignored)
explicit PackageTreeItem( const QVariantMap& groupData, GroupTag&& parent );
///@brief A root item, always selected, named "<root>"
explicit PackageTreeItem();
~PackageTreeItem() override;
void appendChild( PackageTreeItem* child );
PackageTreeItem* child( int row );
int childCount() const;
QVariant data( int column ) const override;
int row() const;
PackageTreeItem* parentItem();
const PackageTreeItem* parentItem() const;
QString name() const { return m_name; }
QString packageName() const { return m_packageName; }
QString description() const { return m_description; }
QString preScript() const { return m_preScript; }
QString postScript() const { return m_postScript; }
QString source() const { return m_source; }
/** @brief Is this item a group-item?
*
* Groups have a (possibly empty) list of packages, and a
* (possibly empty) list of sub-groups, and can be marked
* critical, hidden, etc. Packages, on the other hand, only
* have a meaningful packageName() and selection status.
*
* Root is a group.
*/
bool isGroup() const { return m_isGroup; }
/// @brief Is this item a single package?
bool isPackage() const { return !isGroup(); }
/** @brief Is this item hidden?
*
* Hidden items (generally only groups) are maintained separately,
* not shown to the user, but do enter into the package-installation process.
*/
bool isHidden() const { return m_isHidden; }
/** @brief Is this hidden item, considered "selected"?
*
* This asserts when called on a non-hidden item.
* A hidden item has its own selected state, but really
* falls under the selectedness of the parent item.
*/
bool hiddenSelected() const;
/** @brief Is this group critical?
*
* A critical group must be successfully installed, for the Calamares
* installation to continue.
*/
bool isCritical() const { return m_isCritical; }
/** @brief Is this group expanded on start?
*
* This does not affect installation, only the UI. A group
* that expands on start is shown expanded (not collapsed)
* in the treeview when the page is loaded.
*/
bool expandOnStart() const { return m_startExpanded; }
/** @brief Is this an immutable item?
*
* Groups can be immutable: then you can't toggle the selected
* state of any of its items.
*/
bool isImmutable() const { return m_showReadOnly; }
/** @brief Is this a non-checkable item?
*
* Groups can be non-checkable: then you can't toggle the selected
* state of the group. This does not affect subgroups or packages.
*/
bool isNoncheckable() const { return m_showNoncheckable; }
/** @brief is this item selected?
*
* Groups may be partially selected; packages are only on or off.
*/
Qt::CheckState isSelected() const { return m_selected; }
/** @brief Turns this item into a variant for PackageOperations use
*
* For "plain" items, this is just the package name; items with
* scripts return a map. See the package module for how it's interpreted.
*/
QVariant toOperation() const;
void setSelected( Qt::CheckState isSelected );
void setChildrenSelected( Qt::CheckState isSelected );
void removeChild( int row );
/** @brief Update selectedness based on the children's states
*
* This only makes sense for groups, which might have packages
* or subgroups; it checks only direct children.
*/
void updateSelected();
// QStandardItem methods
int type() const override;
/** @brief Are two items equal
*
* This **disregards** parent-item and the child-items, and compares
* only the fields for the items-proper (name, .. expanded). Note
* also that *isSelected()* is a run-time state, and is **not**
* compared either.
*/
bool operator==( const PackageTreeItem& rhs ) const;
bool operator!=( const PackageTreeItem& rhs ) const { return !( *this == rhs ); }
private:
PackageTreeItem* m_parentItem;
List m_childItems;
// An entry can be a package, or a group.
QString m_name;
QString m_packageName;
Qt::CheckState m_selected = Qt::Unchecked;
// These are only useful for groups
QString m_description;
QString m_preScript;
QString m_postScript;
QString m_source;
bool m_isGroup = false;
bool m_isCritical = false;
bool m_isHidden = false;
bool m_showReadOnly = false;
bool m_showNoncheckable = false;
bool m_startExpanded = false;
};
#endif // PACKAGETREEITEM_H

View File

@@ -0,0 +1,84 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Tests.h"
#ifdef HAVE_APPDATA
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM_VERSION
#include "ItemAppStream.h"
#endif
#include "PackageModel.h"
#include "utils/Logger.h"
#include <QtTest/QtTest>
QTEST_MAIN( PackageChooserTests )
PackageChooserTests::PackageChooserTests() {}
PackageChooserTests::~PackageChooserTests() {}
void
PackageChooserTests::initTestCase()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
}
void
PackageChooserTests::testBogus()
{
QVERIFY( true );
}
void
PackageChooserTests::testAppData()
{
// Path from the build-dir and from the running-the-test varies,
// for in-source build, for build/, and for tests-in-build/,
// so look in multiple places.
QString appdataName( "io.calamares.calamares.appdata.xml" );
for ( const auto& prefix : QStringList { "", "../", "../../../", "../../../../" } )
{
if ( QFile::exists( prefix + appdataName ) )
{
appdataName = prefix + appdataName;
break;
}
}
QVERIFY( QFile::exists( appdataName ) );
QVariantMap m;
m.insert( "appdata", appdataName );
#ifdef HAVE_XML
PackageItem p1 = fromAppData( m );
QVERIFY( p1.isValid() );
QCOMPARE( p1.id, QStringLiteral( "io.calamares.calamares.desktop" ) );
QCOMPARE( p1.name.get(), QStringLiteral( "Calamares" ) );
// The <description> entry has precedence
QCOMPARE( p1.description.get(), QStringLiteral( "Calamares is an installer program for Linux distributions." ) );
// .. but en_GB doesn't have an entry in description, so uses <summary>
QCOMPARE( p1.description.get( QLocale( "en_GB" ) ), QStringLiteral( "Calamares Linux Installer" ) );
QCOMPARE( p1.description.get( QLocale( "nl" ) ),
QStringLiteral( "Calamares is een installatieprogramma voor Linux distributies." ) );
QVERIFY( p1.screenshot.isNull() );
m.insert( "id", "calamares" );
m.insert( "screenshot", ":/images/calamares.png" );
PackageItem p2 = fromAppData( m );
QVERIFY( p2.isValid() );
QCOMPARE( p2.id, QStringLiteral( "calamares" ) );
QCOMPARE( p2.description.get( QLocale( "nl" ) ),
QStringLiteral( "Calamares is een installatieprogramma voor Linux distributies." ) );
QVERIFY( !p2.screenshot.isNull() );
#endif
}

View File

@@ -0,0 +1,28 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERTESTS_H
#define PACKAGECHOOSERTESTS_H
#include <QObject>
class PackageChooserTests : public QObject
{
Q_OBJECT
public:
PackageChooserTests();
~PackageChooserTests() override;
private Q_SLOTS:
void initTestCase();
void testBogus();
void testAppData();
};
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2014 Uri Herrera <uri_herrera@nitrux.in> and others
SPDX-License-Identifier: LGPL-3.0-or-later

View File

@@ -0,0 +1,172 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Configuration for the low-density software chooser
---
# Software selection mode, to set whether the software packages
# can be chosen singly, or multiply.
#
# Possible modes are "optional", "required" (for zero-or-one or exactly-one)
# or "optionalmultiple", "requiredmultiple" (for zero-or-more
# or one-or-more).
mode: required
# Software installation method:
#
# - "legacy" or "custom" or "contextualprocess"
# When set to "legacy", writes a GlobalStorage value for the choice that
# has been made. The key is *packagechooser_<id>*. The module's
# instance name is used; see the *instances* section of `settings.conf`.
# If there is just one packagechooser module, and no special instance is set,
# resulting GS key is probably *packagechooser_packagechooser*.
#
# The GS value is a comma-separated list of the IDs of the selected
# packages, or an empty string if none is selected.
#
# With "legacy" installation, you should have a contextualprocess or similar
# module somewhere in the `exec` phase to process the GlobalStorage key
# and actually **do** something for the packages.
#
# - "packages"
# When set to "packages", writes GlobalStorage values suitable for
# consumption by the *packages* module (which should appear later
# in the `exec` section. These package settings will then be handed
# off to whatever package manager is configured there.
#
# - "netinstall-select"
# When this is set, the id(s) selected are passed to the netinstall module.
# Any id that matches a group name in that module is set to checked
#
# - "netinstall-add"
# With this method, the packagechooser module is used to add groups to the
# netinstall module. For this to have any effect. You must set netinstall,
# which is described below.
#
# There is no need to put this module in the `exec` section. There
# are no jobs that this module provides. You should put **other**
# modules, either *contextualprocess* or *packages* or some custom
# module, in the `exec` section to do the actual work.
method: legacy
# Human-visible strings in this module. These are all optional.
# The following translated keys are used:
# - *step*, used in the overall progress view (left-hand pane)
#
# Each key can have a [locale] added to it, which is used as
# the translated string for that locale. For the strings
# associated with the "no-selection" item, see *items*, below
# with the explicit item-*id* "".
#
labels:
step: "Packages"
step[nl]: "Pakketten"
# (Optional) item-*id* of pre-selected list-view item.
# Pre-selects one of the items below.
# default: kde
# Items to display in the chooser. In general, this should be a
# pretty short list to avoid overwhelming the UI. This is a list
# of objects, and the items are displayed in list order.
#
# Either provide the data for an item in the list (using the keys
# below), or use existing AppData XML files, or use AppStream cache
# as a source for the data.
#
# For data provided by the list: the item has an id, which is used in
# setting the value of *packagechooser_<module-id>*. The following field
# is mandatory:
#
# - *id*
# ID for the product. The ID "" is special, and is used for
# "no package selected". Only include this if the mode allows
# selecting none. The name and description given for the "no package
# selected" item are displayed when the module starts.
#
# Each item must adhere to one of three "styles" of item. Which styles
# are supported depends on compile-time dependencies of Calamares.
# Both AppData and AppStream may **optionally** be available.
#
# # Generic Items #
#
# These items are always supported. They require the most configuration
# **in this file** and duplicate information that may be available elsewhere
# (e.g. in AppData or AppStream), but do not require any additional
# dependencies. These items have the following **mandatory** fields:
#
# - *name*
# Human-readable name of the product. To provide translations,
# add a *[lang]* decoration as part of the key name, e.g. `name[nl]`
# for Dutch. The list of usable languages can be found in
# `CMakeLists.txt` or as part of the debug output of Calamares.
# - *description*
# Human-readable description. These can be translated as well.
# - *screenshot*
# Path to a single screenshot of the product. May be a filesystem
# path or a QRC path, e.g. ":/images/no-selection.png". If the path
# is not found (e.g. is a non-existent absolute path, or is a relative
# path that does not exist in the current working directory) then
# an additional attempt is made to load the image from the **branding**
# directory.
#
# The following fields are **optional** for an item:
#
# - *packages* :
# List of package names for the product. If using the *method*
# "packages", consider this item mandatory (because otherwise
# selecting the item would install no packages).
#
# - *netinstall* :
# The data in this field should follow the format of a group
# from the netinstall module documented in
# src/modules/netinstall/netinstall.conf. This is only used
# when method is set to "netinstall-add"
#
# # AppData Items #
#
# For data provided by AppData XML: the item has an *appdata*
# key which points to an AppData XML file in the local filesystem.
# This file is parsed to provide the id (from AppData id), name
# (from AppData name), description (from AppData description paragraphs
# or the summary entries), and a screenshot (the default screenshot
# from AppData). No package is set (but that is unused anyway).
#
# AppData may contain IDs that are not useful inside Calamares,
# and the screenshot URL may be remote -- a remote URL will not
# be loaded and the screenshot will be missing. An item with *appdata*
# **may** specify an ID or screenshot path, as above. This will override
# the settings from AppData.
#
# # AppStream Items #
#
# For data provided by AppStream cache: the item has an *appstream*
# key which matches the AppStream identifier in the cache (e.g.
# *org.kde.kwrite.desktop*). Data is retrieved from the AppStream
# cache for that ID. The package name is set from the AppStream data.
#
# An item for AppStream may also contain an *id* and a *screenshot*
# key which will override the data from AppStream.
items:
- id: ""
# packages: [] # This item installs no packages
name: "No Desktop"
name[nl]: "Geen desktop"
description: "Please pick a desktop environment from the list. If you don't want to install a desktop, that's fine, your system will start up in text-only mode and you can install a desktop environment later."
description[nl]: "Kies eventueel een desktop-omgeving uit deze lijst. Als u geen desktop-omgeving wenst te gebruiken, kies er dan geen. In dat geval start het systeem straks op in tekst-modus en kunt u later alsnog een desktop-omgeving installeren."
screenshot: ":/images/no-selection.png"
- id: kde
packages: [ kde-frameworks, kde-plasma, kde-gear ]
name: Plasma Desktop
description: "KDE Plasma Desktop, simple by default, a clean work area for real-world usage which intends to stay out of your way. Plasma is powerful when needed, enabling the user to create the workflow that makes them more effective to complete their tasks."
screenshot: ":/images/kde.png"
- id: gnome
packages: [ gnome-all ]
name: GNOME
description: GNU Networked Object Modeling Environment Desktop
screenshot: ":/images/gnome.png"
- id: calamares
appdata: ../io.calamares.calamares.appdata.xml
screenshot: ":/images/calamares.png"
- id: kate
appstream: org.kde.kwrite.desktop

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>images/if.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>
SPDX-FileCopyrightText: 2019 Adriaan de Groot &lt;groot@kde.org&gt;
SPDX-License-Identifier: GPL-3.0-or-later
</author>
<class>PackageChooserPage</class>
<widget class="QWidget" name="PackageChooserPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>500</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,4">
<item>
<widget class="QListView" name="products">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,30,1">
<item>
<widget class="QLabel" name="productName">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Product Name</string>
</property>
</widget>
</item>
<item>
<widget class="FixedAspectRatioLabel" name="productScreenshot">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="productDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Long Product Description</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FixedAspectRatioLabel</class>
<extends>QLabel</extends>
<header>widgets/FixedAspectRatioLabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

130
modules/refind/main.py Normal file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Calamares is Free Software: see the License-Identifier above.
#
import libcalamares
import os
import subprocess
from libcalamares.utils import check_target_env_call
import gettext
_ = gettext.translation("calamares-python",
localedir=libcalamares.utils.gettext_path(),
languages=libcalamares.utils.gettext_languages(),
fallback=True).gettext
def pretty_name():
return _("Install rEFInd.")
def get_uuid():
partitions = libcalamares.globalstorage.value("partitions")
for partition in partitions:
if partition["mountPoint"] == "/":
libcalamares.utils.debug(partition["uuid"])
return partition["uuid"]
return None
def update_conf(uuid, conf_path):
"""
Updates the created rEFInd configuration file based on given parameters.
"""
partitions = libcalamares.globalstorage.value("partitions")
kernel_params = ["quiet", "systemd.show_status=0"]
swap = None # Partition UUID
swap_luks = None # LUKS name
cryptdevice_params = []
btrfs_params = ""
for partition in partitions:
if partition["fs"] == "linuxswap" and not "luksMapperName" in partition:
swap = partition["uuid"]
if partition["fs"] == "linuxswap" and "luksMapperName" in partition:
swap_luks = partition["luksMapperName"]
if partition["mountPoint"] == "/" and "luksMapperName" in partition:
cryptdevice_params = [
"cryptdevice=UUID={!s}:{!s}".format(partition["luksUuid"],
partition["luksMapperName"]),
"root=/dev/mapper/{!s}".format(partition["luksMapperName"]),
"resume=/dev/mapper/{!s}".format(partition["luksMapperName"])
]
# rEFInd with a BTRFS root filesystem needs to be told
# about the root subvolume.
if partition["mountPoint"] == "/" and partition["fs"] == "btrfs":
btrfs_params = "rootflags=subvol=@"
if cryptdevice_params:
kernel_params.extend(cryptdevice_params)
else:
kernel_params.append("root=UUID={!s}".format(uuid))
if swap:
kernel_params.append("resume=UUID={!s}".format(swap))
if swap_luks:
kernel_params.append("resume=/dev/mapper/{!s}".format(swap_luks))
if btrfs_params:
kernel_params.append(btrfs_params)
with open(conf_path, "r") as refind_file:
filedata = [x.strip() for x in refind_file.readlines()]
with open(conf_path, 'w') as refind_file:
for line in filedata:
if line.startswith('"Boot with standard options"'):
line = '"Boot with standard options" "rw {!s}"'.format(" ".join(kernel_params))
refind_file.write(line + "\n")
def efi_partitions(efi_boot_path):
"""
The (one) partition mounted on @p efi_boot_path, or an empty list.
"""
return [p for p in libcalamares.globalstorage.value("partitions") if p["mountPoint"] == efi_boot_path]
def install_refind():
install_path = libcalamares.globalstorage.value("rootMountPoint")
uuid = get_uuid()
conf_path = os.path.join(install_path, "boot/refind_linux.conf")
# TODO: some distro's use /boot/efi , so maybe this needs to
# become configurable (that depends on what rEFInd likes).
efi_boot_path = "/boot"
# Might not have a /boot configured in the system at all; warn and don't operate
if not efi_partitions(efi_boot_path):
libcalamares.utils.warning("No partition mounted on {!s}".format(efi_boot_path))
# This isn't returned as an error, but the installation
# probably won't boot because no bootloader was installed.
return None
subprocess.call(
["refind-install", "--root", "{!s}".format(install_path)])
update_conf(uuid, conf_path)
def run():
"""
Optional entry for when providing bootloader choices.
Values taken from a packagechooser instance.
Module won't run, if value not present.
"""
bootchoice = libcalamares.globalstorage.value("packagechooser_bootchoice")
if bootchoice == "refind":
return install_refind()

View File

@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "refind"
interface: "python"
script: "main.py"
noconfig: true
# The partition module sets up the needed paths in
# global storage, which is used to decide how to install.
requiredModules: [ "partition" ]

View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
## This file should be present in the same directory as the EFISTUB kernel and initramfs files
## More info at http://www.rodsbooks.com/refind/linux.html , http://www.rodsbooks.com/efi-bootloaders/efistub.html
## File is not needed when rEFInd is installed with the `refind-install` option, it will be created automatically.
#"Boot with defaults" "root=PARTUUID=XXXXXXXX rootfstype=XXXX rw add_efi_memmap"
#"Boot to terminal" "root=PARTUUID=XXXXXXXX rootfstype=XXXX rw add_efi_memmap systemd.unit=multi-user.target"

View File

@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The slowpython module is .. just slow. It can be used # The slowpython module is .. just slow. It can be used
# in testing to allow the slideshow time to run. # in testing to allow the slideshow time to run.
--- ---

View File

@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# This is an example module for Python Job Modules. # This is an example module for Python Job Modules.
# #
# The slowpython module is just slow. It does produce # The slowpython module is just slow. It does produce

View File

@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: GPL-3.0-or-later
calamares_add_plugin( unpackfsc
TYPE job
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
UnpackFSCJob.cpp
# The workers for differently-packed filesystems
Runners.cpp
FSArchiverRunner.cpp
TarballRunner.cpp
UnsquashRunner.cpp
SHARED_LIB
)

View File

@@ -0,0 +1,117 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "FSArchiverRunner.h"
#include <utils/Logger.h>
#include <utils/Runner.h>
#include <QProcess>
static constexpr const int chunk_size = 137;
static const QString&
toolName()
{
static const QString name = QStringLiteral( "fsarchiver" );
return name;
}
void
FSArchiverRunner::fsarchiverProgress( QString line )
{
m_since++;
// Typical line of output is this:
// -[00][ 99%][REGFILEM] /boot/thing
// 5 9 ^21
if ( m_since >= chunk_size && line.length() > 21 && line[ 5 ] == '[' && line[ 9 ] == '%' )
{
m_since = 0;
double p = double( line.mid( 6, 3 ).toInt() ) / 100.0;
const QString filename = line.mid( 22 );
Q_EMIT progress( p, filename );
}
}
Calamares::JobResult
FSArchiverRunner::checkPrerequisites( QString& fsarchiverExecutable ) const
{
if ( !checkToolExists( toolName(), fsarchiverExecutable ) )
{
return Calamares::JobResult::internalError(
tr( "Missing tools" ),
tr( "The <i>%1</i> tool is not installed on the system." ).arg( toolName() ),
Calamares::JobResult::MissingRequirements );
}
if ( !checkSourceExists() )
{
return Calamares::JobResult::internalError(
tr( "Invalid fsarchiver configuration" ),
tr( "The source archive <i>%1</i> does not exist." ).arg( m_source ),
Calamares::JobResult::InvalidConfiguration );
}
return Calamares::JobResult::ok();
}
Calamares::JobResult
FSArchiverRunner::checkDestination( QString& destinationPath ) const
{
destinationPath = CalamaresUtils::System::instance()->targetPath( m_destination );
if ( destinationPath.isEmpty() )
{
return Calamares::JobResult::internalError(
tr( "Invalid fsarchiver configuration" ),
tr( "No destination could be found for <i>%1</i>." ).arg( m_destination ),
Calamares::JobResult::InvalidConfiguration );
}
return Calamares::JobResult::ok();
}
Calamares::JobResult
FSArchiverDirRunner::run()
{
QString fsarchiverExecutable;
if ( auto res = checkPrerequisites( fsarchiverExecutable ); !res )
{
return res;
}
QString destinationPath;
if ( auto res = checkDestination( destinationPath ); !res )
{
return res;
}
Calamares::Utils::Runner r(
{ fsarchiverExecutable, QStringLiteral( "-v" ), QStringLiteral( "restdir" ), m_source, destinationPath } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
connect( &r, &decltype( r )::output, this, &FSArchiverDirRunner::fsarchiverProgress );
return r.run().explainProcess( toolName(), std::chrono::seconds( 0 ) );
}
Calamares::JobResult
FSArchiverFSRunner::run()
{
QString fsarchiverExecutable;
if ( auto res = checkPrerequisites( fsarchiverExecutable ); !res )
{
return res;
}
QString destinationPath;
if ( auto res = checkDestination( destinationPath ); !res )
{
return res;
}
Calamares::Utils::Runner r(
{ fsarchiverExecutable, QStringLiteral( "-v" ), QStringLiteral( "restfs" ), m_source, destinationPath } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
connect( &r, &decltype( r )::output, this, &FSArchiverFSRunner::fsarchiverProgress );
return r.run().explainProcess( toolName(), std::chrono::seconds( 0 ) );
}

View File

@@ -0,0 +1,59 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef UNPACKFSC_FSARCHIVERRUNNER_H
#define UNPACKFSC_FSARCHIVERRUNNER_H
#include "Runners.h"
/** @brief Base class for runners of FSArchiver
*
*/
class FSArchiverRunner : public Runner
{
Q_OBJECT
public:
using Runner::Runner;
protected Q_SLOTS:
void fsarchiverProgress( QString line );
protected:
/** @brief Checks prerequisites, sets full path of fsarchiver in @p executable
*/
Calamares::JobResult checkPrerequisites( QString& executable ) const;
Calamares::JobResult checkDestination( QString& destinationPath ) const;
int m_since = 0;
};
/** @brief Running FSArchiver in **dir** mode
*
*/
class FSArchiverDirRunner : public FSArchiverRunner
{
public:
using FSArchiverRunner::FSArchiverRunner;
Calamares::JobResult run() override;
};
/** @brief Running FSArchiver in **dir** mode
*
*/
class FSArchiverFSRunner : public FSArchiverRunner
{
public:
using FSArchiverRunner::FSArchiverRunner;
Calamares::JobResult run() override;
};
#endif

View File

@@ -0,0 +1,38 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Runners.h"
#include <utils/CalamaresUtilsSystem.h>
#include <utils/Logger.h>
#include <QFileInfo>
#include <QStandardPaths>
Runner::Runner( const QString& source, const QString& destination )
: m_source( source )
, m_destination( destination )
{
}
Runner::~Runner() { }
bool
Runner::checkSourceExists() const
{
QFileInfo fi( m_source );
return fi.exists() && fi.isReadable();
}
bool
Runner::checkToolExists( const QString& toolName, QString& fullPath )
{
fullPath = QStandardPaths::findExecutable( toolName );
return !fullPath.isEmpty();
}

View File

@@ -0,0 +1,48 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef UNPACKFSC_RUNNERS_H
#define UNPACKFSC_RUNNERS_H
#include <Job.h>
class Runner : public QObject
{
Q_OBJECT
public:
Runner( const QString& source, const QString& destination );
~Runner() override;
virtual Calamares::JobResult run() = 0;
/** @brief Check that the (configured) source file exists.
*
* Returns @c true if it's a file and readable.
*/
bool checkSourceExists() const;
/** @brief Check that a named tool (executable) exists in the search path.
*
* Returns @c true if the tool is found and sets @p fullPath
* to the full path of that tool; returns @c false and clears
* @p fullPath otherwise.
*/
static bool checkToolExists( const QString& toolName, QString& fullPath );
Q_SIGNALS:
// See Calamares Job::progress
void progress( qreal percent, const QString& message );
protected:
QString m_source;
QString m_destination;
};
#endif

View File

@@ -0,0 +1,86 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "TarballRunner.h"
#include <utils/Logger.h>
#include <utils/Runner.h>
#include <utils/String.h>
#include <QString>
static constexpr const int chunk_size = 107;
Calamares::JobResult
TarballRunner::run()
{
if ( !checkSourceExists() )
{
return Calamares::JobResult::internalError(
tr( "Invalid tarball configuration" ),
tr( "The source archive <i>%1</i> does not exist." ).arg( m_source ),
Calamares::JobResult::InvalidConfiguration );
}
const QString toolName = QStringLiteral( "tar" );
QString tarExecutable;
if ( !checkToolExists( toolName, tarExecutable ) )
{
return Calamares::JobResult::internalError(
tr( "Missing tools" ),
tr( "The <i>%1</i> tool is not installed on the system." ).arg( toolName ),
Calamares::JobResult::MissingRequirements );
}
const QString destinationPath = CalamaresUtils::System::instance()->targetPath( m_destination );
if ( destinationPath.isEmpty() )
{
return Calamares::JobResult::internalError(
tr( "Invalid tarball configuration" ),
tr( "No destination could be found for <i>%1</i>." ).arg( m_destination ),
Calamares::JobResult::InvalidConfiguration );
}
// Get the stats (number of inodes) from the FS
{
m_total = 0;
Calamares::Utils::Runner r( { tarExecutable, QStringLiteral( "-tf" ), m_source } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
QObject::connect( &r, &decltype( r )::output, [ & ]( QString line ) { m_total++; } );
/* ignored */ r.run();
}
if ( m_total <= 0 )
{
cWarning() << "No stats could be obtained from" << tarExecutable << "-tf" << m_source;
}
// Now do the actual unpack
{
m_processed = 0;
m_since = 0;
Calamares::Utils::Runner r(
{ tarExecutable, QStringLiteral( "-xpvf" ), m_source, QStringLiteral( "-C" ), destinationPath } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
connect( &r, &decltype( r )::output, this, &TarballRunner::tarballProgress );
return r.run().explainProcess( toolName, std::chrono::seconds( 0 ) );
}
}
void
TarballRunner::tarballProgress( QString line )
{
m_processed++;
m_since++;
if ( m_since > chunk_size )
{
m_since = 0;
double p = m_total > 0 ? ( double( m_processed ) / double( m_total ) ) : 0.5;
Q_EMIT progress( p, tr( "Tarball extract file %1" ).arg( line ) );
}
}

View File

@@ -0,0 +1,35 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef UNPACKFSC_TARBALLRUNNER_H
#define UNPACKFSC_TARBALLRUNNER_H
#include "Runners.h"
/** @brief Use (GNU) tar for extracting a filesystem
*
*/
class TarballRunner : public Runner
{
public:
using Runner::Runner;
Calamares::JobResult run() override;
protected Q_SLOTS:
void tarballProgress( QString line );
private:
// Progress reporting
int m_total = 0;
int m_processed = 0;
int m_since = 0;
};
#endif

View File

@@ -0,0 +1,130 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "UnpackFSCJob.h"
#include "FSArchiverRunner.h"
#include "TarballRunner.h"
#include "UnsquashRunner.h"
#include <utils/Logger.h>
#include <utils/NamedEnum.h>
#include <utils/RAII.h>
#include <utils/Variant.h>
#include <memory>
static const NamedEnumTable< UnpackFSCJob::Type >
typeNames()
{
using T = UnpackFSCJob::Type;
// clang-format off
static const NamedEnumTable< T > names
{
{ "none", T::None },
{ "fsarchiver", T::FSArchive },
{ "fsarchive", T::FSArchive },
{ "fsa", T::FSArchive },
{ "fsa-dir", T::FSArchive },
{ "fsa-block", T::FSArchiveFS },
{ "fsa-fs", T::FSArchiveFS },
{ "squashfs", T::Squashfs },
{ "squash", T::Squashfs },
{ "unsquash", T::Squashfs },
{ "tar", T::Tarball },
{ "tarball", T::Tarball },
{ "tgz", T::Tarball },
};
// clang-format on
return names;
}
UnpackFSCJob::UnpackFSCJob( QObject* parent )
: Calamares::CppJob( parent )
{
}
UnpackFSCJob::~UnpackFSCJob() { }
QString
UnpackFSCJob::prettyName() const
{
return tr( "Unpack filesystems" );
}
QString
UnpackFSCJob::prettyStatusMessage() const
{
return m_progressMessage;
}
Calamares::JobResult
UnpackFSCJob::exec()
{
cScopedAssignment messageClearer( &m_progressMessage, QString() );
std::unique_ptr< Runner > r;
switch ( m_type )
{
case Type::FSArchive:
r = std::make_unique< FSArchiverDirRunner >( m_source, m_destination );
break;
case Type::FSArchiveFS:
r = std::make_unique< FSArchiverFSRunner >( m_source, m_destination );
break;
case Type::Squashfs:
r = std::make_unique< UnsquashRunner >( m_source, m_destination );
break;
case Type::Tarball:
r = std::make_unique< TarballRunner >( m_source, m_destination );
break;
case Type::None:
default:
cDebug() << "Nothing to do.";
return Calamares::JobResult::ok();
}
connect( r.get(),
&Runner::progress,
[ = ]( qreal percent, const QString& message )
{
m_progressMessage = message;
Q_EMIT progress( percent );
} );
return r->run();
}
void
UnpackFSCJob::setConfigurationMap( const QVariantMap& map )
{
QString source = CalamaresUtils::getString( map, "source" );
QString sourceTypeName = CalamaresUtils::getString( map, "sourcefs" );
if ( source.isEmpty() || sourceTypeName.isEmpty() )
{
cWarning() << "Skipping item with bad source data:" << map;
return;
}
bool bogus = false;
Type sourceType = typeNames().find( sourceTypeName, bogus );
if ( sourceType == Type::None )
{
cWarning() << "Skipping item with source type None";
return;
}
QString destination = CalamaresUtils::getString( map, "destination" );
if ( destination.isEmpty() )
{
cWarning() << "Skipping item with empty destination";
return;
}
m_source = source;
m_destination = destination;
m_type = sourceType;
}
CALAMARES_PLUGIN_FACTORY_DEFINITION( UnpackFSCFactory, registerPlugin< UnpackFSCJob >(); )

View File

@@ -0,0 +1,50 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef UNPACKFSC_UNPACKFSCJOB_H
#define UNPACKFSC_UNPACKFSCJOB_H
#include <CppJob.h>
#include <DllMacro.h>
#include <utils/PluginFactory.h>
class PLUGINDLLEXPORT UnpackFSCJob : public Calamares::CppJob
{
Q_OBJECT
public:
enum class Type
{
None, /// << Invalid
FSArchive,
FSArchiveFS,
Squashfs,
Tarball,
};
explicit UnpackFSCJob( QObject* parent = nullptr );
~UnpackFSCJob() override;
QString prettyName() const override;
QString prettyStatusMessage() const override;
Calamares::JobResult exec() override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
QString m_source;
QString m_destination;
Type m_type = Type::None;
QString m_progressMessage;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( UnpackFSCFactory )
#endif

View File

@@ -0,0 +1,101 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "UnsquashRunner.h"
#include <utils/Logger.h>
#include <utils/Runner.h>
#include <utils/String.h>
#include <QString>
static constexpr const int chunk_size = 107;
Calamares::JobResult
UnsquashRunner::run()
{
if ( !checkSourceExists() )
{
return Calamares::JobResult::internalError(
tr( "Invalid unsquash configuration" ),
tr( "The source archive <i>%1</i> does not exist." ).arg( m_source ),
Calamares::JobResult::InvalidConfiguration );
}
const QString toolName = QStringLiteral( "unsquashfs" );
QString unsquashExecutable;
if ( !checkToolExists( toolName, unsquashExecutable ) )
{
return Calamares::JobResult::internalError(
tr( "Missing tools" ),
tr( "The <i>%1</i> tool is not installed on the system." ).arg( toolName ),
Calamares::JobResult::MissingRequirements );
}
const QString destinationPath = CalamaresUtils::System::instance()->targetPath( m_destination );
if ( destinationPath.isEmpty() )
{
return Calamares::JobResult::internalError(
tr( "Invalid unsquash configuration" ),
tr( "No destination could be found for <i>%1</i>." ).arg( m_destination ),
Calamares::JobResult::InvalidConfiguration );
}
// Get the stats (number of inodes) from the FS
{
m_inodes = -1;
Calamares::Utils::Runner r( { unsquashExecutable, QStringLiteral( "-s" ), m_source } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
QObject::connect( &r,
&decltype( r )::output,
[ & ]( QString line )
{
if ( line.startsWith( "Number of inodes " ) )
{
m_inodes = line.split( ' ', SplitSkipEmptyParts ).last().toInt();
}
} );
/* ignored */ r.run();
}
if ( m_inodes <= 0 )
{
cWarning() << "No stats could be obtained from" << unsquashExecutable << "-s";
}
// Now do the actual unpack
{
m_processed = 0;
Calamares::Utils::Runner r( { unsquashExecutable,
QStringLiteral( "-i" ), // List files
QStringLiteral( "-f" ), // Force-overwrite
QStringLiteral( "-d" ),
destinationPath,
m_source } );
r.setLocation( Calamares::Utils::RunLocation::RunInHost ).enableOutputProcessing();
connect( &r, &decltype( r )::output, this, &UnsquashRunner::unsquashProgress );
return r.run().explainProcess( toolName, std::chrono::seconds( 0 ) );
}
}
void
UnsquashRunner::unsquashProgress( QString line )
{
m_processed++;
m_since++;
if ( m_since > chunk_size && line.contains( '/' ) )
{
const QString filename = line.split( '/', SplitSkipEmptyParts ).last().trimmed();
if ( !filename.isEmpty() )
{
m_since = 0;
double p = m_inodes > 0 ? ( double( m_processed ) / double( m_inodes ) ) : 0.5;
Q_EMIT progress( p, tr( "Unsquash file %1" ).arg( filename ) );
}
}
}

View File

@@ -0,0 +1,36 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef UNPACKFSC_UNSQUASHRUNNER_H
#define UNPACKFSC_UNSQUASHRUNNER_H
#include "Runners.h"
/** @brief Use Unsquash for extracting a filesystem
*
*/
class UnsquashRunner : public Runner
{
public:
using Runner::Runner;
Calamares::JobResult run() override;
protected Q_SLOTS:
void unsquashProgress( QString line );
private:
int m_inodes = 0; // Total in the FS
// Progress reporting
int m_processed = 0;
int m_since = 0;
};
#endif

View File

@@ -0,0 +1,2 @@
---
rootMountPoint: /tmp/fstest

View File

@@ -0,0 +1,4 @@
---
source: /tmp/src.fsa
sourcefs: fsarchive
destination: "/calasrc"

View File

@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Unpack a filesystem. Supported ways to "pack" the filesystem are:
# - fsarchiver in *savedir/restdir* mode (directories, not block devices)
# - squashfs
#
# Configuration:
#
# from globalstorage: rootMountPoint
# from job configuration: the item to unpack
#
---
# This module is configured a lot like the items in the *unpackfs*
# module, but with only **one** item. Use multiple instances for
# unpacking more than one filesystem.
#
# There are the following **mandatory** keys:
# - *source* path relative to the live / intstalling system to the image
# - *sourcefs* the type of the source files; valid entries are
# - `none` (this entry is ignored; kind of useless)
# - `fsarchiver`
# Aliases of this are `fsarchive`, `fsa` and `fsa-dir`. Uses
# fsarchiver in "restdir" mode.
# - `fsarchiver-block`
# Aliases of this are `fsa-block` and `fsa-fs`. Uses fsarchiver
# in "restfs" mode.
# - `squashfs`
# Aliases of this are `squash` and `unsquash`.
# - `tar`
# - *destination* path relative to rootMountPoint (so in the target
# system) where this filesystem is unpacked. It may be an
# empty string, which effectively is / (the root) of the target
# system.
#
source: /data/rootfs.fsa
sourcefs: fsarchiver
destination: "/"

View File

@@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: GPL-3.0-or-later
---
$schema: https://json-schema.org/schema#
$id: https://calamares.io/schemas/unpackfsc
additionalProperties: false
type: object
properties:
unpack:
type: array
items:
type: object
additionalProperties: false
properties:
source: { type: string }
sourcefs: { type: string }
destination: { type: string }
weight: { type: integer, exclusiveMinimum: 0 }
required: [ source , sourcefs, destination ]