Compare commits
152 Commits
v1.1.0
...
packagecho
| Author | SHA1 | Date | |
|---|---|---|---|
| 674bb81358 | |||
|
|
6bd75570de | ||
|
|
acdcdea668 | ||
|
|
edb405a4d1 | ||
|
|
3ad9c221d4 | ||
|
|
dc3550eccd | ||
|
|
fac16ac6a7 | ||
|
|
a442ed8c12 | ||
|
|
e08d687061 | ||
|
|
15f6369537 | ||
|
|
f01b4ed2f8 | ||
|
|
ea762b0945 | ||
|
|
cae8acb4a4 | ||
|
|
ae204d0108 | ||
|
|
a2fae600c4 | ||
|
|
349acad491 | ||
|
|
9b4c4876bc | ||
|
|
cc02baed9a | ||
|
|
dfab089938 | ||
|
|
2dd9a7ba8a | ||
|
|
82f31f3cd9 | ||
|
|
c5b01a574d | ||
|
|
bc4789d57f | ||
|
|
0962b98494 | ||
|
|
108476c025 | ||
|
|
04a1bc9e2c | ||
|
|
bff5e485f4 | ||
|
|
3c838436c2 | ||
|
|
644c9cf4f3 | ||
|
|
dcaa378ddd | ||
|
|
14fd23dcef | ||
|
|
f4bc7052e0 | ||
|
|
4f0f48d99d | ||
|
|
1344880f2e | ||
|
|
5704e146a0 | ||
|
|
85eb434b57 | ||
|
|
4b2ea61aa3 | ||
|
|
39d25eef1c | ||
|
|
e82d32fe66 | ||
|
|
a50ab49c22 | ||
|
|
a66ab99ce8 | ||
|
|
c3170a9dfb | ||
|
|
88437d3612 | ||
|
|
6db52a904e | ||
|
|
8f0403a3f8 | ||
|
|
caf231b0d1 | ||
|
|
9ad86f81bb | ||
|
|
4d601f2e6a | ||
|
|
69f68d82e6 | ||
|
|
572a94e493 | ||
|
|
90454be1b9 | ||
|
|
6144404bd0 | ||
|
|
8f4d8d119c | ||
|
|
e8c870205d | ||
|
|
081d7d47d1 | ||
|
|
e92ecea3f5 | ||
|
|
e158402478 | ||
|
|
d242d077db | ||
|
|
5f9b46a820 | ||
|
|
9151d0fcee | ||
|
|
282f5bfade | ||
|
|
eeb264f32e | ||
|
|
83606aaf9d | ||
|
|
322a7a212f | ||
|
|
40c7496f85 | ||
|
|
f1fd52e790 | ||
|
|
12f6068622 | ||
|
|
16a3e2edb2 | ||
|
|
8fc9b907af | ||
|
|
ecaf2c3076 | ||
|
|
ea97927997 | ||
|
|
6795190216 | ||
|
|
51149e34d7 | ||
|
|
a2674c652d | ||
|
|
d4d6d17efe | ||
|
|
b74d77a9c5 | ||
|
|
5074bebd32 | ||
|
|
76be9988fe | ||
|
|
c85daf8f7b | ||
|
|
13e8b1e9b5 | ||
|
|
e6ddf30512 | ||
|
|
9938d11ad1 | ||
|
|
def4ee5c7e | ||
|
|
718f73c9c8 | ||
|
|
720415d8b7 | ||
|
|
0f0b9aa776 | ||
|
|
19d508ca2e | ||
|
|
7af1aeb132 | ||
|
|
3f7cabe832 | ||
|
|
341c9f4a7f | ||
|
|
03260efb3a | ||
|
|
18060db82d | ||
|
|
b978f03618 | ||
|
|
a4a6194a5d | ||
|
|
93cf06da82 | ||
|
|
901211c12d | ||
|
|
e89a58a34b | ||
|
|
fb36765982 | ||
|
|
f1d2bfacce | ||
|
|
9703bd9058 | ||
|
|
0e2a178436 | ||
|
|
fb70e29e29 | ||
|
|
fbfa9d8038 | ||
|
|
59ab728502 | ||
|
|
476224d34e | ||
|
|
2aaf440fec | ||
|
|
b416caa462 | ||
|
|
0c11b864e5 | ||
|
|
0f9ffb9e35 | ||
|
|
813d26ec4e | ||
|
|
5d44118b4b | ||
|
|
833e33f011 | ||
|
|
ea3a03a8cc | ||
|
|
05bbeae2c3 | ||
|
|
b9b923e69c | ||
|
|
ff3391f67b | ||
|
|
b60b21b680 | ||
|
|
8a533d22be | ||
|
|
6f32c18ef9 | ||
|
|
f36f21c55c | ||
|
|
bcacab531f | ||
|
|
85586293c8 | ||
|
|
e71eb01feb | ||
|
|
3eb3e9c98d | ||
|
|
2bf5706f73 | ||
|
|
809491c969 | ||
|
|
febeb3281e | ||
|
|
76885c7fe1 | ||
|
|
fad0575e4d | ||
|
|
550f7d40ef | ||
|
|
6c07d39374 | ||
|
|
4285ccebd7 | ||
|
|
320d67a5ba | ||
|
|
6a80ce6dab | ||
|
|
33678a6a16 | ||
|
|
2c91dc4664 | ||
|
|
b3cda8d7cb | ||
|
|
4611f2f9ea | ||
|
|
7acd4bb3d6 | ||
|
|
41b4b59a5e | ||
|
|
077320dc19 | ||
|
|
c5eeeb7a50 | ||
|
|
52882df5ee | ||
|
|
616e51b3ee | ||
|
|
f2e59e611f | ||
|
|
a532b309b7 | ||
|
|
45ae6084d8 | ||
|
|
e0cd044e5b | ||
|
|
2c1cbb7f85 | ||
|
|
bf5e3d857a | ||
|
|
e78bd936a3 | ||
|
|
a53f9359f4 |
@@ -8,6 +8,7 @@ AlignEscapedNewlines: DontAlign
|
||||
AllowAllParametersOfDeclarationOnNextLine: "false"
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: "false"
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: "false"
|
||||
AlwaysBreakAfterReturnType: TopLevelDefinitions
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
@@ -28,7 +29,6 @@ ReflowComments: "false"
|
||||
SortIncludes: "true"
|
||||
SpaceAfterCStyleCast: "false"
|
||||
SpacesBeforeTrailingComments: "2"
|
||||
# SpaceInEmptyBlock: "true"
|
||||
SpacesInAngles: "true"
|
||||
SpacesInParentheses: "true"
|
||||
SpacesInSquareBrackets: "true"
|
||||
|
||||
35
.clang-format.base
Normal 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
|
||||
60
.github/workflows/nightly-neon.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: nightly-neon-xtn
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "52 1 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BUILDDIR: /build
|
||||
SRCDIR: ${{ github.workspace }}
|
||||
CMAKE_ARGS: |
|
||||
-DWEBVIEW_FORCE_WEBKIT=1
|
||||
-DKDE_INSTALL_USE_QT_SYS_PATHS=ON
|
||||
-DWITH_PYTHONQT=OFF"
|
||||
-DCMAKE_BUILD_TYPE=Debug
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://kdeneon/plasma:user
|
||||
options: --tmpfs /build:rw --user 0:0
|
||||
steps:
|
||||
- name: "fetch artifacts"
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: nightly-neon.yml
|
||||
workflow_conclusion: success
|
||||
branch: calamares
|
||||
name: calamares-tarball
|
||||
path: ${{ env.BUILDDIR }}
|
||||
repo: calamares/calamares
|
||||
- name: "prepare env"
|
||||
uses: calamares/actions/prepare-neon@v2
|
||||
- name: "prepare source"
|
||||
uses: actions/checkout@v2
|
||||
- name: "prepare artifacts"
|
||||
run: tar xvzf "$BUILDDIR/calamares.tar.gz" -C / --strip-components 1
|
||||
- name: "build"
|
||||
id: build
|
||||
uses: calamares/actions/generic-build@v2
|
||||
- name: "notify: ok"
|
||||
if: ${{ success() && github.repository == 'calamares/calamares-extensions' }}
|
||||
uses: calamares/actions/matrix-notify@v2
|
||||
with:
|
||||
token: ${{ secrets.MATRIX_TOKEN }}
|
||||
room: ${{ secrets.MATRIX_ROOM }}
|
||||
message: "OK ${{ github.workflow }} in ${{ github.repository }} ${{ steps.build.outputs.git_summary }}"
|
||||
- name: "notify: fail"
|
||||
if: ${{ failure() && github.repository == 'calamares/calamares' }}
|
||||
uses: calamares/actions/matrix-notify@v2
|
||||
with:
|
||||
token: ${{ secrets.MATRIX_TOKEN }}
|
||||
room: ${{ secrets.MATRIX_ROOM }}
|
||||
message: "FAIL ${{ github.workflow }} in ${{ github.repository }} ${{ steps.build.outputs.git_summary}}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
66
.github/workflows/push.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: ci-push-xtn
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- calamares
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BUILDDIR: /build
|
||||
SRCDIR: ${{ github.workspace }}
|
||||
CMAKE_ARGS: |
|
||||
-DWEBVIEW_FORCE_WEBKIT=1
|
||||
-DKDE_INSTALL_USE_QT_SYS_PATHS=ON
|
||||
-DWITH_PYTHONQT=OFF"
|
||||
-DCMAKE_BUILD_TYPE=Debug
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://kdeneon/plasma:user
|
||||
options: --tmpfs /build:rw --user 0:0
|
||||
steps:
|
||||
- name: "fetch artifacts"
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: nightly-neon.yml
|
||||
workflow_conclusion: success
|
||||
branch: calamares
|
||||
name: calamares-tarball
|
||||
path: ${{ env.BUILDDIR }}
|
||||
repo: calamares/calamares
|
||||
- name: "prepare env"
|
||||
uses: calamares/actions/prepare-neon@v2
|
||||
- name: "prepare source"
|
||||
uses: actions/checkout@v2
|
||||
- name: "prepare artifacts"
|
||||
run: tar xvzf "$BUILDDIR/calamares.tar.gz" -C / --strip-components 1
|
||||
- name: "build"
|
||||
id: build
|
||||
uses: calamares/actions/generic-build@v2
|
||||
- name: "notify: ok"
|
||||
if: ${{ success() && github.repository == 'calamares/calamares-extensions' }}
|
||||
uses: calamares/actions/matrix-notify@v2
|
||||
with:
|
||||
token: ${{ secrets.MATRIX_TOKEN }}
|
||||
room: ${{ secrets.MATRIX_ROOM }}
|
||||
message: |
|
||||
OK ${{ github.workflow }} in ${{ github.repository }} by ${{ github.actor }} on ${{ github.event.ref }}
|
||||
.. ${{ steps.build.outputs.git-summary }}
|
||||
- name: "notify: fail"
|
||||
if: ${{ failure() && github.repository == 'calamares/calamares-extensions' }}
|
||||
uses: calamares/actions/matrix-notify@v2
|
||||
with:
|
||||
token: ${{ secrets.MATRIX_TOKEN }}
|
||||
room: ${{ secrets.MATRIX_ROOM }}
|
||||
message: |
|
||||
FAIL ${{ github.workflow }} in ${{ github.repository }} by ${{ github.actor }} on ${{ github.event.ref }}
|
||||
.. ${{ steps.build.outputs.git-summary }}
|
||||
.. ${{ github.event.compare }}
|
||||
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
.kdev4/
|
||||
build/
|
||||
*.kdev4
|
||||
|
||||
23
.reuse/dep5
Normal 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
|
||||
|
||||
73
CHANGES
@@ -6,8 +6,77 @@ This is the changelog for Calamares-Extensions. For each release, the major
|
||||
changes and contributors are listed. Note that Calamares-Extensions does not
|
||||
have a historical changelog -- this log starts with version 1.0.0.
|
||||
|
||||
# 1.3.2 (2023-08-28)
|
||||
|
||||
# 1.1.0 (2021-01-04) #
|
||||
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)
|
||||
|
||||
This release contains contributions from (alphabetically by first name):
|
||||
- Oliver Smith
|
||||
|
||||
Changes and new modules in this release:
|
||||
- New *os-* modules are intended for OS-specific work. They don't
|
||||
do anything concrete yet, though.
|
||||
- The *mobile* module has new features thanks to Oliver, with
|
||||
keyboard selection (numeric / alpha) for PIN / password entry among them.
|
||||
|
||||
|
||||
# 1.1.1 (2021-02-23)
|
||||
|
||||
This release contains contributions from (alphabetically by first name):
|
||||
- Oliver Smith
|
||||
- Undef
|
||||
|
||||
Changes and new modules in this release:
|
||||
- Branding has a new example, *image-slideshow*, for a QML-based
|
||||
slideshow with only images.
|
||||
- CI now builds the extensions against a recent Calamares build.
|
||||
- *mobile* add ability to select target filesystem.
|
||||
- *mobile* rename `cmdMkfsRoot` to `cmdMkfsRootExt4`.
|
||||
- *mobile* wait screen has been re-worded.
|
||||
|
||||
|
||||
# 1.1.0 (2021-01-04)
|
||||
|
||||
This release contains contributions from (alphabetically by first name):
|
||||
- Oliver Smith
|
||||
@@ -17,7 +86,7 @@ Changes and new modules in this release:
|
||||
- *mobile* module SSH daemon can be disabled
|
||||
|
||||
|
||||
# 1.0.0 (2020-12-05) #
|
||||
# 1.0.0 (2020-12-05)
|
||||
|
||||
This release contains contributions from (alphabetically by first name):
|
||||
- Oliver Smith
|
||||
|
||||
101
CMakeLists.txt
@@ -1,20 +1,15 @@
|
||||
# === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
#
|
||||
# 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.
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# 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/>.
|
||||
# Calamares-Examples is Free Software: see the License-Identifier above.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0+
|
||||
# License-Filename: LICENSE
|
||||
# Individual files may have different licenses (like the CMake
|
||||
# infrastructure, which is BSD-2-Clause licensed). Check the SPDX
|
||||
# identifiers in each file.
|
||||
#
|
||||
###
|
||||
#
|
||||
@@ -24,15 +19,56 @@
|
||||
# distro can use an unmodified (upstream) Calamares package and a local
|
||||
# customisation package in tandem.
|
||||
#
|
||||
cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
|
||||
# Besides being an example repository, it is also a collection of modules
|
||||
# and branding that is usable in its own right.
|
||||
#
|
||||
### CONFIGURING
|
||||
#
|
||||
# By default, all the branding examples and all the modules are built.
|
||||
# This can be influenced through:
|
||||
# SKIP_MODULES : a space or semicolon-separated list of directory names
|
||||
# under src/modules that should not be built.
|
||||
# USE_* : fills in SKIP_MODULES for modules called *-<something>
|
||||
# In this repository, there is just one "group" to which USE_* applies:
|
||||
# USE_os : operating-system-specific modules.
|
||||
#
|
||||
### 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
|
||||
VERSION 1.1.0
|
||||
VERSION ${CALAMARES_EXTENSIONS_VERSION}
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
find_package(Calamares 3.2.26 REQUIRED)
|
||||
set( CMAKE_CXX_STANDARD 17 )
|
||||
set( CMAKE_CXX_STANDARD_REQUIRED ON )
|
||||
|
||||
find_package(YAMLCPP REQUIRED) # Needed to untangle some dependencies before Calamares 3.2.36
|
||||
# On developer's machine, the user package registry breaks
|
||||
# consumers by loading the developer's config from a build
|
||||
# directory (which doesn't have the rest of the config
|
||||
# installed inside it).
|
||||
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)
|
||||
find_package(Calamares ${CALAMARES_VERSION_REQUIRED} REQUIRED)
|
||||
endif()
|
||||
|
||||
message(STATUS "Found Calamares version ${Calamares_VERSION}")
|
||||
message(STATUS " libraries ${Calamares_LIB_DIRS}")
|
||||
message(STATUS "")
|
||||
|
||||
### CMAKE SETUP
|
||||
#
|
||||
@@ -52,6 +88,7 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.10.0")
|
||||
"K_EXPORT_PLASMA_RUNNER"
|
||||
)
|
||||
endif()
|
||||
include( CTest )
|
||||
|
||||
|
||||
### BRANDING
|
||||
@@ -76,28 +113,18 @@ calamares_add_branding_subdirectory( branding/kaos_branding NAME kaos )
|
||||
#
|
||||
# Add one of more modules, either C++ or Python.
|
||||
#
|
||||
set(SKIPPED_MODULES "")
|
||||
set(LIST_SKIPPED_MODULES "")
|
||||
|
||||
calamares_add_module_subdirectory( modules/filekeeper ) # C++ job
|
||||
calamares_add_module_subdirectory( modules/freebsddisk ) # C++ viewmodule
|
||||
calamares_add_module_subdirectory( modules/mobile )
|
||||
calamares_add_module_subdirectory( modules/slowpython ) # Python job
|
||||
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/os-freebsd 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/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
|
||||
# which builds a list of explanations; show that list.
|
||||
calamares_explain_skipped_modules( ${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
|
||||
)
|
||||
calamares_explain_skipped_modules( ${LIST_SKIPPED_MODULES} )
|
||||
|
||||
90
CMakeModules/ExtendedVersion.cmake
Normal 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
@@ -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.
|
||||
|
||||
* [](https://webchat.kde.org/#/room/%23calamares:kde.org)
|
||||
24
LICENSES/BSD-2-Clause.txt
Normal 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
@@ -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.
|
||||
100
README.md
@@ -1,10 +1,15 @@
|
||||
<!-- SPDX-FileCopyrightText: no
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
-->
|
||||
|
||||
# Calamares Branding and Module Examples
|
||||
|
||||
> A *branding component* in Calamares is a description of the
|
||||
> produce (i.e. distribution) being installed along with a "slideshow"
|
||||
> product (i.e. distribution) being installed along with a "slideshow"
|
||||
> that is displayed during the installation phase of Calamares.
|
||||
> This shapes the **look** of your installation.
|
||||
>
|
||||
> A *module* adds functionality to Calamares; modules may be written
|
||||
> A *module* adds **functionality** to Calamares; modules may be written
|
||||
> in C++ or Python, using Qt Widgets or QML for the UI (with C++)
|
||||
> if there is one. Both C++ and Python allow a full control over the
|
||||
> target system during the installation.
|
||||
@@ -13,13 +18,9 @@ This repository contains complete examples of branding and some
|
||||
modules for Calamares.
|
||||
|
||||
- [Branding](#branding) documentation
|
||||
- [default](branding/default/branding.desc) branding example
|
||||
- [fancy](branding/fancy/branding.desc) branding example
|
||||
- [KaOS](branding/kaos_branding/branding.desc) branding example
|
||||
- [SameGame](branding/samegame/branding.desc) branding example
|
||||
- [Module](#module) documentation
|
||||
|
||||
## Branding
|
||||
- [Module](#modules) documentation
|
||||
|
||||
## Branding
|
||||
|
||||
> Branding shapes the **look** of Calamares to your distro
|
||||
|
||||
@@ -28,11 +29,23 @@ can be used for testing. The examples here show what can be done
|
||||
with QML in the context of Calamares branding, and provide examples
|
||||
and documentation for the framework that Calamares ships with.
|
||||
|
||||
- `default/` is a copy of the default branding included with Calamares.
|
||||
- `fancy/` has navigation buttons and a slide counter.
|
||||
- `kaos_branding/` is a copy of the KaOS branding component, which
|
||||
has translations and a bunch of fancy graphics.
|
||||
- `samegame/` is a copy of the Qt Company "Same Game" QML demo. It
|
||||
- [`default/`](branding/default/branding.desc)
|
||||
is a copy of the default branding included with Calamares.
|
||||
- [`fancy/`](branding/fancy/branding.desc)
|
||||
has navigation buttons and a slide counter.
|
||||
- [`image-slideshow/`](branding/image-slideshow/branding.desc)
|
||||
is a variant of the *default* branding that implements its
|
||||
own slide element for QML that supports a single image.
|
||||
This is useful for straightforward images-only slideshows
|
||||
(probably moreso than the default slideshow).
|
||||
- [`kaos_branding/`](branding/kaos_branding/branding.desc)
|
||||
is a copy of the KaOS branding component, which
|
||||
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)
|
||||
is a copy of the Qt Company "Same Game" QML demo. It
|
||||
shows that **any** QML can be used for branding purposes.
|
||||
|
||||
### Writing your own Branding
|
||||
@@ -62,7 +75,7 @@ and documentation for the framework that Calamares ships with.
|
||||
parts of Calamares.
|
||||
|
||||
See the [styling paragraph](https://github.com/calamares/calamares/wiki/Deploy-Guide#styling-calamares) of the deployment guide for more details.
|
||||
|
||||
|
||||
### Testing a Branding Component
|
||||
|
||||
If Calamares is installed, then the Calamares QML support files
|
||||
@@ -72,7 +85,7 @@ branding component is free to do whatever is interesting in QML.
|
||||
|
||||
The tool for quickly viewing QML files is `qmlscene`, which is
|
||||
included with the Qt development tools. It can be used to
|
||||
preview a Calamares branding component (slideshow) without starting
|
||||
preview a Calamares branding component (slideshow) without starting
|
||||
Calamares. If the component uses translations, you will need to
|
||||
build the translations first (using Qt Linguist `lrelease`, or by
|
||||
using the normal build system for branding components).
|
||||
@@ -87,7 +100,7 @@ qmlscene \
|
||||
-translation build/calamares-fancy_nl.qm \
|
||||
-I /usr/local/share/calamares/qml \
|
||||
-geometry 600x400 \
|
||||
fancy/show.qml
|
||||
fancy/show.qml
|
||||
```
|
||||
|
||||
This starts the viewer with the Dutch (nl) translation, using the
|
||||
@@ -104,25 +117,39 @@ every time.
|
||||
### Calamares Branding API
|
||||
|
||||
The slideshow which is configured in the branding files can have
|
||||
one of two "API styles".
|
||||
one of two "API styles".
|
||||
|
||||
- Version 1 is loaded when the slideshow starts. If the slideshow is
|
||||
large, or contains remote content, then this may be slow.
|
||||
The loading time may be visible as a "white flash" as the
|
||||
QML component is displayed with no background until the
|
||||
slideshow is loaded.
|
||||
- Version 2 is loaded asynchronously from the moment Calamares is
|
||||
started. This may delay startup a little, but may appear more
|
||||
started. This may delay startup a little, but appears more
|
||||
responsive overall.
|
||||
|
||||
If the slideshow QML defines functions
|
||||
`onActivate()` and `onLeave()` then those functions
|
||||
are called when the slideshow becomes visible and when the installation is finished.
|
||||
These can be used to start and stop timers or sound effects or
|
||||
whatever.
|
||||
whatever.
|
||||
|
||||
In addition, if the slideshow QML defines a property
|
||||
`activatedInCalamares` then it is set to `true`
|
||||
when the slideshow becomes visible, and to `false` when
|
||||
when the slideshow becomes visible, and to `false` when
|
||||
the installation is finished. This can also be used to
|
||||
start timers, etc.
|
||||
start timers, etc. The standard `Presentation.qml` included with
|
||||
Calamares has such a property.
|
||||
|
||||
A slideshow (`show.qml`) can be entirely independent, with bespoke code,
|
||||
or it can make use of files shipped as part of Calamares: a *Presentation* and
|
||||
a *Slide* element (and some others). There are also Calamares internals which
|
||||
can be used from QML:
|
||||
- `import calamares.slideshow 1.0` for the standard QML slideshow (e.g. *Presentation* element;
|
||||
use *Slide* with this or write an API-equivalent element such as the one in `image-slideshow/`).
|
||||
- `import io.calamares.ui 1.0` for a *Branding* object which has an API to
|
||||
get colors and strings that are used elsewhere in Calamares (e.g. to make the
|
||||
slide background the same as the background defined in `branding.desc`).
|
||||
|
||||
|
||||
## Modules
|
||||
@@ -135,6 +162,8 @@ listed in the *show* phase, and may run jobs if listed in the
|
||||
*exec* phase) and a Python job module (no UI, runs in the *exec*
|
||||
phase).
|
||||
|
||||
### Example Modules
|
||||
|
||||
- [filekeeper](modules/filekeeper/CMakeLists.txt) is a C++ **job** module
|
||||
to copy files from the host (live) system to the target system at
|
||||
the end of installation, like logfiles. (This module is made obsolete
|
||||
@@ -146,6 +175,18 @@ phase).
|
||||
module that just serves to slow down an installation by delaying
|
||||
a configurable (default 30 seconds) amount of time.
|
||||
|
||||
### Functional Modules
|
||||
|
||||
- [mobile](modules/mobile/CMakeLists.txt) is a QML **view** that
|
||||
takes over a number of other view steps. It is specific to
|
||||
mobile phone use, and as of writing used by
|
||||
[postmarketOS](https://postmarketos.org) and
|
||||
[Mobian](https://mobian-project.org/). Among other things, it
|
||||
allows to set up full disk encryption and to configure the
|
||||
default user's password. Read the
|
||||
[on-device installer](https://wiki.postmarketos.org/wiki/On-device_installer)
|
||||
article for more information.
|
||||
|
||||
### CMake Preparation
|
||||
|
||||
The single macro `calamares_add_module_subdirectory()`
|
||||
@@ -173,3 +214,18 @@ usually called `main.py` which defines a `run()` function.
|
||||
|
||||
The API is loosely documented in the
|
||||
[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.
|
||||
|
||||
* [](https://webchat.kde.org/#/room/%23calamares:kde.org)
|
||||
|
||||
64
branding/image-slideshow/ImageSlide.qml
Normal file
@@ -0,0 +1,64 @@
|
||||
/* === This file is part of Calamares Extensions - <http://github.com/calamares-extensions> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
/* An *ImageSlide* is a *Slide* (it offers the API that *Presentation*
|
||||
* expects) while displaying only a single image. This is useful
|
||||
* for presentations that are all images, with no interaction or text.
|
||||
*/
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
/* To use an *ImageSlide*, instantiate it inside your *Presentation*
|
||||
* and set the *src* property to a path to an image file in a supported
|
||||
* format. Relative paths are ok.
|
||||
*/
|
||||
Item {
|
||||
id: imageslide
|
||||
|
||||
/* Slides should be non-visible at the start; the *Presentation*
|
||||
* handles visibility (so that one slide at a time is visible).
|
||||
*/
|
||||
visible: false
|
||||
/* Make this item fill up the parent, so that alignment of the
|
||||
* image (below) works out to "middle of the parent".
|
||||
*/
|
||||
anchors.fill: parent
|
||||
|
||||
/* The *Presentation* manages visibility of children that have
|
||||
* attribute *isSlide* and *isSlide* is set to *true*. Other
|
||||
* children are ignored, so we need to set this so that the
|
||||
* *ImageSlide* elements are treated like slides.
|
||||
*/
|
||||
property bool isSlide: true;
|
||||
/* The *Presentation* allows slides to have notes, so just leave
|
||||
* an empty string here.
|
||||
*/
|
||||
property string notes;
|
||||
|
||||
|
||||
/* This is the important property for *ImageSlide*: the path to the
|
||||
* image to display. When instantiating *ImageSlide*, set this for
|
||||
* each instance. Relative paths are ok.
|
||||
*/
|
||||
property string src;
|
||||
|
||||
/* The image itself. It has fixed sizes (300x150px). You could set
|
||||
* an aspect ratio here (e.g. `height: width / 2`) as well.
|
||||
*
|
||||
* This binds the image source (filename) to the string *src*
|
||||
* in the *ImageSlide* element, for convenience in setting things
|
||||
* up in the overall slideshow. If you want to make width and
|
||||
* height configurable, add a property above and then bind to
|
||||
* them from the Image element.
|
||||
*/
|
||||
Image {
|
||||
id: image
|
||||
source: src
|
||||
width: 300
|
||||
height: 150
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
40
branding/image-slideshow/branding.desc
Normal file
@@ -0,0 +1,40 @@
|
||||
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
---
|
||||
componentName: default
|
||||
# image-slideshow
|
||||
|
||||
# Branding must define some strings for the welcome page,
|
||||
# even though this example is about the slideshow, not the rest.
|
||||
strings:
|
||||
productName: Mirror Linux
|
||||
shortProductName: Mirror
|
||||
version:
|
||||
shortVersion:
|
||||
versionedName: Mirror Linux 1.0
|
||||
shortVersionedName: Mirror 1.0
|
||||
bootloaderEntryName: Mirror
|
||||
|
||||
# These images do not exist in this branding example.
|
||||
images:
|
||||
productLogo: "logo.png"
|
||||
productIcon: "logo.png"
|
||||
productWelcome: "languages.png"
|
||||
|
||||
# Dark-mode for Calamares. The slideshow can access these color values
|
||||
# through the Branding object which is always available to QML inside Calamares.
|
||||
style:
|
||||
sidebarBackground: "#36393e"
|
||||
sidebarText: "#efefef"
|
||||
sidebarTextSelect: "#2eb69b"
|
||||
sidebarTextHighlight: "#313338"
|
||||
|
||||
# The actual slideshow. API version 2 means that the QML is loaded at
|
||||
# startup. This is **slightly** slower at startup, but means that by
|
||||
# the time we reach the slideshow, it is loaded and ready-to-go.
|
||||
#
|
||||
# A v2 slideshow **may** have an onActivate() and onLeave() function,
|
||||
# although Calamares will complain if it does not.
|
||||
slideshow: "show.qml"
|
||||
slideshowAPI: 2
|
||||
|
||||
103
branding/image-slideshow/show.qml
Normal file
@@ -0,0 +1,103 @@
|
||||
/* === This file is part of Calamares Extensions - <http://github.com/calamares-extensions> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
/* This is a simple slideshow for use during the *exec* phase of
|
||||
* installation, that displays a handful of slides. It uses
|
||||
* the *Presentation* QML components -- this allows, for instance,
|
||||
* notes to be added to slides, and for arrow navigation to be
|
||||
* used. But at its core it's just a bunch of images, repeating.
|
||||
*
|
||||
* For this kind of limited functionality, it may be better to
|
||||
* use the "plain images" slideshow format in Calamares, although
|
||||
* then you don't have any say in how things are animated.
|
||||
*
|
||||
* This slideshow is written for *slideshowAPI* version 1, so in
|
||||
* `branding.desc` set that appropriately.
|
||||
*/
|
||||
|
||||
|
||||
import QtQuick 2.0 // Basic QML
|
||||
import calamares.slideshow 1.0 // Calamares slideshow: Presentation
|
||||
import io.calamares.ui 1.0 // Calamares internals: Branding
|
||||
|
||||
/* *Presentation* comes from the pre-installed calamares.slideshow
|
||||
* that comes with Calamares itself. See `Presentation.qml` in the
|
||||
* Calamares repository for details and documentation.
|
||||
*
|
||||
* The important parts of presentation are:
|
||||
* - it has a property *activatedInCalamares* which is set to *true*
|
||||
* when the slideshow becomes visible, *false* afterwards.
|
||||
* - it expects one or more children with a property *isSlide*
|
||||
* set to *true*.
|
||||
* - it has a function *goToNextSlide()* to do just that (where
|
||||
* "slides" is the sequence of children that have property
|
||||
* *isSlide* set to *true*.
|
||||
*
|
||||
*/
|
||||
Presentation
|
||||
{
|
||||
id: presentation
|
||||
|
||||
/* This timer ticks once per second (1000ms, set in *interval*)
|
||||
* and calls *goToNextSlide()* each time. Note that it needs
|
||||
* to know the *id* of the presentation, so keep *id* (above)
|
||||
* matched with the function call.
|
||||
*
|
||||
* The timer starts when the presentation is activated; you could
|
||||
* also set *running* to true, but that might cost extra resources.
|
||||
*/
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: presentation.activatedInCalamares
|
||||
repeat: true
|
||||
onTriggered: presentation.goToNextSlide()
|
||||
}
|
||||
|
||||
/* These functions are called when the presentation starts and
|
||||
* ends, respectively. They could be used to start the timer,
|
||||
* but that is done automatically through *activatedInCalamares*,
|
||||
* so there's nothing **to** do.
|
||||
*
|
||||
* Leaving these functions out is fine, although Calamares will
|
||||
* complain that they are missing, then.
|
||||
*/
|
||||
function onActivate() { }
|
||||
function onLeave() { }
|
||||
|
||||
|
||||
/* A presentation is an Item: it has no visual appearance at all.
|
||||
* Give it a background, which fills the whole area of the presentation.
|
||||
* Setting *z* to a low value places this rectangle **behind** other
|
||||
* things in the presentation -- which is correct for a background.
|
||||
*
|
||||
* This uses the background set in the styles section of `branding.desc`.
|
||||
*/
|
||||
Rectangle {
|
||||
id: mybackground
|
||||
anchors.fill: parent
|
||||
color: Branding.styleString(Branding.SidebarBackground)
|
||||
z: -1
|
||||
}
|
||||
|
||||
/* The *ImageSlide* is a component unique to this branding directory.
|
||||
* The QML file `ImageSlide.qml` can be stored alongside `show.qml`
|
||||
* and it will be loaded on-demand. See the documentation in that
|
||||
* file for details, but it comes down to this: for each *ImageSlide*,
|
||||
* set *src* to a suitable value (an image path in this directory)
|
||||
* and that will be displayed.
|
||||
*/
|
||||
ImageSlide {
|
||||
src: "slide1.png"
|
||||
}
|
||||
|
||||
ImageSlide {
|
||||
src: "slide2.png"
|
||||
}
|
||||
|
||||
ImageSlide {
|
||||
src: "slide3.png"
|
||||
}
|
||||
}
|
||||
BIN
branding/image-slideshow/slide1.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
branding/image-slideshow/slide2.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
branding/image-slideshow/slide3.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
101
branding/kaos_branding/about.qml
Normal 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 <teo@kde.org><br/>
|
||||
Copyright 2017-2022 Adriaan de Groot <groot@kde.org><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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,32 @@
|
||||
---
|
||||
componentName: kaos
|
||||
|
||||
welcomeStyleCalamares: false
|
||||
|
||||
# Should the welcome image (productWelcome, below) be scaled
|
||||
# up beyond its natural size?
|
||||
welcomeExpandingLogo: true
|
||||
|
||||
windowExpanding: normal
|
||||
windowSize: 920px,630px
|
||||
windowPlacement: center
|
||||
|
||||
sidebar: qml,bottom
|
||||
navigation: qml,right
|
||||
|
||||
strings:
|
||||
productName: KaOS
|
||||
shortProductName: KaOS
|
||||
version: 2018.03
|
||||
version: 2022.08
|
||||
shortVersion: KaOS
|
||||
versionedName: KaOS 2018.03
|
||||
versionedName: KaOS 2022.08
|
||||
shortVersionedName: KaOS 2018.03
|
||||
bootloaderEntryName: KaOS
|
||||
productUrl: https://kaosx.us/
|
||||
supportUrl: https://kaosx.us/docs/
|
||||
knownIssuesUrl: https://kaosx.us/pages/download/#known-issues
|
||||
releaseNotesUrl: https://kaosx.us/pages/release_notes
|
||||
donateUrl: https://kaosx.us/about/donors
|
||||
|
||||
images:
|
||||
productLogo: "kaos.png"
|
||||
@@ -27,6 +37,7 @@ slideshow: "show.qml"
|
||||
slideshowAPI: 1
|
||||
|
||||
style:
|
||||
sidebarBackground: "#bdc3c7"
|
||||
sidebarText: "#1F1F1F"
|
||||
sidebarTextSelect: "#3498DB"
|
||||
SidebarBackground: "#bdc3c7"
|
||||
SidebarText: "#1F1F1F"
|
||||
SidebarTextCurrent: "#3498DB"
|
||||
SidebarBackgroundCurrent: "#eff0f1"
|
||||
|
||||
224
branding/kaos_branding/calamares-navigation.qml
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
branding/kaos_branding/calamares-sidebar.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
branding/kaos_branding/draw-rectangle.svg
Normal 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 |
@@ -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 )
|
||||
list( APPEND TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/calamares-${COMPONENT_NAME}_${lang}.ts" )
|
||||
endforeach()
|
||||
|
||||
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 106 KiB |
15
branding/kaos_branding/pan-end-symbolic.svg
Normal 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 |
15
branding/kaos_branding/pan-start-symbolic.svg
Normal 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 |
15
branding/kaos_branding/pan-up-symbolic.svg
Normal 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 |
@@ -1,6 +1,7 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -24,13 +25,12 @@ Presentation
|
||||
id: presentation
|
||||
|
||||
Timer {
|
||||
id: advanceTimer
|
||||
interval: 5000
|
||||
running: false
|
||||
repeat: true
|
||||
onTriggered: presentation.goToNextSlide()
|
||||
}
|
||||
|
||||
|
||||
Slide {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -38,7 +38,7 @@ Presentation
|
||||
id: background
|
||||
source: "1.svg"
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: 0
|
||||
@@ -96,15 +96,15 @@ Presentation
|
||||
anchors.horizontalCenterOffset: -100
|
||||
font.pixelSize: parent.width *.015
|
||||
color: 'white'
|
||||
text: qsTr("The default Office Suite is Calligra.<br/>"+
|
||||
"LibreOffice is available in the repositories. <br/>")
|
||||
text: qsTr("The default Office Suite is LibreOffice.<br/>"+
|
||||
"Calligra is available in the repositories. <br/>")
|
||||
wrapMode: Text.WordWrap
|
||||
width: 450
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Slide {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -120,7 +120,7 @@ Presentation
|
||||
font.pixelSize: parent.width *.015
|
||||
color: 'white'
|
||||
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/>")
|
||||
wrapMode: Text.WordWrap
|
||||
width: 450
|
||||
@@ -128,7 +128,7 @@ Presentation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Slide {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -150,7 +150,7 @@ Presentation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Slide {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -172,6 +172,4 @@ Presentation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: advanceTimer.running = true
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
# NOTE: this is largely a copy of the release script for Calamares,
|
||||
# with not-applicable parts (such as translation-freeze) either
|
||||
# commented-out, or skipped with if(false).
|
||||
# NOTE: this script may contain Linuxisms
|
||||
#
|
||||
# This attempts to perform the different steps of the RELEASE.md
|
||||
# document automatically. It's not tested on other machines or
|
||||
@@ -31,6 +32,7 @@
|
||||
# * `-B` do not build (before tagging)
|
||||
# * `-P` do not package (tag, sign, tarball)
|
||||
# * `-T` do not respect string freeze
|
||||
# * `-C <args>` set extra arguments to pass to CMake
|
||||
#
|
||||
# The build / package settings can be influenced via environment variables:
|
||||
# * BUILD_DEFAULT set to `false` to avoid first build with gcc
|
||||
@@ -91,7 +93,7 @@ fi
|
||||
### Setup
|
||||
#
|
||||
#
|
||||
BUILDDIR=$(mktemp -d --suffix=-build --tmpdir=.)
|
||||
BUILDDIR=$(mktemp -d -p . -t build.XXXXX)
|
||||
|
||||
### Build with default compiler
|
||||
#
|
||||
@@ -138,30 +140,34 @@ fi
|
||||
### 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 ; }
|
||||
|
||||
### Create signed tag
|
||||
#
|
||||
# This is the signing key ID associated with the GitHub account adriaandegroot,
|
||||
# which is used to create all "verified" tags in the Calamares repo.
|
||||
KEY_ID="CFDDC96F12B1915C"
|
||||
#
|
||||
KEY_ID="328D742D8807A435"
|
||||
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, compute SHA256 for later reporting, and
|
||||
# sign the tarball so the signature can be uploaded separately.
|
||||
#
|
||||
TAR_V="$TARBALL_PREFIX-$V"
|
||||
TAR_FILE="$TAR_V.tar.gz"
|
||||
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 ; }
|
||||
SHA256=$(sha256sum "$TAR_FILE" | cut -d" " -f1)
|
||||
gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball
|
||||
|
||||
### Build the tarball
|
||||
#
|
||||
#
|
||||
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 ; }
|
||||
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 ; }
|
||||
@@ -181,7 +187,6 @@ rm -rf "$TMPDIR" # From tarball
|
||||
cat <<EOF
|
||||
# Next steps for this release:
|
||||
git push origin v$V
|
||||
gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball
|
||||
# Upload tarball $TAR_FILE and the signature $TAR_FILE.asc
|
||||
# Announce via https://github.com/calamares/$PROJECT_NAME/releases/new
|
||||
# SHA256: $SHA256
|
||||
|
||||
17
ci/astylerc
Normal file
@@ -0,0 +1,17 @@
|
||||
# SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
# Do not create a backup file
|
||||
suffix=none
|
||||
|
||||
indent=spaces=4
|
||||
|
||||
# Brackets
|
||||
style=break
|
||||
add-brackets
|
||||
|
||||
# Spaces
|
||||
pad-paren-in
|
||||
pad-header
|
||||
align-pointer=type
|
||||
105
ci/calamaresstyle
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# Calls astyle with settings matching Calamares coding style
|
||||
# Requires astyle >= 2.04 and clang-format-8 or later
|
||||
#
|
||||
# You can pass in directory names, in which case the files
|
||||
# in that directory (NOT below it) are processed.
|
||||
#
|
||||
LANG=C
|
||||
LC_ALL=C
|
||||
LC_NUMERIC=C
|
||||
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 )
|
||||
|
||||
# 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
|
||||
do
|
||||
# Not an error if this particular clang-format isn't found
|
||||
CF=$( which $_cf 2> /dev/null || true )
|
||||
test -n "$CF" && break
|
||||
done
|
||||
|
||||
test -n "$AS" || { echo "! No astyle found in PATH"; exit 1 ; }
|
||||
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 "$CF" || { echo "! $CF is not executable."; exit 1 ; }
|
||||
|
||||
### CLANG-FORMAT-WRANGLING
|
||||
#
|
||||
# Version 7 and earlier doesn't understand all the options we would like
|
||||
# Version 8 is ok
|
||||
# Version 9 is ok
|
||||
# Later versions change some defaults so need extra wrangling.
|
||||
# .. 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
|
||||
|
||||
any_dirs=no
|
||||
for d in "$@"
|
||||
do
|
||||
test -d "$d" && any_dirs=yes
|
||||
done
|
||||
|
||||
style_some()
|
||||
{
|
||||
if test -n "$*" ; then
|
||||
$AS --options=$BASEDIR/astylerc --quiet "$@"
|
||||
$CF -i -style=file "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
if test "x$any_dirs" = "xyes" ; then
|
||||
for d in "$@"
|
||||
do
|
||||
if test -d "$d" ; then
|
||||
style_some $( find "$d" -maxdepth 1 -type f -name '*.cpp' -o -name '*.h' )
|
||||
else
|
||||
style_some "$d"
|
||||
fi
|
||||
done
|
||||
else
|
||||
style_some "$@"
|
||||
fi
|
||||
|
||||
### CLANG-FORMAT-WRANGLING
|
||||
#
|
||||
# Restore the original .clang-format
|
||||
cp "$_fmt.base" "$_fmt"
|
||||
@@ -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
|
||||
)
|
||||
@@ -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 >(); )
|
||||
@@ -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
|
||||
@@ -1,3 +1,6 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
if( NOT Calamares_WITH_QML )
|
||||
calamares_skip_module( "freebsddisk (QML is not supported in this build)" )
|
||||
return()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# The *freebsddisk* module can be used to pick a disk
|
||||
# as an installer step. This module supports ZFSroot
|
||||
# on one whole disk, and UFSroot on one whole disk.
|
||||
|
||||
@@ -6,13 +6,9 @@ calamares_add_plugin( mobile
|
||||
EXPORT_MACRO PLUGINDLLEXPORT_PRO
|
||||
SOURCES
|
||||
Config.cpp
|
||||
Config.h
|
||||
MobileQmlViewStep.cpp
|
||||
MobileQmlViewStep.h
|
||||
PartitionJob.cpp
|
||||
PartitionJob.h
|
||||
UsersJob.cpp
|
||||
UsersJob.h
|
||||
RESOURCES
|
||||
mobile.qrc
|
||||
SHARED_LIB
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "UsersJob.h"
|
||||
|
||||
#include "ViewManager.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include <QVariant>
|
||||
@@ -19,24 +20,49 @@ Config::setConfigurationMap( const QVariantMap& cfgMap )
|
||||
{
|
||||
using namespace CalamaresUtils;
|
||||
|
||||
if ( getBool( cfgMap, "bogus", false ) )
|
||||
{
|
||||
cWarning() << "Configuration key \"bogus\" is still set for *mobile*";
|
||||
}
|
||||
|
||||
m_osName = getString( cfgMap, "osName", "(unknown)" );
|
||||
m_arch = getString( cfgMap, "arch", "(unknown)" );
|
||||
m_device = getString( cfgMap, "device", "(unknown)" );
|
||||
m_userInterface = getString( cfgMap, "userInterface", "(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_userPasswordNumeric = getBool( cfgMap, "userPasswordNumeric", true );
|
||||
|
||||
m_builtinVirtualKeyboard = getBool( cfgMap, "builtinVirtualKeyboard", true );
|
||||
|
||||
m_featureSshd = getBool( cfgMap, "featureSshd", true );
|
||||
m_featureFsType = getBool( cfgMap, "featureFsType", false );
|
||||
|
||||
m_cmdLuksFormat = getString( cfgMap, "cmdLuksFormat", "cryptsetup luksFormat --use-random" );
|
||||
m_cmdLuksOpen = getString( cfgMap, "cmdLuksOpen", "cryptsetup luksOpen" );
|
||||
m_cmdMkfsRoot = getString( cfgMap, "cmdMkfsRoot", "mkfs.ext4 -L 'unknownOS_root'" );
|
||||
m_cmdMount = getString( cfgMap, "cmdMount", "mount" );
|
||||
m_targetDeviceRoot = getString( cfgMap, "targetDeviceRoot", "/dev/unknown" );
|
||||
m_targetDeviceRootInternal = getString( cfgMap, "targetDeviceRootInternal", "" );
|
||||
|
||||
m_cmdMkfsRootBtrfs = getString( cfgMap, "cmdMkfsRootBtrfs", "mkfs.btrfs -L 'unknownOS_root'" );
|
||||
m_cmdMkfsRootExt4 = getString( cfgMap, "cmdMkfsRootExt4", "mkfs.ext4 -L 'unknownOS_root'" );
|
||||
m_cmdMkfsRootF2fs = getString( cfgMap, "cmdMkfsRootF2fs", "mkfs.f2fs -l 'unknownOS_root'" );
|
||||
m_fsList = getStringList( cfgMap, "fsModel", QStringList { "ext4", "f2fs", "btrfs" } );
|
||||
m_defaultFs = getString( cfgMap, "defaultFs", "ext4" );
|
||||
m_fsIndex = m_fsList.indexOf( m_defaultFs );
|
||||
m_fsType = m_defaultFs;
|
||||
|
||||
m_cmdInternalStoragePrepare = getString( cfgMap, "cmdInternalStoragePrepare", "ondev-internal-storage-prepare" );
|
||||
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_cmdSshdDisable = getString( cfgMap, "cmdSshdDisable", "systemctl disable sshd.service" );
|
||||
m_cmdSshdUseradd = getString( cfgMap, "cmdSshdUseradd", "useradd -G wheel -m" );
|
||||
@@ -51,6 +77,7 @@ Config::createJobs()
|
||||
/* Put users job in queue (should run after unpackfs) */
|
||||
Calamares::Job* j = new UsersJob( m_featureSshd,
|
||||
m_cmdPasswd,
|
||||
m_cmdUsermod,
|
||||
cmdSshd,
|
||||
m_cmdSshdUseradd,
|
||||
m_isSshEnabled,
|
||||
@@ -66,6 +93,24 @@ Config::createJobs()
|
||||
void
|
||||
Config::runPartitionJobThenLeave( bool b )
|
||||
{
|
||||
Calamares::ViewManager* v = Calamares::ViewManager::instance();
|
||||
QString cmdMkfsRoot;
|
||||
if ( m_fsType == QStringLiteral( "btrfs" ) )
|
||||
{
|
||||
cmdMkfsRoot = m_cmdMkfsRootBtrfs;
|
||||
}
|
||||
else if ( m_fsType == QStringLiteral( "f2fs" ) )
|
||||
{
|
||||
cmdMkfsRoot = m_cmdMkfsRootF2fs;
|
||||
}
|
||||
else if ( m_fsType == QStringLiteral( "ext4" ) )
|
||||
{
|
||||
cmdMkfsRoot = m_cmdMkfsRootExt4;
|
||||
}
|
||||
else
|
||||
{
|
||||
v->onInstallationFailed( "Unknown filesystem: '" + m_fsType + "'", "" );
|
||||
}
|
||||
/* HACK: run partition job
|
||||
* The "mobile" module has two jobs, the partition job and the users job.
|
||||
* If we added both of them in Config::createJobs(), Calamares would run
|
||||
@@ -78,7 +123,7 @@ Config::runPartitionJobThenLeave( bool b )
|
||||
Calamares::Job* j = new PartitionJob( m_cmdInternalStoragePrepare,
|
||||
m_cmdLuksFormat,
|
||||
m_cmdLuksOpen,
|
||||
m_cmdMkfsRoot,
|
||||
cmdMkfsRoot,
|
||||
m_cmdMount,
|
||||
m_targetDeviceRoot,
|
||||
m_targetDeviceRootInternal,
|
||||
@@ -87,7 +132,6 @@ Config::runPartitionJobThenLeave( bool b )
|
||||
m_fdePassword );
|
||||
Calamares::JobResult res = j->exec();
|
||||
|
||||
Calamares::ViewManager* v = Calamares::ViewManager::instance();
|
||||
if ( res )
|
||||
{
|
||||
v->next();
|
||||
@@ -98,6 +142,13 @@ Config::runPartitionJobThenLeave( bool b )
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Config::setUsername( const QString& username )
|
||||
{
|
||||
m_username = username;
|
||||
emit usernameChanged( m_username );
|
||||
}
|
||||
|
||||
void
|
||||
Config::setUserPassword( const QString& userPassword )
|
||||
{
|
||||
@@ -143,3 +194,28 @@ Config::setInstallFromExternalToInternal( const bool val )
|
||||
{
|
||||
m_installFromExternalToInternal = val;
|
||||
}
|
||||
|
||||
void
|
||||
Config::setFsType( int idx )
|
||||
{
|
||||
if ( idx >= 0 && idx < m_fsList.length() )
|
||||
{
|
||||
setFsType( m_fsList[ idx ] );
|
||||
}
|
||||
}
|
||||
void
|
||||
Config::setFsType( const QString& fsType )
|
||||
{
|
||||
if ( fsType != m_fsType )
|
||||
{
|
||||
m_fsType = fsType;
|
||||
emit fsTypeChanged( m_fsType );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Config::setFsIndex( const int fsIndex )
|
||||
{
|
||||
m_fsIndex = fsIndex;
|
||||
emit fsIndexChanged( m_fsIndex );
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
class Config : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
/* installer UI */
|
||||
Q_PROPERTY( bool builtinVirtualKeyboard READ builtinVirtualKeyboard CONSTANT FINAL )
|
||||
|
||||
/* welcome */
|
||||
Q_PROPERTY( QString osName READ osName CONSTANT FINAL )
|
||||
Q_PROPERTY( QString arch READ arch CONSTANT FINAL )
|
||||
@@ -17,9 +20,13 @@ class Config : public QObject
|
||||
Q_PROPERTY( QString userInterface READ userInterface 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 */
|
||||
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( bool userPasswordNumeric READ userPasswordNumeric CONSTANT FINAL )
|
||||
|
||||
/* ssh server + credentials */
|
||||
Q_PROPERTY( bool featureSshd READ featureSshd CONSTANT FINAL )
|
||||
@@ -31,12 +38,18 @@ class Config : public QObject
|
||||
Q_PROPERTY( QString fdePassword READ fdePassword WRITE setFdePassword NOTIFY fdePasswordChanged )
|
||||
Q_PROPERTY( bool isFdeEnabled READ isFdeEnabled WRITE setIsFdeEnabled )
|
||||
|
||||
/* filesystem selection */
|
||||
Q_PROPERTY( QString fsType READ fsType WRITE setFsType NOTIFY fsTypeChanged )
|
||||
Q_PROPERTY( bool featureFsType READ featureFsType CONSTANT FINAL )
|
||||
Q_PROPERTY( QStringList fsList READ fsList CONSTANT FINAL )
|
||||
Q_PROPERTY( QString defaultFs READ defaultFs CONSTANT FINAL )
|
||||
Q_PROPERTY( int fsIndex READ fsIndex WRITE setFsIndex NOTIFY fsIndexChanged )
|
||||
|
||||
/* partition job */
|
||||
Q_PROPERTY( bool runPartitionJobThenLeave READ runPartitionJobThenLeaveDummy WRITE runPartitionJobThenLeave )
|
||||
Q_PROPERTY( QString cmdInternalStoragePrepare READ cmdInternalStoragePrepare CONSTANT FINAL )
|
||||
Q_PROPERTY( QString cmdLuksFormat READ cmdLuksFormat CONSTANT FINAL )
|
||||
Q_PROPERTY( QString cmdLuksOpen READ cmdLuksOpen CONSTANT FINAL )
|
||||
Q_PROPERTY( QString cmdMkfsRoot READ cmdMkfsRoot CONSTANT FINAL )
|
||||
Q_PROPERTY( QString cmdMount READ cmdMount CONSTANT FINAL )
|
||||
Q_PROPERTY( QString targetDeviceRoot READ targetDeviceRoot CONSTANT FINAL )
|
||||
Q_PROPERTY( QString targetDeviceRootInternal READ targetDeviceRootInternal CONSTANT FINAL )
|
||||
@@ -52,6 +65,9 @@ public:
|
||||
void setConfigurationMap( const QVariantMap& );
|
||||
Calamares::JobList createJobs();
|
||||
|
||||
/* installer UI */
|
||||
bool builtinVirtualKeyboard() { return m_builtinVirtualKeyboard; }
|
||||
|
||||
/* welcome */
|
||||
QString osName() const { return m_osName; }
|
||||
QString arch() const { return m_arch; }
|
||||
@@ -59,12 +75,17 @@ public:
|
||||
QString userInterface() const { return m_userInterface; }
|
||||
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 userPassword() const { return m_userPassword; }
|
||||
void setUsername( const QString& username );
|
||||
void setUserPassword( const QString& userPassword );
|
||||
bool userPasswordNumeric() const { return m_userPasswordNumeric; }
|
||||
|
||||
/* ssh server + credetials */
|
||||
/* ssh server + credentials */
|
||||
bool featureSshd() { return m_featureSshd; }
|
||||
QString sshdUsername() const { return m_sshdUsername; }
|
||||
QString sshdPassword() const { return m_sshdPassword; }
|
||||
@@ -79,13 +100,25 @@ public:
|
||||
void setFdePassword( const QString& fdePassword );
|
||||
void setIsFdeEnabled( bool isFdeEnabled );
|
||||
|
||||
/* filesystem selection */
|
||||
bool featureFsType() { return m_featureFsType; };
|
||||
QString fsType() const { return m_fsType; };
|
||||
void setFsType( int idx );
|
||||
void setFsType( const QString& fsType );
|
||||
QStringList fsList() const { return m_fsList; };
|
||||
int fsIndex() const { return m_fsIndex; };
|
||||
void setFsIndex( const int fsIndex );
|
||||
QString defaultFs() const { return m_defaultFs; };
|
||||
|
||||
/* partition job */
|
||||
bool runPartitionJobThenLeaveDummy() { return 0; }
|
||||
void runPartitionJobThenLeave( bool b );
|
||||
QString cmdInternalStoragePrepare() const { return m_cmdInternalStoragePrepare; }
|
||||
QString cmdLuksFormat() const { return m_cmdLuksFormat; }
|
||||
QString cmdLuksOpen() const { return m_cmdLuksOpen; }
|
||||
QString cmdMkfsRoot() const { return m_cmdMkfsRoot; }
|
||||
QString cmdMkfsRootBtrfs() const { return m_cmdMkfsRootBtrfs; }
|
||||
QString cmdMkfsRootExt4() const { return m_cmdMkfsRootExt4; }
|
||||
QString cmdMkfsRootF2fs() const { return m_cmdMkfsRootF2fs; }
|
||||
QString cmdMount() const { return m_cmdMount; }
|
||||
QString targetDeviceRoot() const { return m_targetDeviceRoot; }
|
||||
QString targetDeviceRootInternal() const { return m_targetDeviceRootInternal; }
|
||||
@@ -94,11 +127,15 @@ public:
|
||||
|
||||
/* users job */
|
||||
QString cmdPasswd() const { return m_cmdPasswd; }
|
||||
QString cmdUsermod() const { return m_cmdUsermod; }
|
||||
QString cmdSshdEnable() const { return m_cmdSshdEnable; }
|
||||
QString cmdSshdDisable() const { return m_cmdSshdDisable; }
|
||||
QString cmdSshdUseradd() const { return m_cmdSshdUseradd; }
|
||||
|
||||
private:
|
||||
/* installer UI */
|
||||
bool m_builtinVirtualKeyboard;
|
||||
|
||||
/* welcome */
|
||||
QString m_osName;
|
||||
QString m_arch;
|
||||
@@ -106,32 +143,47 @@ private:
|
||||
QString m_userInterface;
|
||||
QString m_version;
|
||||
|
||||
/* reserved usernames (user_pass, ssh_credentials) */
|
||||
QStringList m_reservedUsernames;
|
||||
|
||||
/* default user */
|
||||
QString m_username;
|
||||
QString m_userPassword;
|
||||
bool m_userPasswordNumeric;
|
||||
|
||||
/* ssh server + credetials */
|
||||
bool m_featureSshd;
|
||||
/* ssh server + credentials */
|
||||
bool m_featureSshd = false;
|
||||
QString m_sshdUsername;
|
||||
QString m_sshdPassword;
|
||||
bool m_isSshEnabled;
|
||||
bool m_isSshEnabled = false;
|
||||
|
||||
/* full disk encryption */
|
||||
QString m_fdePassword = "";
|
||||
QString m_fdePassword;
|
||||
bool m_isFdeEnabled = false;
|
||||
|
||||
/* filesystem selection */
|
||||
bool m_featureFsType = false;
|
||||
QString m_defaultFs;
|
||||
QString m_fsType;
|
||||
// Index of the currently selected filesystem in UI.
|
||||
int m_fsIndex = -1;
|
||||
QStringList m_fsList;
|
||||
|
||||
/* partition job */
|
||||
QString m_cmdInternalStoragePrepare;
|
||||
QString m_cmdLuksFormat;
|
||||
QString m_cmdLuksOpen;
|
||||
QString m_cmdMkfsRoot;
|
||||
QString m_cmdMkfsRootBtrfs;
|
||||
QString m_cmdMkfsRootExt4;
|
||||
QString m_cmdMkfsRootF2fs;
|
||||
QString m_cmdMount;
|
||||
QString m_targetDeviceRoot;
|
||||
QString m_targetDeviceRootInternal;
|
||||
bool m_installFromExternalToInternal;
|
||||
bool m_installFromExternalToInternal = false;
|
||||
|
||||
/* users job */
|
||||
QString m_cmdPasswd;
|
||||
QString m_cmdUsermod;
|
||||
QString m_cmdSshdEnable;
|
||||
QString m_cmdSshdDisable;
|
||||
QString m_cmdSshdUseradd;
|
||||
@@ -141,6 +193,7 @@ signals:
|
||||
|
||||
/* default user */
|
||||
void userPasswordChanged( QString userPassword );
|
||||
void usernameChanged( QString username );
|
||||
|
||||
/* ssh server + credentials */
|
||||
void sshdUsernameChanged( QString sshdUsername );
|
||||
@@ -148,4 +201,7 @@ signals:
|
||||
|
||||
/* full disk encryption */
|
||||
void fdePasswordChanged( QString fdePassword );
|
||||
|
||||
void fsTypeChanged( QString fsType );
|
||||
void fsIndexChanged( int fsIndex );
|
||||
};
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "MobileQmlViewStep.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "GlobalStorage.h"
|
||||
|
||||
#include "locale/LabelModel.h"
|
||||
#include "locale/TranslationsModel.h"
|
||||
#include "modulesystem/ModuleManager.h"
|
||||
#include "utils/Dirs.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "modulesystem/ModuleManager.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DEFINITION( MobileQmlViewStepFactory, registerPlugin< MobileQmlViewStep >(); )
|
||||
|
||||
@@ -118,7 +118,7 @@ PartitionJob::exec()
|
||||
const QString pathRoot = "/";
|
||||
|
||||
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() )
|
||||
{
|
||||
return JobResult::error( "Command failed:<br><br>"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
UsersJob::UsersJob( bool featureSshd,
|
||||
const QString& cmdPasswd,
|
||||
const QString& cmdUsermod,
|
||||
const QString& cmdSshd,
|
||||
const QString& cmdSshdUseradd,
|
||||
bool isSshEnabled,
|
||||
@@ -24,6 +25,7 @@ UsersJob::UsersJob( bool featureSshd,
|
||||
: Calamares::Job()
|
||||
, m_featureSshd( featureSshd )
|
||||
, m_cmdPasswd( cmdPasswd )
|
||||
, m_cmdUsermod( cmdUsermod )
|
||||
, m_cmdSshd( cmdSshd )
|
||||
, m_cmdSshdUseradd( cmdSshdUseradd )
|
||||
, m_isSshEnabled( isSshEnabled )
|
||||
@@ -49,9 +51,11 @@ UsersJob::exec()
|
||||
using namespace std;
|
||||
|
||||
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 )
|
||||
{
|
||||
commands.append( { { "sh", "-c", m_cmdSshd }, QString() } );
|
||||
|
||||
@@ -10,6 +10,7 @@ class UsersJob : public Calamares::Job
|
||||
public:
|
||||
UsersJob( bool featureSshd,
|
||||
const QString& cmdPasswd,
|
||||
const QString& cmdUsermod,
|
||||
const QString& cmdSshd,
|
||||
const QString& cmdSshdUseradd,
|
||||
bool isSshEnabled,
|
||||
@@ -26,6 +27,7 @@ public:
|
||||
private:
|
||||
bool m_featureSshd;
|
||||
QString m_cmdPasswd;
|
||||
QString m_cmdUsermod;
|
||||
QString m_cmdSshd;
|
||||
QString m_cmdSshdUseradd;
|
||||
bool m_isSshEnabled;
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: description
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "Set the numeric password of your user. The lockscreen will" +
|
||||
" ask for this PIN. This is <i>not</i> the PIN of your SIM" +
|
||||
" card. Make sure to remember it."
|
||||
|
||||
width: 500
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userPin
|
||||
anchors.top: description.bottom
|
||||
placeholderText: qsTr("PIN")
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validatePin(userPin, userPinRepeat, errorText)
|
||||
text: config.userPassword
|
||||
|
||||
/* Let the virtual keyboard change to digits only */
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
onActiveFocusChanged: {
|
||||
if(activeFocus) {
|
||||
Qt.inputMethod.update(Qt.ImQueryInput)
|
||||
}
|
||||
}
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userPinRepeat
|
||||
anchors.top: userPin.bottom
|
||||
placeholderText: qsTr("PIN (repeat)")
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validatePin(userPin, userPinRepeat, errorText)
|
||||
text: config.userPassword
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.top: userPinRepeat.bottom
|
||||
id: errorText
|
||||
visible: false
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: errorText.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
if (validatePin(userPin, userPinRepeat, errorText)) {
|
||||
config.userPassword = userPin.text;
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ Item {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
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" +
|
||||
" you don't lose this password!"
|
||||
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Enable")
|
||||
onClicked: {
|
||||
@@ -53,8 +53,8 @@ Item {
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: firstButton.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Disable")
|
||||
onClicked: {
|
||||
|
||||
@@ -36,7 +36,7 @@ Item {
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
@@ -49,8 +49,8 @@ Item {
|
||||
text: config.fdePassword
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -59,15 +59,15 @@ Item {
|
||||
visible: false
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: errorText.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
|
||||
59
modules/mobile/fs_selection.qml
Normal file
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Undef <calamares@undef.tools>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "Select the filesystem for root partition. If unsure, leave the default."
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: fsTypeCB
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 150
|
||||
height: 30
|
||||
editable: false
|
||||
model: config.fsList
|
||||
/* Save the current state on selection so it is there when the back button is pressed */
|
||||
onActivated: config.fsType = fsTypeCB.currentText;
|
||||
Component.onCompleted: fsTypeCB.currentIndex = find( config.fsType, Qt.MatchContains );
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: fsTypeCB.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
config.fsType = fsTypeCB.currentText;
|
||||
navNextFeature();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,13 @@ Item {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: (function() {
|
||||
var ret = "Once you hit 'install', the installation will begin." +
|
||||
" 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) {
|
||||
ret += "<b>After the installation, your device will shutdown" +
|
||||
@@ -44,15 +44,15 @@ Item {
|
||||
return ret;
|
||||
}())
|
||||
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Install")
|
||||
onClicked: navFinish()
|
||||
|
||||
@@ -22,7 +22,7 @@ Item {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "The installation was started from an external storage medium." +
|
||||
@@ -32,15 +32,15 @@ Item {
|
||||
"<br>" +
|
||||
"Where would you like to install " + config.osName + "?"
|
||||
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Internal (eMMC)")
|
||||
onClicked: {
|
||||
@@ -52,8 +52,8 @@ Item {
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: firstButton.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("External (SD card)")
|
||||
onClicked: {
|
||||
|
||||
@@ -22,21 +22,21 @@ Item {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "Are you sure that you want to overwrite the internal storage?" +
|
||||
"<br><br>" +
|
||||
"<b>All existing data on the device will be lost!</b>"
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Yes")
|
||||
onClicked: {
|
||||
@@ -47,8 +47,8 @@ Item {
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: firstButton.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("No")
|
||||
onClicked: {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# Commented out values are defaults.
|
||||
# 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
|
||||
#######
|
||||
@@ -11,12 +18,53 @@
|
||||
## User Interface name (e.g. Plasma Mobile)
|
||||
# userInterface: "(unknown)"
|
||||
|
||||
## User Interface assumes that the password is numeric (as of writing, this is
|
||||
## the case with Plasma Mobile and Phosh)
|
||||
# userPasswordNumeric: true
|
||||
|
||||
## OS version
|
||||
# version: "(unknown)"
|
||||
|
||||
## 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"
|
||||
|
||||
## 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
|
||||
#######
|
||||
@@ -53,6 +101,21 @@
|
||||
## authentication after installation.
|
||||
# featureSshd: true
|
||||
|
||||
## Ask the user, which filesystem to use.
|
||||
# featureFsType: false
|
||||
## Filesystems that the user can choose from.
|
||||
#fsModel:
|
||||
# - ext4
|
||||
# - f2fs
|
||||
# - btrfs
|
||||
## Default filesystem to display in the dialog. If featureFsType is disabled,
|
||||
## this gets used without asking the user.
|
||||
# defaultFs: ext4
|
||||
|
||||
## Start Qt's virtual keyboard within the mobile module. Disable if you bring
|
||||
## your own virtual keyboard (e.g. svkbd).
|
||||
# builtinVirtualKeyboard: true
|
||||
|
||||
#######
|
||||
### Commands running in the installer OS
|
||||
#######
|
||||
@@ -69,7 +132,13 @@
|
||||
|
||||
## Format the rootfs with a file system
|
||||
## Arguments: <device>
|
||||
# cmdMkfsRoot: "mkfs.ext4 -L 'unknownOS_root'"
|
||||
## Btrfs: to allow snapshots to work on the root subvolume, it is recommended that this
|
||||
## command be a script which will create a subvolume and make it default
|
||||
## An example can be found at:
|
||||
## https://gitlab.com/mobian1/calamares-settings-mobian/-/merge_requests/2/diffs#diff-content-dde34f5f1c89e3dea63608c553bbc452dedf428f
|
||||
# cmdMkfsRootBtrfs: "mkfs.btrfs -L 'unknownOS_root'"
|
||||
# cmdMkfsRootExt4: "mkfs.ext4 -L 'unknownOS_root'"
|
||||
# cmdMkfsRootF2fs: "mkfs.f2fs -l 'unknownOS_root'"
|
||||
|
||||
## Mount the partition after formatting with file system
|
||||
## Arguments: <device> <mountpoint>
|
||||
@@ -86,6 +155,10 @@
|
||||
### 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
|
||||
## Arguments: <username>
|
||||
## Stdin: password twice, each time with \n
|
||||
|
||||
@@ -19,9 +19,10 @@ Page
|
||||
"welcome": null, /* titlebar disabled */
|
||||
"install_target": "Installation target",
|
||||
"install_target_confirm": "Warning",
|
||||
"default_pin": "Lockscreen PIN",
|
||||
"user_pass": "User password",
|
||||
"ssh_confirm": "SSH server",
|
||||
"ssh_credentials": "SSH credentials",
|
||||
"fs_selection": "Root filesystem",
|
||||
"fde_confirm": "Full disk encryption",
|
||||
"fde_pass": "Full disk encryption",
|
||||
"install_confirm": "Ready to install",
|
||||
@@ -32,10 +33,12 @@ Page
|
||||
"screens": ["welcome"]},
|
||||
{"name": "installTarget",
|
||||
"screens": ["install_target", "install_target_confirm"]},
|
||||
{"name": "userPin",
|
||||
"screens": ["default_pin"]},
|
||||
{"name": "userPassword",
|
||||
"screens": ["user_pass"]},
|
||||
{"name": "sshd",
|
||||
"screens": ["ssh_confirm", "ssh_credentials"]},
|
||||
{"name": "fsType",
|
||||
"screens": ["fs_selection"]},
|
||||
{"name": "fde",
|
||||
"screens": ["fde_confirm", "fde_pass"]},
|
||||
{"name": "installConfirm",
|
||||
@@ -43,7 +46,7 @@ Page
|
||||
]
|
||||
property var featureIdByScreen: (function() {
|
||||
/* Put "features" above into an index of screen name -> feature id:
|
||||
* featureIdByScreen = {"welcome": 0, "default_pin": 1, ...} */
|
||||
* featureIdByScreen = {"welcome": 0, "user_pass": 1, ...} */
|
||||
var ret = {};
|
||||
for (var i=0; i<features.length; i++) {
|
||||
for (var j=0; j<features[i]["screens"].length; j++) {
|
||||
@@ -52,8 +55,8 @@ Page
|
||||
}
|
||||
return ret;
|
||||
}())
|
||||
/* Only allow characters, that can be typed in with the initramfs on-screen keyboard
|
||||
* (osk-sdl: see src/keyboard.cpp). FIXME: make configurable, but keep this as default? */
|
||||
/* Only allow characters, that can be typed in with osk-sdl
|
||||
* (src/keyboard.cpp). Details in big comment in validatePassword(). */
|
||||
property var allowed_chars:
|
||||
/* layer 0 */ "abcdefghijklmnopqrstuvwxyz" +
|
||||
/* layer 1 */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
@@ -74,7 +77,7 @@ Page
|
||||
Rectangle {
|
||||
id: mobileNavigation
|
||||
width: parent.width
|
||||
height: 60
|
||||
height: 30
|
||||
color: "#e6e4e1"
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -95,8 +98,8 @@ Page
|
||||
text: "<"
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 32
|
||||
implicitHeight: 30
|
||||
implicitWidth: 10
|
||||
implicitHeight: 7
|
||||
border.color: "#c1bab5"
|
||||
border.width: 1
|
||||
radius: 4
|
||||
@@ -106,7 +109,7 @@ Page
|
||||
onClicked: navBack()
|
||||
}
|
||||
Rectangle {
|
||||
implicitHeight: 30
|
||||
implicitHeight: 10
|
||||
Layout.fillWidth: true
|
||||
color: "#e6e4e1"
|
||||
|
||||
@@ -139,6 +142,7 @@ Page
|
||||
InputPanel {
|
||||
id: inputPanel
|
||||
y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height
|
||||
visible: config.builtinVirtualKeyboard
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
@@ -180,10 +184,10 @@ Page
|
||||
timer.start();
|
||||
}
|
||||
function navNextFeature() {
|
||||
var id = featureIdByScreen[screen] + 1;
|
||||
var id;
|
||||
|
||||
/* Skip disabled features */
|
||||
do {
|
||||
for (id = featureIdByScreen[screen] + 1; id < features.length; id++) {
|
||||
/* First letter uppercase */
|
||||
var name = features[id]["name"];
|
||||
var nameUp = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
@@ -192,7 +196,6 @@ Page
|
||||
var configOption = "feature" + nameUp;
|
||||
if (config[configOption] === false) {
|
||||
console.log("Skipping feature (disabled in config): " + name);
|
||||
id += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -201,10 +204,11 @@ Page
|
||||
if (eval("typeof " + funcName) === "function"
|
||||
&& eval(funcName + "()")) {
|
||||
console.log("Skipping feature (skip function): " + name);
|
||||
id += 1;
|
||||
continue;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
console.log("Navigating to feature: " + features[id]["name"]);
|
||||
return navTo(features[id]["screens"][0]);
|
||||
@@ -249,7 +253,7 @@ Page
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Input validation: user-screens (default_pin, ssh_credentials) */
|
||||
/* Input validation: user-screens (fde_pass, user_pass, ssh_credentials) */
|
||||
function validatePin(userPin, userPinRepeat, errorText) {
|
||||
var pin = userPin.text;
|
||||
var repeat = userPinRepeat.text;
|
||||
@@ -274,47 +278,12 @@ Page
|
||||
|
||||
return validationFailureClear(errorText);
|
||||
}
|
||||
function validateSshdUsername(username, errorText) {
|
||||
function validateUsername(username, errorText, extraReservedUsernames = []) {
|
||||
var name = username.text;
|
||||
var reserved = [ /* FIXME: make configurable */
|
||||
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",
|
||||
]
|
||||
var reserved = config.reservedUsernames.concat(extraReservedUsernames);
|
||||
|
||||
/* Validate characters */
|
||||
for (var i=0; i<name.length; i++) {
|
||||
for (var i = 0; i < name.length; i++) {
|
||||
if (i) {
|
||||
if (!name[i].match(/^[a-z0-9_-]$/))
|
||||
return validationFailure(errorText,
|
||||
@@ -331,16 +300,20 @@ Page
|
||||
}
|
||||
|
||||
/* Validate against reserved usernames */
|
||||
for (var i=0;i<reserved.length;i++) {
|
||||
for (var i = 0; i < reserved.length; i++) {
|
||||
if (name == reserved[i])
|
||||
return validationFailure(errorText, "Username '" +
|
||||
reserved[i] +
|
||||
"' is reserved.")
|
||||
"' is reserved.");
|
||||
}
|
||||
|
||||
/* Passed */
|
||||
return validationFailureClear(errorText);
|
||||
}
|
||||
|
||||
function validateSshdUsername(username, errorText) {
|
||||
return validateUsername(username, errorText, [config.username]);
|
||||
}
|
||||
function validateSshdPassword(password, passwordRepeat, errorText) {
|
||||
var pass = password.text;
|
||||
var repeat = passwordRepeat.text;
|
||||
@@ -348,10 +321,10 @@ Page
|
||||
if (pass == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
if (pass.length < 8)
|
||||
if (pass.length < 6)
|
||||
return validationFailure(errorText,
|
||||
"Too short: needs at least 8" +
|
||||
" characters.");
|
||||
"Too short: needs at least 6" +
|
||||
" digits/characters.");
|
||||
|
||||
if (repeat == "")
|
||||
return validationFailure(errorText);
|
||||
@@ -361,8 +334,6 @@ Page
|
||||
|
||||
return validationFailureClear(errorText);
|
||||
}
|
||||
|
||||
/* Input validation: fde_pass */
|
||||
function check_chars(input) {
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
if (allowed_chars.indexOf(input[i]) == -1)
|
||||
@@ -385,18 +356,28 @@ Page
|
||||
if (pass == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
/* This function gets called for the FDE password and for the user
|
||||
* password. As of writing, all distributions shipping the mobile
|
||||
* module are using osk-sdl to type in the FDE password after the
|
||||
* installation, and another keyboard after booting up, to type in the
|
||||
* user password. The osk-sdl password has the same keys as
|
||||
* squeekboard's default layout, and other keyboards should be able to
|
||||
* type these characters in as well. For now, verify that the password
|
||||
* only contains characters that can be typed in by osk-sdl. If you
|
||||
* need this to be more sophisticated, feel free to submit patches to
|
||||
* make this more configurable. */
|
||||
if (!check_chars(pass))
|
||||
return validationFailure(errorText,
|
||||
"The password must only contain" +
|
||||
" these characters, others cannot be" +
|
||||
" typed in at boot time:\n" +
|
||||
" these characters, others can possibly" +
|
||||
" not be typed in after installation:\n" +
|
||||
"\n" +
|
||||
allowed_chars_multiline());
|
||||
|
||||
if (pass.length < 8)
|
||||
if (pass.length < 6)
|
||||
return validationFailure(errorText,
|
||||
"Too short: needs at least 8" +
|
||||
" characters.");
|
||||
"Too short: needs at least 6" +
|
||||
" digits/characters.");
|
||||
|
||||
if (repeat == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
<file>install_target.qml</file> <!-- install from external to internal? -->
|
||||
<file>install_target_confirm.qml</file> <!-- overwrite internal storage? -->
|
||||
|
||||
<file>default_pin.qml</file> <!-- default user: pin -->
|
||||
<file>user_pass.qml</file> <!-- default user: username, password -->
|
||||
<file>ssh_confirm.qml</file> <!-- sshd: enable or not? -->
|
||||
<file>ssh_credentials.qml</file> <!-- sshd user: username, password -->
|
||||
<file>fs_selection.qml</file> <!-- filesystem selection -->
|
||||
|
||||
<file>fde_confirm.qml</file> <!-- enable FDE or not? -->
|
||||
<file>fde_pass.qml</file> <!-- FDE password (optional) -->
|
||||
|
||||
@@ -36,7 +36,7 @@ Item {
|
||||
"More information:<br>" +
|
||||
"https://postmarketos.org/ssh"
|
||||
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
@@ -44,7 +44,7 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
width: 200
|
||||
|
||||
text: qsTr("Enable")
|
||||
onClicked: {
|
||||
@@ -57,7 +57,7 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: firstButton.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
width: 200
|
||||
|
||||
text: qsTr("Disable")
|
||||
onClicked: {
|
||||
|
||||
@@ -34,7 +34,7 @@ Item {
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -45,7 +45,7 @@ Item {
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
@@ -59,7 +59,7 @@ Item {
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
@@ -73,7 +73,7 @@ Item {
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -84,13 +84,13 @@ Item {
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: errorTextPassword.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 500
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
|
||||
143
modules/mobile/user_pass.qml
Normal file
@@ -0,0 +1,143 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
property var passPlaceholder: (config.userPasswordNumeric
|
||||
? "PIN"
|
||||
: "Password")
|
||||
property var hints: (config.userPasswordNumeric
|
||||
? Qt.ImhDigitsOnly
|
||||
: Qt.ImhPreferLowercase)
|
||||
property var validatePassFunc: (config.userPasswordNumeric
|
||||
? validatePin
|
||||
: validatePassword);
|
||||
|
||||
property var validateNameFunc: validateUsername;
|
||||
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: usernameDescription
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
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
|
||||
|
||||
text: (function() {
|
||||
if (config.userPasswordNumeric) {
|
||||
return "Set the numeric password of your user. The" +
|
||||
" lockscreen will ask for this PIN. This is" +
|
||||
" <i>not</i> the PIN of your SIM card. Make sure to" +
|
||||
" remember it.";
|
||||
} else {
|
||||
return "Set the password of your user. The lockscreen will" +
|
||||
" ask for this password. Make sure to remember it.";
|
||||
}
|
||||
}())
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userPass
|
||||
anchors.top: userPassDescription.bottom
|
||||
placeholderText: qsTr(passPlaceholder)
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validatePassFunc(userPass, userPassRepeat, errorText)
|
||||
text: config.userPassword
|
||||
|
||||
/* Let the virtual keyboard change to digits only */
|
||||
inputMethodHints: hints
|
||||
onActiveFocusChanged: {
|
||||
if(activeFocus) {
|
||||
Qt.inputMethod.update(Qt.ImQueryInput)
|
||||
}
|
||||
}
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userPassRepeat
|
||||
anchors.top: userPass.bottom
|
||||
placeholderText: qsTr(passPlaceholder + " (repeat)")
|
||||
inputMethodHints: hints
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validatePassFunc(userPass, userPassRepeat, errorText)
|
||||
text: config.userPassword
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.top: userPassRepeat.bottom
|
||||
id: errorText
|
||||
visible: false
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: errorText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
if (validatePassFunc(userPass, userPassRepeat, errorText) && validateNameFunc(username, errorText)) {
|
||||
config.userPassword = userPass.text;
|
||||
config.username = username.text;
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ Page
|
||||
id: logo
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 50
|
||||
height: 250
|
||||
anchors.topMargin: 10
|
||||
height: 50
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
|
||||
}
|
||||
@@ -35,11 +35,11 @@ Page
|
||||
id: waitText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: logo.bottom
|
||||
anchors.topMargin: 150
|
||||
anchors.topMargin: 50
|
||||
wrapMode: Text.WordWrap
|
||||
text: "Formatting and mounting target partition. This may" +
|
||||
" take up to 20 seconds, please be patient."
|
||||
width: 500
|
||||
" take up to ten minutes, please be patient."
|
||||
width: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ Page
|
||||
id: logo
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 50
|
||||
height: 250
|
||||
anchors.topMargin: 10
|
||||
height: 50
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
|
||||
}
|
||||
@@ -37,7 +37,7 @@ Page
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: logo.bottom
|
||||
anchors.topMargin: 50
|
||||
anchors.topMargin: 10
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: "You are about to install<br>" +
|
||||
"<b>" + config.osName +
|
||||
@@ -46,16 +46,16 @@ Page
|
||||
"<b>" + config.userInterface + "</b><br>" +
|
||||
"architecture " +
|
||||
"<b>" + config.arch + "</b><br>" +
|
||||
"on your " +
|
||||
"on your <br>" +
|
||||
"<b>" + config.device + "</b><br>"
|
||||
width: 500
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 50
|
||||
width: 500
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: navNext()
|
||||
|
||||
20
modules/os-freebsd/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
# The OS-FreeBSD module does "all the things" in a FreeBSD installation.
|
||||
# Since the other modules -- users, fstab, grub, pretty much all of them
|
||||
# -- are Linux-specific, it doesn't make much sense to fork each of them
|
||||
# or provide alternatives, so instead we have one module that completes
|
||||
# a FreeBSD installation based on the GlobalStorage values set by
|
||||
# Calamares viewmodules.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# License-Filename: LICENSE
|
||||
#
|
||||
|
||||
calamares_add_plugin( os-freebsd
|
||||
TYPE job
|
||||
EXPORT_MACRO PLUGINDLLEXPORT_PRO
|
||||
SOURCES
|
||||
FreeBSDJob.cpp
|
||||
SHARED_LIB
|
||||
NO_CONFIG
|
||||
)
|
||||
58
modules/os-freebsd/FreeBSDJob.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* License-Filename: LICENSE
|
||||
*/
|
||||
|
||||
#include "FreeBSDJob.h"
|
||||
|
||||
#include "CalamaresVersion.h"
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QProcess>
|
||||
#include <QThread>
|
||||
|
||||
|
||||
FreeBSDJob::FreeBSDJob( QObject* parent )
|
||||
: Calamares::CppJob( parent )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
FreeBSDJob::~FreeBSDJob() {}
|
||||
|
||||
|
||||
QString
|
||||
FreeBSDJob::prettyName() const
|
||||
{
|
||||
return tr( "FreeBSD Installation Job" );
|
||||
}
|
||||
|
||||
Calamares::JobResult
|
||||
FreeBSDJob::exec()
|
||||
{
|
||||
emit progress( 0.1 );
|
||||
cDebug() << "[FREEBSD]";
|
||||
|
||||
Calamares::JobQueue::instance()->globalStorage()->debugDump();
|
||||
emit progress( 0.5 );
|
||||
|
||||
QThread::sleep( 3 );
|
||||
emit progress( 1.0 );
|
||||
|
||||
return Calamares::JobResult::ok();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FreeBSDJob::setConfigurationMap( const QVariantMap& configurationMap )
|
||||
{
|
||||
// TODO: actually fetch something from that configuration
|
||||
m_configurationMap = configurationMap;
|
||||
}
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DEFINITION( FreeBSDJobFactory, registerPlugin< FreeBSDJob >(); )
|
||||
39
modules/os-freebsd/FreeBSDJob.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* License-Filename: LICENSE
|
||||
*/
|
||||
|
||||
#ifndef FREEBSDJOB_H
|
||||
#define FREEBSDJOB_H
|
||||
|
||||
#include "CppJob.h"
|
||||
#include "DllMacro.h"
|
||||
#include "utils/PluginFactory.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
|
||||
|
||||
class PLUGINDLLEXPORT FreeBSDJob : public Calamares::CppJob
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FreeBSDJob( QObject* parent = nullptr );
|
||||
virtual ~FreeBSDJob() override;
|
||||
|
||||
QString prettyName() const override;
|
||||
|
||||
Calamares::JobResult exec() override;
|
||||
|
||||
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
||||
|
||||
private:
|
||||
QVariantMap m_configurationMap;
|
||||
};
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DECLARATION( FreeBSDJobFactory )
|
||||
|
||||
#endif // FREEBSDJOB_H
|
||||
276
modules/os-nixos/main.py
Normal file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
"""
|
||||
=== NixOS Configuration
|
||||
|
||||
NixOS has its own "do all the things" configuration file which
|
||||
declaratively handles what things need to be done in the target
|
||||
system, and it has an existing tool to "execute" that declarative
|
||||
specification. This module takes configuration values set by
|
||||
Calamares viewmodules (e.g. the users module) and puts
|
||||
them into the configuration file in the target system,
|
||||
and then runs the necessary NixOS specific tools.
|
||||
"""
|
||||
|
||||
import libcalamares
|
||||
import os
|
||||
from time import gmtime, strftime, sleep
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
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. It‘s 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():
|
||||
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():
|
||||
"""NixOS Configuration."""
|
||||
|
||||
gs = libcalamares.globalstorage
|
||||
text = configuration_nix_sample
|
||||
|
||||
# Collect variables to substitute into the main text
|
||||
variables = dict()
|
||||
catenate(variables, "hostname", gs.value("hostname"))
|
||||
catenate(variables, "timezone", gs.value("locationRegion"), "/", gs.value("locationZone"))
|
||||
|
||||
# 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))
|
||||
|
||||
# Check that all patterns exist
|
||||
import re
|
||||
variable_pattern = re.compile("@@\w+@@")
|
||||
for match in variable_pattern.finditer(text):
|
||||
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))
|
||||
|
||||
# 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:
|
||||
# (message, detailed-error-message)
|
||||
return None
|
||||
11
modules/os-nixos/module.desc
Normal file
@@ -0,0 +1,11 @@
|
||||
# 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"
|
||||
name: "os-nixos"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
6
modules/os-nixos/tests/1.global
Normal file
@@ -0,0 +1,6 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
hostname: my-nix-host
|
||||
locationRegion: Asia
|
||||
locationZone: Kolkata
|
||||
58
modules/packagechooser/CMakeLists.txt
Normal 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}
|
||||
# )
|
||||
444
modules/packagechooser/Config.cpp
Normal 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" );
|
||||
}
|
||||
}
|
||||
}
|
||||
171
modules/packagechooser/Config.h
Normal 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
|
||||
225
modules/packagechooser/ItemAppData.cpp
Normal 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();
|
||||
}
|
||||
28
modules/packagechooser/ItemAppData.h
Normal 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
|
||||
165
modules/packagechooser/ItemAppStream.cpp
Normal 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;
|
||||
}
|
||||
52
modules/packagechooser/ItemAppStream.h
Normal 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
|
||||
205
modules/packagechooser/LoaderQueue.cpp
Normal 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 );
|
||||
}
|
||||
}
|
||||
77
modules/packagechooser/LoaderQueue.h
Normal 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
|
||||
145
modules/packagechooser/PackageChooserPage.cpp
Normal 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;
|
||||
}
|
||||
57
modules/packagechooser/PackageChooserPage.h
Normal 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
|
||||
158
modules/packagechooser/PackageChooserViewStep.cpp
Normal 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() );
|
||||
}
|
||||
57
modules/packagechooser/PackageChooserViewStep.h
Normal 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
|
||||
575
modules/packagechooser/PackageModel.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
214
modules/packagechooser/PackageModel.h
Normal 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
|
||||
311
modules/packagechooser/PackageTreeItem.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
179
modules/packagechooser/PackageTreeItem.h
Normal 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
|
||||
84
modules/packagechooser/Tests.cpp
Normal 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
|
||||
}
|
||||
28
modules/packagechooser/Tests.h
Normal 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
|
||||
BIN
modules/packagechooser/images/if.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
2
modules/packagechooser/images/if.png.license
Normal file
@@ -0,0 +1,2 @@
|
||||
SPDX-FileCopyrightText: 2014 Uri Herrera <uri_herrera@nitrux.in> and others
|
||||
SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
172
modules/packagechooser/packagechooser.conf
Normal 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
|
||||
5
modules/packagechooser/packagechooser.qrc
Normal file
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>images/if.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
104
modules/packagechooser/page_package.ui
Normal file
@@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<author>
|
||||
SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
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
@@ -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()
|
||||
11
modules/refind/module.desc
Normal 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" ]
|
||||
12
modules/refind/refind.conf.in
Normal 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"
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# The slowpython module is .. just slow. It can be used
|
||||
# in testing to allow the slideshow time to run.
|
||||
---
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# This is an example module for Python Job Modules.
|
||||
#
|
||||
# The slowpython module is just slow. It does produce
|
||||
|
||||
15
modules/unpackfsc/CMakeLists.txt
Normal 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
|
||||
)
|
||||
117
modules/unpackfsc/FSArchiverRunner.cpp
Normal 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 ) );
|
||||
}
|
||||
59
modules/unpackfsc/FSArchiverRunner.h
Normal 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
|
||||