Compare commits
	
		
			1 Commits
		
	
	
		
			old-module
			...
			packagecho
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 674bb81358 | 
| @@ -56,29 +56,6 @@ project(calamares-extensions | ||||
| set( CMAKE_CXX_STANDARD 17 ) | ||||
| set( CMAKE_CXX_STANDARD_REQUIRED ON ) | ||||
|  | ||||
|  | ||||
| if(WITH_QT6) | ||||
|     set(kfname "KF6") | ||||
|     set(KF_VERSION 5.240) # KDE Neon weirdness | ||||
| else() | ||||
|     message(STATUS "Building Calamares with Qt5") | ||||
|     set(kfname "KF5") | ||||
|     set(KF_VERSION 5.78) | ||||
|     # API that was deprecated before Qt 5.15 causes a compile error | ||||
|     add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x050f00) | ||||
| endif() | ||||
|  | ||||
| include( FeatureSummary ) | ||||
| find_package(${kfname}CoreAddons ${KF_VERSION} QUIET) | ||||
| set_package_properties( | ||||
|     ${kfname}CoreAddons | ||||
|     PROPERTIES | ||||
|     TYPE REQUIRED | ||||
|     DESCRIPTION "KDE Framework CoreAddons" | ||||
|     URL "https://api.kde.org/frameworks/" | ||||
|     PURPOSE "Essential Framework for AboutData and Macros" | ||||
| ) | ||||
|  | ||||
| # 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 | ||||
| @@ -119,19 +96,18 @@ include( CTest ) | ||||
| # Typically you would use only one branding, since that's | ||||
| # the (single) branding for your distro. | ||||
| # | ||||
| # calamares_add_branding_subdirectory( branding/default NAME default ) | ||||
| # calamares_add_branding_subdirectory( branding/default-mobile NAME default-mobile ) | ||||
| # calamares_add_branding_subdirectory( branding/fancy NAME fancy ) | ||||
| calamares_add_branding_subdirectory( branding/default NAME default ) | ||||
| calamares_add_branding_subdirectory( branding/default-mobile NAME default-mobile ) | ||||
| calamares_add_branding_subdirectory( branding/fancy NAME fancy ) | ||||
|  | ||||
| # This one has files in subdirectories | ||||
| # calamares_add_branding_subdirectory( branding/samegame NAME samegame SUBDIRECTORIES img ) | ||||
| calamares_add_branding_subdirectory( branding/samegame NAME samegame SUBDIRECTORIES img ) | ||||
|  | ||||
| # KaOS branding, with translations, note we can *NAME* something | ||||
| # different from the source directory it lives in; this will be installed | ||||
| # to a directory called *NAME* though -- and the `branding.desc` must | ||||
| # have a *componentName* that matches this *NAME*. | ||||
| # calamares_add_branding_subdirectory( branding/kaos_branding NAME kaos ) | ||||
| calamares_add_branding_subdirectory( branding/artix NAME artix ) | ||||
| calamares_add_branding_subdirectory( branding/kaos_branding NAME kaos ) | ||||
|  | ||||
| ### MODULES | ||||
| # | ||||
| @@ -139,22 +115,13 @@ calamares_add_branding_subdirectory( branding/artix NAME artix ) | ||||
| # | ||||
| set(LIST_SKIPPED_MODULES "") | ||||
|  | ||||
| # 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 ) | ||||
|  | ||||
| calamares_add_module_subdirectory( modules/basestrap LIST_SKIPPED_MODULES ) | ||||
| calamares_add_module_subdirectory( modules/services-artix LIST_SKIPPED_MODULES ) | ||||
| # calamares_add_module_subdirectory( modules/services-runit LIST_SKIPPED_MODULES ) | ||||
| # calamares_add_module_subdirectory( modules/services-dinit LIST_SKIPPED_MODULES ) | ||||
| # calamares_add_module_subdirectory( modules/services-s6 LIST_SKIPPED_MODULES ) | ||||
| calamares_add_module_subdirectory( modules/postcfg LIST_SKIPPED_MODULES ) | ||||
| calamares_add_module_subdirectory( modules/packagechooser LIST_SKIPPED_MODULES ) | ||||
| calamares_add_module_subdirectory( modules/packagechooserq LIST_SKIPPED_MODULES ) | ||||
| 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}") | ||||
|  | ||||
|   | ||||
| Before Width: | Height: | Size: 70 KiB | 
| Before Width: | Height: | Size: 6.8 KiB | 
| @@ -1,239 +0,0 @@ | ||||
| # SPDX-FileCopyrightText: no | ||||
| # SPDX-License-Identifier: CC0-1.0 | ||||
| # | ||||
| # Product branding information. This influences some global | ||||
| # user-visible aspects of Calamares, such as the product | ||||
| # name, window behavior, and the slideshow during installation. | ||||
| # | ||||
| # Additional styling can be done using the stylesheet.qss | ||||
| # file, also in the branding directory. | ||||
| --- | ||||
| componentName:  artix | ||||
|  | ||||
|  | ||||
| ### WELCOME / OVERALL WORDING | ||||
| # | ||||
| # These settings affect some overall phrasing and looks, | ||||
| # which are most visible in the welcome page. | ||||
|  | ||||
| # This selects between different welcome texts. When false, uses | ||||
| # the traditional "Welcome to the %1 installer.", and when true, | ||||
| # uses "Welcome to the Calamares installer for %1." This allows | ||||
| # to distinguish this installer from other installers for the | ||||
| # same distribution. | ||||
| welcomeStyleCalamares:   false | ||||
|  | ||||
| # Should the welcome image (productWelcome, below) be scaled | ||||
| # up beyond its natural size? If false, the image does not grow | ||||
| # with the window but remains the same size throughout (this | ||||
| # may have surprising effects on HiDPI monitors). | ||||
| welcomeExpandingLogo:   true | ||||
|  | ||||
| ### WINDOW CONFIGURATION | ||||
| # | ||||
| # The settings here affect the placement of the Calamares | ||||
| # window through hints to the window manager and initial | ||||
| # sizing of the Calamares window. | ||||
|  | ||||
| # Size and expansion policy for Calamares. | ||||
| #  - "normal" or unset, expand as needed, use *windowSize* | ||||
| #  - "fullscreen", start as large as possible, ignore *windowSize* | ||||
| #  - "noexpand", don't expand automatically, use *windowSize* | ||||
| windowExpanding:    normal | ||||
|  | ||||
| # Size of Calamares window, expressed as w,h. Both w and h | ||||
| # may be either pixels (suffix px) or font-units (suffix em). | ||||
| #   e.g.    "800px,600px" | ||||
| #           "60em,480px" | ||||
| # This setting is ignored if "fullscreen" is selected for | ||||
| # *windowExpanding*, above. If not set, use constants defined | ||||
| # in CalamaresUtilsGui, 800x520. | ||||
| windowSize: 800px,520px | ||||
|  | ||||
| # Placement of Calamares window. Either "center" or "free". | ||||
| # Whether "center" actually works does depend on the window | ||||
| # manager in use (and only makes sense if you're not using | ||||
| # *windowExpanding* set to "fullscreen"). | ||||
| windowPlacement: center | ||||
|  | ||||
| ### PANELS CONFIGURATION | ||||
| # | ||||
| # Calamares has a main content area, and two panels (navigation | ||||
| # and progress / sidebar). The panels can be controlled individually, | ||||
| # or switched off. If both panels are switched off, the layout of | ||||
| # the main content area loses its margins, on the assumption that | ||||
| # you're doing something special. | ||||
|  | ||||
| # Kind of sidebar (panel on the left, showing progress). | ||||
| #  - "widget" or unset, use traditional sidebar (logo, items) | ||||
| #  - "none", hide it entirely | ||||
| #  - "qml", use calamares-sidebar.qml from branding folder | ||||
| # In addition, you **may** specify a side, separated by a comma, | ||||
| # from the kind. Valid sides are: | ||||
| #  - "left" (if not specified, uses this) | ||||
| #  - "right" | ||||
| #  - "top" | ||||
| #  - "bottom" | ||||
| # For instance, "widget,right" is valid; so is "qml", which defaults | ||||
| # to putting the sidebar on the left. Also valid is "qml,top". | ||||
| # While "widget,top" is valid, the widgets code is **not** flexible | ||||
| # and results will be terrible. | ||||
| sidebar: widget | ||||
|  | ||||
| # Kind of navigation (button panel on the bottom). | ||||
| #  - "widget" or unset, use traditional navigation | ||||
| #  - "none", hide it entirely | ||||
| #  - "qml", use calamares-navigation.qml from branding folder | ||||
| # In addition, you **may** specify a side, separated by a comma, | ||||
| # from the kind. The same sides are valid as for *sidebar*, | ||||
| # except the default is *bottom*. | ||||
| navigation: widget | ||||
|  | ||||
|  | ||||
| ### STRINGS, IMAGES AND COLORS | ||||
| # | ||||
| # This section contains the "branding proper" of names | ||||
| # and images, rather than global-look settings. | ||||
|  | ||||
| # These are strings shown to the user in the user interface. | ||||
| # There is no provision for translating them -- since they | ||||
| # are names, the string is included as-is. | ||||
| # | ||||
| # The four Url strings are the Urls used by the buttons in | ||||
| # the welcome screen, and are not shown to the user. Clicking | ||||
| # on the "Support" button, for instance, opens the link supportUrl. | ||||
| # If a Url is empty, the corresponding button is not shown. | ||||
| # | ||||
| # bootloaderEntryName is how this installation / distro is named | ||||
| # in the boot loader (e.g. in the GRUB menu). | ||||
| # | ||||
| # These strings support substitution from /etc/os-release | ||||
| # if KDE Frameworks 5.58 are available at build-time. When | ||||
| # enabled, ${varname} is replaced by the equivalent value | ||||
| # from os-release. All the supported var-names are in all-caps, | ||||
| # and are listed on the FreeDesktop.org site, | ||||
| #       https://www.freedesktop.org/software/systemd/man/os-release.html | ||||
| # Note that ANSI_COLOR and CPE_NAME don't make sense here, and | ||||
| # are not supported (the rest are). Remember to quote the string | ||||
| # if it contains substitutions, or you'll get YAML exceptions. | ||||
| # | ||||
| # The *Url* entries are used on the welcome page, and they | ||||
| # are visible as buttons there if the corresponding *show* keys | ||||
| # are set to "true" (they can also be overridden). | ||||
| strings: | ||||
|     productName:         Artix Linux | ||||
|     shortProductName:    Artix | ||||
|     version:             rolling | ||||
|     shortVersion:        rolling | ||||
|     versionedName:       Artix Linux "rolling" | ||||
|     shortVersionedName:  Artix rolling | ||||
|     bootloaderEntryName: Artix | ||||
|     productUrl:          https://www.artixlinux.org/ | ||||
|     supportUrl:          https://github.com/calamares/calamares/issues | ||||
|     knownIssuesUrl:      https://calamares.io/about/ | ||||
|     releaseNotesUrl:     https://calamares.io/about/ | ||||
| #     donateUrl:           https://kde.org/community/donations/index.php | ||||
|  | ||||
| # These images are loaded from the branding module directory. | ||||
| # | ||||
| # productBanner is an optional image, which if present, will be shown | ||||
| #       on the welcome page of the application, above the welcome text. | ||||
| #       It is intended to have a width much greater than height. | ||||
| #       It is displayed at 64px height (also on HiDPI). | ||||
| #       Recommended size is 64px tall, and up to 460px wide. | ||||
| # productIcon is used as the window icon, and will (usually) be used | ||||
| #       by the window manager to represent the application. This image | ||||
| #       should be square, and may be displayed by the window manager | ||||
| #       as small as 16x16 (but possibly larger). | ||||
| # productLogo is used as the logo at the top of the left-hand column | ||||
| #       which shows the steps to be taken. The image should be square, | ||||
| #       and is displayed at 80x80 pixels (also on HiDPI). | ||||
| # productWallpaper is an optional image, which if present, will replace | ||||
| #       the normal solid background on every page of the application. | ||||
| #       It can be any size and proportion, | ||||
| #       and will be tiled to fit the entire window. | ||||
| #       For a non-tiled wallpaper, the size should be the same as | ||||
| #       the overall window, see *windowSize* above (800x520). | ||||
| # productWelcome is shown on the welcome page of the application in | ||||
| #       the middle of the window, below the welcome text. It can be | ||||
| #       any size and proportion, and will be scaled to fit inside | ||||
| #       the window. Use `welcomeExpandingLogo` to make it non-scaled. | ||||
| #       Recommended size is 320x150. | ||||
| # | ||||
| # These filenames can also use substitutions from os-release (see above). | ||||
| images: | ||||
|     productBanner:       "banner.png" | ||||
|     productIcon:         "logo.png" | ||||
|     productLogo:         "logo.png" | ||||
|     # productWallpaper:    "wallpaper.png" | ||||
|     productWelcome:      "languages.png" | ||||
|  | ||||
| # Colors for text and background components. | ||||
| # | ||||
| #  - SidebarBackground is the background of the sidebar | ||||
| #  - SidebarText is the (foreground) text color | ||||
| #  - SidebarBackgroundCurrent sets the background of the current step. | ||||
| #    Optional, and defaults to the application palette. | ||||
| #  - SidebarTextCurrent is the text color of the current step. | ||||
| # | ||||
| # These colors can **also** be set through the stylesheet, if the | ||||
| # branding component also ships a stylesheet.qss. Then they are | ||||
| # the corresponding CSS attributes of #sidebarApp. | ||||
| style: | ||||
|     SidebarBackground:        "#292F34" | ||||
|     SidebarText:              "#FFFFFF" | ||||
|     SidebarTextCurrent:       "#292F34" | ||||
|     SidebarBackgroundCurrent: "#16a3f5" | ||||
|  | ||||
| ### SLIDESHOW | ||||
| # | ||||
| # The slideshow is displayed during execution steps (e.g. when the | ||||
| # installer is actually writing to disk and doing other slow things). | ||||
|  | ||||
| # The slideshow can be a QML file (recommended) which can display | ||||
| # arbitrary things -- text, images, animations, or even play a game -- | ||||
| # during the execution step. The QML **is** abruptly stopped when the | ||||
| # execution step is done, though, so maybe a game isn't a great idea. | ||||
| # | ||||
| # The slideshow can also be a sequence of images (not recommended unless | ||||
| # you don't want QML at all in your Calamares). The images are displayed | ||||
| # at a rate of 1 every 2 seconds during the execution step. | ||||
| # | ||||
| # To configure a QML file, list a single filename: | ||||
| #   slideshow:               "show.qml" | ||||
| # To configure images, like the filenames (here, as an inline list): | ||||
| #   slideshow: [ "/etc/calamares/slideshow/0.png", "/etc/logo.png" ] | ||||
| slideshow:               "show.qml" | ||||
|  | ||||
| # There are two available APIs for a QML slideshow: | ||||
| #  - 1 (the default) loads the entire slideshow when the installation- | ||||
| #      slideshow page is shown and starts the QML then. The QML | ||||
| #      is never stopped (after installation is done, times etc. | ||||
| #      continue to fire). | ||||
| #  - 2 loads the slideshow on startup and calls onActivate() and | ||||
| #      onLeave() in the root object. After the installation is done, | ||||
| #      the show is stopped (first by calling onLeave(), then destroying | ||||
| #      the QML components). | ||||
| # | ||||
| # An image slideshow does not need to have the API defined. | ||||
| slideshowAPI: 2 | ||||
|  | ||||
|  | ||||
| # These options are to customize online uploading of logs to pastebins: | ||||
| #  - type      : Defines the kind of pastebin service to be used. Currently | ||||
| #                it accepts two values: | ||||
| #                - none    :    disables the pastebin functionality | ||||
| #                - fiche   :    use fiche pastebin server | ||||
| #  - url       : Defines the address of pastebin service to be used. | ||||
| #                Takes string as input. Important bits are the host and port, | ||||
| #                the scheme is not used. | ||||
| #  - sizeLimit : Defines maximum size limit (in KiB) of log file to be pasted. | ||||
| #                The option must be set, to have the log option work. | ||||
| #                Takes integer as input. If < 0, no limit will be forced, | ||||
| #                else only last (approximately) 'n' KiB of log file will be pasted. | ||||
| #                Please note that upload size may be slightly over the limit (due | ||||
| #                to last minute logging), so provide a suitable value. | ||||
| uploadServer : | ||||
|     type :    "fiche" | ||||
|     url :     "http://termbin.com:9999" | ||||
|     sizeLimit : -1 | ||||
| Before Width: | Height: | Size: 399 KiB | 
| Before Width: | Height: | Size: 818 KiB | 
| Before Width: | Height: | Size: 151 KiB | 
| Before Width: | Height: | Size: 84 KiB | 
| @@ -1,2 +0,0 @@ | ||||
| SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org> | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| Before Width: | Height: | Size: 4.4 KiB | 
| Before Width: | Height: | Size: 363 KiB | 
| Before Width: | Height: | Size: 359 KiB | 
| Before Width: | Height: | Size: 873 KiB | 
| Before Width: | Height: | Size: 1.3 MiB | 
| Before Width: | Height: | Size: 502 KiB | 
| Before Width: | Height: | Size: 121 KiB | 
| @@ -1,178 +0,0 @@ | ||||
| /* === This file is part of Calamares - <http://github.com/calamares> === | ||||
|  * | ||||
|  *   Copyright 2015, Teo Mrnjavac <teo@kde.org> | ||||
|  *   Copyright 2016, Luca Giambonini <almack@chakralinux.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/>. | ||||
|  */ | ||||
|  | ||||
| import QtQuick 2.0; | ||||
| import calamares.slideshow 1.0; | ||||
|  | ||||
| /* Tested with slide images of 1800x1200 and 1632x1248 pixels */ | ||||
|  | ||||
| Presentation | ||||
| { | ||||
|     id: presentation | ||||
|  | ||||
|     Timer { | ||||
|         interval: 10000 | ||||
|         running: true | ||||
|         repeat: true | ||||
|         onTriggered: presentation.goToNextSlide() | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic0 | ||||
|             source: "thanks.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic00 | ||||
|             source: "artix.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic02 | ||||
|             source: "tools.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic03 | ||||
|             source: "desktops.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic04 | ||||
|             source: "productivity.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic06 | ||||
|             source: "browsers.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic07 | ||||
|             source: "office.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic08 | ||||
|             source: "multimedia.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic09 | ||||
|             source: "web.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic11 | ||||
|             source: "packages1.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic13 | ||||
|             source: "packages2.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic15 | ||||
|             source: "customise.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Slide { | ||||
|         Image { | ||||
|             id: pic16 | ||||
|             source: "rolling.png" | ||||
|             width: parent.width * 1.12; height: parent.height * 1.5 | ||||
|             fillMode: Image.Stretch | ||||
|             anchors.centerIn: parent | ||||
|             anchors.verticalCenterOffset: -20 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,96 +0,0 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: no | ||||
|  * SPDX-License-Identifier: CC0-1.0 | ||||
|  */ | ||||
|  | ||||
| /* | ||||
| A branding component can ship a stylesheet (like this one) | ||||
| which is applied to parts of the Calamares user-interface. | ||||
| In principle, all parts can be styled through CSS. | ||||
| Missing parts should be filed as issues. | ||||
|  | ||||
| The IDs are based on the object names in the C++ code. | ||||
| You can use the Debug Dialog to find out object names: | ||||
|   - Open the debug dialog | ||||
|   - Choose tab *Tools* | ||||
|   - Click *Widget Tree* button | ||||
| The list of object names is printed in the log. | ||||
|  | ||||
| Documentation for styling Qt Widgets through a stylesheet | ||||
| can be found at | ||||
|     https://doc.qt.io/qt-5/stylesheet-examples.html | ||||
|     https://doc.qt.io/qt-5/stylesheet-reference.html | ||||
| In Calamares, styling widget classes is supported (e.g. | ||||
| using `QComboBox` as a selector). | ||||
|  | ||||
| This example stylesheet has all the actual styling commented out. | ||||
| The examples are not exhaustive. | ||||
|  | ||||
| */ | ||||
|  | ||||
| /*** Generic Widgets. | ||||
|  * | ||||
|  * You can style **all** widgets of a given class by selecting | ||||
|  * the class name. Some widgets have specialized sub-selectors. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
| QPushButton { background-color: green; } | ||||
| */ | ||||
|  | ||||
| /*** Main application window. | ||||
|  * | ||||
|  * The main application window has the sidebar, which in turn | ||||
|  * contains a logo and a list of items -- note that the list | ||||
|  * can **not** be styled, since it has its own custom C++ | ||||
|  * delegate code. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
| #mainApp { } | ||||
| #sidebarApp { } | ||||
| #logoApp { } | ||||
| */ | ||||
|  | ||||
| /*** Welcome module. | ||||
|  * | ||||
|  * There are plenty of parts, but the buttons are the most interesting | ||||
|  * ones (donate, release notes, ...). The little icon image can be | ||||
|  * styled through *qproperty-icon*, which is a little obscure. | ||||
|  * URLs can reference the QRC paths of the Calamares application | ||||
|  * or loaded via plugins or within the filesystem. There is no | ||||
|  * comprehensive list of available icons, though. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
| QPushButton#aboutButton { qproperty-icon: url(:/data/images/release.svg); } | ||||
| #donateButton, | ||||
| #supportButton, | ||||
| #releaseNotesButton, | ||||
| #knownIssuesButton { qproperty-icon: url(:/data/images/help.svg); } | ||||
| */ | ||||
|  | ||||
| /*** Partitioning module. | ||||
|  * | ||||
|  * Many moving parts, which you will need to experiment with. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
| #bootInfoIcon { } | ||||
| #bootInfoLable { } | ||||
| #deviceInfoIcon { } | ||||
| #defineInfoLabel { } | ||||
| #scrollAreaWidgetContents { } | ||||
| #partitionBarView { } | ||||
| */ | ||||
|  | ||||
| /*** Licensing module. | ||||
|  * | ||||
|  * The licensing module paints individual widgets for each of | ||||
|  * the licenses. The item can be collapsed or expanded. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
| #licenseItem {  } | ||||
| #licenseItemFullText {  } | ||||
| */ | ||||
| Before Width: | Height: | Size: 75 KiB | 
| Before Width: | Height: | Size: 160 KiB | 
| Before Width: | Height: | Size: 136 KiB | 
| @@ -1,75 +0,0 @@ | ||||
| # SPDX-FileCopyrightText: no | ||||
| # SPDX-License-Identifier: CC0-1.0 | ||||
| # | ||||
| # The configuration for the package manager starts with the | ||||
| # *backend* key, which picks one of the backends to use. | ||||
| # In `main.py` there is a base class `PackageManager`. | ||||
| # Implementations must subclass that and set a (class-level) | ||||
| # property *backend* to the name of the backend (e.g. "dummy"). | ||||
| # That property is used to match against the *backend* key here. | ||||
| # | ||||
| # You will have to add such a class for your package manager. | ||||
| # It is fairly simple Python code. The API is described in the | ||||
| # abstract methods in class `PackageManager`. Mostly, the only | ||||
| # trick is to figure out the correct commands to use, and in particular, | ||||
| # whether additional switches are required or not. Some package managers | ||||
| # have more installer-friendly defaults than others, e.g., DNF requires | ||||
| # passing --disablerepo=* -C to allow removing packages without Internet | ||||
| # connectivity, and it also returns an error exit code if the package did | ||||
| # not exist to begin with. | ||||
| --- | ||||
| # | ||||
| # Which package manager to use, options are: | ||||
| #  - pacman      - Pacman | ||||
| # | ||||
| # Not actually a package manager, but suitable for testing: | ||||
| #  - dummy       - Dummy manager, only logs | ||||
| # | ||||
| backend: dummy | ||||
|  | ||||
| # pacman specific options | ||||
| # | ||||
| # *num_retries* should be a positive integer which specifies the | ||||
| # number of times the call to pacman will be retried in the event of a | ||||
| # failure.  If it is missing, it will be set to 0. | ||||
| # | ||||
| # *disable_download_timeout* is a boolean that, when true, includes | ||||
| # the flag --disable-download-timeout on calls to pacman.  When missing, | ||||
| # false is assumed. | ||||
| # | ||||
| # *needed_only* is a boolean that includes the pacman argument --needed | ||||
| # when set to true.  If missing, false is assumed. | ||||
| # *handle_keyrings* is a boolean that includes initializing and populating keyrings | ||||
| # when set to true.  If missing, false is assumed. | ||||
|  | ||||
| pacman: | ||||
|     num_retries: 0 | ||||
|     disable_download_timeout: false | ||||
|     needed_only: false | ||||
|     handle_keyrings: false | ||||
|     copy_pacconf: false | ||||
|     requirements: | ||||
|       - name: /etc | ||||
|         mode: "0o755" | ||||
|       - name: /var | ||||
|         mode: "0o755" | ||||
|       - name: /var/cache | ||||
|         mode: "0o755" | ||||
|       - name: /var/cache/pacman | ||||
|         mode: "0o755" | ||||
|       - name: /var/cache/pacman/pkg | ||||
|         mode: "0o755" | ||||
|       - name: /var/lib | ||||
|         mode: "0o755" | ||||
|       - name: /var/lib/pacman | ||||
|         mode: "0o755" | ||||
|     keyrings: | ||||
|       - artix | ||||
|  | ||||
| # the artix base package allows selection of the init system tied to elogind | ||||
| # this option is artix specific | ||||
| # base_init: elogind | ||||
|  | ||||
| operations: | ||||
|   - install: | ||||
|     - base | ||||
| @@ -1,552 +0,0 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # === This file is part of Calamares - <https://calamares.io> === | ||||
| # | ||||
| #   SPDX-FileCopyrightText: 2014 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> | ||||
| #   SPDX-FileCopyrightText: 2015-2017 Teo Mrnjavac <teo@kde.org> | ||||
| #   SPDX-FileCopyrightText: 2016-2017 Kyle Robbertze <kyle@aims.ac.za> | ||||
| #   SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org> | ||||
| #   SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org> | ||||
| #   SPDX-FileCopyrightText: 2018 Philip Müller <philm@manjaro.org> | ||||
| #   SPDX-FileCopyrightText: 2023 Artoo <artoo@artixlinux.org> | ||||
| #   SPDX-License-Identifier: GPL-3.0-or-later | ||||
| # | ||||
| #   Calamares is Free Software: see the License-Identifier above. | ||||
| # | ||||
|  | ||||
| import abc | ||||
| from string import Template | ||||
| import os, shutil, subprocess, sys | ||||
|  | ||||
| import libcalamares | ||||
| from libcalamares.utils import host_env_process_output, target_env_process_output | ||||
| from libcalamares.utils import gettext_path, gettext_languages | ||||
| from os.path import join | ||||
|  | ||||
| import gettext | ||||
| _translation = gettext.translation("calamares-python", | ||||
|                                    localedir=gettext_path(), | ||||
|                                    languages=gettext_languages(), | ||||
|                                    fallback=True) | ||||
| _ = _translation.gettext | ||||
| _n = _translation.ngettext | ||||
|  | ||||
|  | ||||
| total_packages = 0  # For the entire job | ||||
| completed_packages = 0  # Done so far for this job | ||||
| group_packages = 0  # One group of packages from an -install or -remove entry | ||||
|  | ||||
| # A PM object may set this to a string (take care of translations!) | ||||
| # to override the string produced by pretty_status_message() | ||||
| custom_status_message = None | ||||
|  | ||||
| INSTALL = object() | ||||
| REMOVE = object() | ||||
| mode_packages = None  # Changes to INSTALL or REMOVE | ||||
|  | ||||
|  | ||||
| def _change_mode(mode): | ||||
|     global mode_packages | ||||
|     mode_packages = mode | ||||
|     libcalamares.job.setprogress(completed_packages * 1.0 / total_packages) | ||||
|  | ||||
|  | ||||
| def pretty_name(): | ||||
|     return _("Install packages.") | ||||
|  | ||||
|  | ||||
| def pretty_status_message(): | ||||
|     if custom_status_message is not None: | ||||
|         return custom_status_message | ||||
|     if not group_packages: | ||||
|         if (total_packages > 0): | ||||
|             # Outside the context of an operation | ||||
|             s = _("Processing packages (%(count)d / %(total)d)") | ||||
|         else: | ||||
|             s = _("Install packages.") | ||||
|  | ||||
|     elif mode_packages is INSTALL: | ||||
|         s = _n("Installing one package.", | ||||
|                "Installing %(num)d packages.", group_packages) | ||||
|     elif mode_packages is REMOVE: | ||||
|         s = _n("Removing one package.", | ||||
|                "Removing %(num)d packages.", group_packages) | ||||
|     else: | ||||
|         # No mode, generic description | ||||
|         s = _("Install packages.") | ||||
|  | ||||
|     return s % {"num": group_packages, | ||||
|                 "count": completed_packages, | ||||
|                 "total": total_packages} | ||||
|  | ||||
|  | ||||
|  | ||||
| class PackageManager(metaclass=abc.ABCMeta): | ||||
|     """ | ||||
|     Package manager base class. A subclass implements package management | ||||
|     for a specific backend, and must have a class property `backend` | ||||
|     with the string identifier for that backend. | ||||
|  | ||||
|     Subclasses are collected below to populate the list of possible | ||||
|     backends. | ||||
|     """ | ||||
|     backend = None | ||||
|  | ||||
|     @abc.abstractmethod | ||||
|     def install(self, pkgs, from_local=False): | ||||
|         """ | ||||
|         Install a list of packages (named) into the system. | ||||
|         Although this handles lists, in practice it is called | ||||
|         with one package at a time. | ||||
|  | ||||
|         @param pkgs: list[str] | ||||
|             list of package names | ||||
|         @param from_local: bool | ||||
|             if True, then these are local packages (on disk) and the | ||||
|             pkgs names are paths. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     @abc.abstractmethod | ||||
|     def remove(self, pkgs): | ||||
|         """ | ||||
|         Removes packages. | ||||
|  | ||||
|         @param pkgs: list[str] | ||||
|             list of package names | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def run(self, script): | ||||
|         if script != "": | ||||
|             host_env_process_output(script.split(" ")) | ||||
|  | ||||
|     def install_package(self, packagedata, from_local=False): | ||||
|         """ | ||||
|         Install a package from a single entry in the install list. | ||||
|         This can be either a single package name, or an object | ||||
|         with pre- and post-scripts. If @p packagedata is a dict, | ||||
|         it is assumed to follow the documented structure. | ||||
|  | ||||
|         @param packagedata: str|dict | ||||
|         @param from_local: bool | ||||
|             see install.from_local | ||||
|         """ | ||||
|         if isinstance(packagedata, str): | ||||
|             self.install([packagedata], from_local=from_local) | ||||
|         else: | ||||
|             self.run(packagedata["pre-script"]) | ||||
|             self.install([packagedata["package"]], from_local=from_local) | ||||
|             self.run(packagedata["post-script"]) | ||||
|  | ||||
|     def remove_package(self, packagedata): | ||||
|         """ | ||||
|         Remove a package from a single entry in the remove list. | ||||
|         This can be either a single package name, or an object | ||||
|         with pre- and post-scripts. If @p packagedata is a dict, | ||||
|         it is assumed to follow the documented structure. | ||||
|  | ||||
|         @param packagedata: str|dict | ||||
|         """ | ||||
|         if isinstance(packagedata, str): | ||||
|             self.remove([packagedata]) | ||||
|         else: | ||||
|             self.run(packagedata["pre-script"]) | ||||
|             self.remove([packagedata["package"]]) | ||||
|             self.run(packagedata["post-script"]) | ||||
|  | ||||
|     def operation_install(self, package_list, from_local=False): | ||||
|         """ | ||||
|         Installs the list of packages named in @p package_list . | ||||
|         These can be strings -- plain package names -- or | ||||
|         structures (with a pre- and post-install step). | ||||
|  | ||||
|         This operation is called for "critical" packages, | ||||
|         which are expected to succeed, or fail, all together. | ||||
|         However, if there are packages with pre- or post-scripts, | ||||
|         then packages are installed one-by-one instead. | ||||
|  | ||||
|         NOTE: package managers may reimplement this method | ||||
|         NOTE: exceptions are expected to leave this method, to indicate | ||||
|               failure of the installation. | ||||
|         """ | ||||
|         if all([isinstance(x, str) for x in package_list]): | ||||
|             self.install(package_list, from_local=from_local) | ||||
|         else: | ||||
|             for package in package_list: | ||||
|                 self.install_package(package, from_local=from_local) | ||||
|  | ||||
|     def operation_try_install(self, package_list): | ||||
|         """ | ||||
|         Installs the list of packages named in @p package_list . | ||||
|         These can be strings -- plain package names -- or | ||||
|         structures (with a pre- and post-install step). | ||||
|  | ||||
|         This operation is called for "non-critical" packages, | ||||
|         which can succeed or fail without affecting the overall installation. | ||||
|         Packages are installed one-by-one to support package managers | ||||
|         that do not have a "install as much as you can" mode. | ||||
|  | ||||
|         NOTE: package managers may reimplement this method | ||||
|         NOTE: no package-installation exceptions should be raised | ||||
|         """ | ||||
|         # we make a separate package manager call for each package so a | ||||
|         # single failing package won't stop all of them | ||||
|         for package in package_list: | ||||
|             try: | ||||
|                 self.install_package(package) | ||||
|             except subprocess.CalledProcessError: | ||||
|                 libcalamares.utils.warning("Could not install package %s" % package) | ||||
|  | ||||
|     def operation_remove(self, package_list): | ||||
|         """ | ||||
|         Removes the list of packages named in @p package_list . | ||||
|         These can be strings -- plain package names -- or | ||||
|         structures (with a pre- and post-install step). | ||||
|  | ||||
|         This operation is called for "critical" packages, which are | ||||
|         expected to succeed or fail all together. | ||||
|         However, if there are packages with pre- or post-scripts, | ||||
|         then packages are removed one-by-one instead. | ||||
|  | ||||
|         NOTE: package managers may reimplement this method | ||||
|         NOTE: exceptions should be raised to indicate failure | ||||
|         """ | ||||
|         if all([isinstance(x, str) for x in package_list]): | ||||
|             self.remove(package_list) | ||||
|         else: | ||||
|             for package in package_list: | ||||
|                 self.remove_package(package) | ||||
|  | ||||
|     def operation_try_remove(self, package_list): | ||||
|         """ | ||||
|         Same relation as try_install has to install, except it removes | ||||
|         packages instead. Packages are removed one-by-one. | ||||
|  | ||||
|         NOTE: package managers may reimplement this method | ||||
|         NOTE: no package-installation exceptions should be raised | ||||
|         """ | ||||
|         for package in package_list: | ||||
|             try: | ||||
|                 self.remove_package(package) | ||||
|             except subprocess.CalledProcessError: | ||||
|                 libcalamares.utils.warning("Could not remove package %s" % package) | ||||
|  | ||||
| ### PACKAGE MANAGER IMPLEMENTATIONS | ||||
| # | ||||
| # Keep these alphabetical (presumably both by class name and backend name), | ||||
| # even the Dummy implementation. | ||||
| # | ||||
|  | ||||
| class PMPacman(PackageManager): | ||||
|     backend = "pacman" | ||||
|  | ||||
|     def __init__(self): | ||||
|         import re | ||||
|         progress_match = re.compile("^\\((\\d+)/(\\d+)\\)") | ||||
|  | ||||
|         def line_cb(line): | ||||
|             if line.startswith(":: "): | ||||
|                 self.in_package_changes = "package" in line or "hooks" in line | ||||
|             else: | ||||
|                 if self.in_package_changes and line.endswith("...\n"): | ||||
|                     # Update the message, untranslated; do not change the | ||||
|                     # progress percentage, since there may be more "installing..." | ||||
|                     # lines in the output for the group, than packages listed | ||||
|                     # explicitly. We don't know how to calculate proper progress. | ||||
|                     global custom_status_message | ||||
|                     custom_status_message = "pacman: " + line.strip() | ||||
|                     libcalamares.job.setprogress(self.progress_fraction) | ||||
|             libcalamares.utils.debug(line) | ||||
|  | ||||
|         self.in_package_changes = False | ||||
|         self.line_cb = line_cb | ||||
|  | ||||
|         pacman = libcalamares.job.configuration.get("pacman", None) | ||||
|         if pacman is None: | ||||
|             pacman = dict() | ||||
|         if type(pacman) is not dict: | ||||
|             libcalamares.utils.warning("Job configuration *pacman* will be ignored.") | ||||
|             pacman = dict() | ||||
|         self.pacman_num_retries = pacman.get("num_retries", 0) | ||||
|         self.pacman_disable_timeout = pacman.get("disable_download_timeout", False) | ||||
|         self.pacman_needed_only = pacman.get("needed_only", False) | ||||
|         self.pacman_key = pacman.get("handle_keyrings", False) | ||||
|         self.pacman_pacconf = pacman.get("copy_pacconf", False) | ||||
|         self.pacman_requirements = pacman.get("requirements", []) | ||||
|         self.pacman_keyrings = pacman.get("keyrings", []) | ||||
|  | ||||
|     def reset_progress(self): | ||||
|         self.in_package_changes = False | ||||
|         # These are globals | ||||
|         self.progress_fraction = (completed_packages * 1.0 / total_packages) | ||||
|  | ||||
|     def run_pacman(self, command, callback=False): | ||||
|         """ | ||||
|         Call pacman in a loop until it is successful or the number of retries is exceeded | ||||
|         :param command: The pacman command to run | ||||
|         :param callback: An optional boolean that indicates if this pacman run should use the callback | ||||
|         :return: | ||||
|         """ | ||||
|  | ||||
|         pacman_count = 0 | ||||
|         while pacman_count <= self.pacman_num_retries: | ||||
|             pacman_count += 1 | ||||
|             try: | ||||
|                 if False: # callback: | ||||
|                     host_env_process_output(command, self.line_cb) | ||||
|                 else: | ||||
|                     host_env_process_output(command) | ||||
|  | ||||
|                 return | ||||
|             except subprocess.CalledProcessError: | ||||
|                 if pacman_count <= self.pacman_num_retries: | ||||
|                     pass | ||||
|                 else: | ||||
|                     raise | ||||
|  | ||||
|     def install(self, pkgs, from_local=False): | ||||
|  | ||||
|         install_root = libcalamares.globalstorage.value("rootMountPoint") | ||||
|  | ||||
|         cal_umask = os.umask(0) | ||||
|         for target in self.pacman_requirements: | ||||
|             dest = install_root + target["name"] | ||||
|             if not os.path.exists(dest): | ||||
|                 mod = int(target["mode"],8) | ||||
|                 os.mkdir(dest, mode=mod) | ||||
|                 libcalamares.utils.debug("Mode: {!s}".format(oct(mod))) | ||||
|                 libcalamares.utils.debug("Created: {!s}".format(dest)) | ||||
|  | ||||
|         path = join(install_root, "run") | ||||
|         os.chmod(path, 0o755) | ||||
|         os.umask(cal_umask) | ||||
|  | ||||
|         f = "etc/resolv.conf" | ||||
|         if os.path.exists(join("/",f)): | ||||
|             shutil.copy2(join("/",f), join(install_root, f)) | ||||
|  | ||||
|         command = ["pacman"] | ||||
|  | ||||
|         cachedir = join(install_root, "var/cache/pacman/pkg") | ||||
|         dbdir = join(install_root, "var/lib/pacman") | ||||
|         pacman_args = ["--root", install_root, "--dbpath", dbdir, "--cachedir", cachedir] | ||||
|         command.extend(pacman_args) | ||||
|  | ||||
|         # Don't ask for user intervention, take the default action | ||||
|         command.append("--noconfirm") | ||||
|  | ||||
|         # Don't report download progress for each file | ||||
|         command.append("--noprogressbar") | ||||
|  | ||||
|         if self.pacman_needed_only is True: | ||||
|             command.append("--needed") | ||||
|  | ||||
|         if self.pacman_disable_timeout is True: | ||||
|             command.append("--disable-download-timeout") | ||||
|  | ||||
|         if from_local: | ||||
|             command.append("-U") | ||||
|         else: | ||||
|             command.append("-Sy") | ||||
|  | ||||
|         command += pkgs | ||||
|  | ||||
|         libcalamares.utils.debug("Command: {!s}".format(command)) | ||||
|  | ||||
|         self.reset_progress() | ||||
|         self.run_pacman(command, True) | ||||
|  | ||||
|         if self.pacman_key: | ||||
|             self.init_keyring() | ||||
|             self.populate_keyring() | ||||
|  | ||||
|         if self.pacman_pacconf: | ||||
|             f = "etc/pacman.conf" | ||||
|             if os.path.exists(join("/",f)): | ||||
|                 shutil.copy2(join("/",f), join(install_root, f)) | ||||
|  | ||||
|     def remove(self, pkgs): | ||||
|         self.reset_progress() | ||||
|         self.run_pacman(["pacman", "-Rs", "--noconfirm"] + pkgs, True) | ||||
|  | ||||
|     def init_keyring(self): | ||||
|         target_env_process_output(["pacman-key", "--init"]) | ||||
|  | ||||
|     def populate_keyring(self): | ||||
|         target_env_process_output(["pacman-key", "--populate"] + self.pacman_keyrings) | ||||
|  | ||||
| # Collect all the subclasses of PackageManager defined above, | ||||
| # and index them based on the backend property of each class. | ||||
| backend_managers = [ | ||||
|     (c.backend, c) | ||||
|     for c in globals().values() | ||||
|     if type(c) is abc.ABCMeta and issubclass(c, PackageManager) and c.backend] | ||||
|  | ||||
|  | ||||
| def subst_locale(plist): | ||||
|     """ | ||||
|     Returns a locale-aware list of packages, based on @p plist. | ||||
|     Package names that contain LOCALE are localized with the | ||||
|     BCP47 name of the chosen system locale; if the system | ||||
|     locale is 'en' (e.g. English, US) then these localized | ||||
|     packages are dropped from the list. | ||||
|  | ||||
|     @param plist: list[str|dict] | ||||
|         Candidate packages to install. | ||||
|     @return: list[str|dict] | ||||
|     """ | ||||
|     locale = libcalamares.globalstorage.value("locale") | ||||
|     if not locale: | ||||
|         # It is possible to skip the locale-setting entirely. | ||||
|         # Then pretend it is "en", so that {LOCALE}-decorated | ||||
|         # package names are removed from the list. | ||||
|         locale = "en" | ||||
|  | ||||
|     ret = [] | ||||
|     for packagedata in plist: | ||||
|         if isinstance(packagedata, str): | ||||
|             packagename = packagedata | ||||
|         else: | ||||
|             packagename = packagedata["package"] | ||||
|  | ||||
|         # Update packagename: substitute LOCALE, and drop packages | ||||
|         # if locale is en and LOCALE is in the package name. | ||||
|         if locale != "en": | ||||
|             packagename = Template(packagename).safe_substitute(LOCALE=locale) | ||||
|         elif 'LOCALE' in packagename: | ||||
|             packagename = None | ||||
|  | ||||
|         if packagename is not None: | ||||
|             # Put it back in packagedata | ||||
|             if isinstance(packagedata, str): | ||||
|                 packagedata = packagename | ||||
|             else: | ||||
|                 packagedata["package"] = packagename | ||||
|  | ||||
|             ret.append(packagedata) | ||||
|  | ||||
|     return ret | ||||
|  | ||||
|  | ||||
| def run_operations(pkgman, entry): | ||||
|     """ | ||||
|     Call package manager with suitable parameters for the given | ||||
|     package actions. | ||||
|  | ||||
|     :param pkgman: PackageManager | ||||
|         This is the manager that does the actual work. | ||||
|     :param entry: dict | ||||
|         Keys are the actions -- e.g. "install" -- to take, and the values | ||||
|         are the (list of) packages to apply the action to. The actions are | ||||
|         not iterated in a specific order, so it is recommended to use only | ||||
|         one action per dictionary. The list of packages may be package | ||||
|         names (strings) or package information dictionaries with pre- | ||||
|         and post-scripts. | ||||
|     """ | ||||
|     global group_packages, completed_packages, mode_packages | ||||
|  | ||||
|     for key in entry.keys(): | ||||
|         package_list = subst_locale(entry[key]) | ||||
|         group_packages = len(package_list) | ||||
|         if key == "install": | ||||
|             _change_mode(INSTALL) | ||||
|             pkgman.operation_install(package_list) | ||||
|         elif key == "try_install": | ||||
|             _change_mode(INSTALL) | ||||
|             pkgman.operation_try_install(package_list) | ||||
|         elif key == "remove": | ||||
|             _change_mode(REMOVE) | ||||
|             pkgman.operation_remove(package_list) | ||||
|         elif key == "try_remove": | ||||
|             _change_mode(REMOVE) | ||||
|             pkgman.operation_try_remove(package_list) | ||||
|         elif key == "localInstall": | ||||
|             _change_mode(INSTALL) | ||||
|             pkgman.operation_install(package_list, from_local=True) | ||||
|         elif key == "source": | ||||
|             libcalamares.utils.debug("Package-list from {!s}".format(entry[key])) | ||||
|         else: | ||||
|             libcalamares.utils.warning("Unknown package-operation key {!s}".format(key)) | ||||
|         completed_packages += len(package_list) | ||||
|         libcalamares.job.setprogress(completed_packages * 1.0 / total_packages) | ||||
|         libcalamares.utils.debug("Pretty name: {!s}, setting progress..".format(pretty_name())) | ||||
|  | ||||
|     group_packages = 0 | ||||
|     _change_mode(None) | ||||
|  | ||||
|  | ||||
| def run(): | ||||
|     """ | ||||
|     Calls routine with detected package manager to install locale packages | ||||
|     or remove drivers not needed on the installed system. | ||||
|  | ||||
|     :return: | ||||
|     """ | ||||
|     global mode_packages, total_packages, completed_packages, group_packages | ||||
|  | ||||
|     backend = libcalamares.job.configuration.get("backend") | ||||
|  | ||||
|     for identifier, impl in backend_managers: | ||||
|         if identifier == backend: | ||||
|             pkgman = impl() | ||||
|             break | ||||
|         else: | ||||
|             return "Bad backend", "backend=\"{}\"".format(backend) | ||||
|  | ||||
|     if not libcalamares.globalstorage.value("hasInternet"): | ||||
|         libcalamares.utils.warning( "Package installation has been skipped: no internet" ) | ||||
|         return None | ||||
|  | ||||
|     operations = libcalamares.job.configuration.get("operations", []) | ||||
|     # if libcalamares.globalstorage.contains("packageOperations"): | ||||
|     #     operations += libcalamares.globalstorage.value("packageOperations") | ||||
|  | ||||
|     if libcalamares.job.configuration.get("base_init"): | ||||
|         base_init = libcalamares.job.configuration.get("base_init", None) | ||||
|  | ||||
|     if libcalamares.globalstorage.contains("netinstallAdd"): | ||||
|         data = libcalamares.globalstorage.value("netinstallAdd") | ||||
|         init_provider = data[0]["name"] | ||||
|         libcalamares.utils.debug("Init provider: {!s}".format(init_provider)) | ||||
|  | ||||
|         if base_init is not None: | ||||
|             init_pkg = "-".join([base_init, init_provider]) | ||||
|             libcalamares.utils.debug("Package added: {!s}".format(init_pkg)) | ||||
|             operations[0]["install"].append(init_pkg) | ||||
|  | ||||
|     if init_provider is not None: | ||||
|         libcalamares.globalstorage.insert("initProvider", init_provider) | ||||
|  | ||||
|     libcalamares.globalstorage.insert("packageOperationsBasestrap", operations) | ||||
|  | ||||
|     mode_packages = None | ||||
|     total_packages = 0 | ||||
|     completed_packages = 0 | ||||
|     for op in operations: | ||||
|         for packagelist in op.values(): | ||||
|             total_packages += len(subst_locale(packagelist)) | ||||
|  | ||||
|     if not total_packages: | ||||
|         # Avoids potential divide-by-zero in progress reporting | ||||
|         return None | ||||
|  | ||||
|     for entry in operations: | ||||
|         group_packages = 0 | ||||
|         libcalamares.utils.debug(pretty_name()) | ||||
|         try: | ||||
|             run_operations(pkgman, entry) | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             libcalamares.utils.warning(str(e)) | ||||
|             libcalamares.utils.debug("stdout:" + str(e.stdout)) | ||||
|             libcalamares.utils.debug("stderr:" + str(e.stderr)) | ||||
|             return (_("Package Manager error"), | ||||
|                     _("The package manager could not make changes to the installed system. The command <pre>{!s}</pre> returned error code {!s}.") | ||||
|                     .format(e.cmd, e.returncode)) | ||||
|  | ||||
|     mode_packages = None | ||||
|  | ||||
|     libcalamares.job.setprogress(1.0) | ||||
|  | ||||
|     return None | ||||
| @@ -1,7 +0,0 @@ | ||||
| # SPDX-FileCopyrightText: no | ||||
| # SPDX-License-Identifier: CC0-1.0 | ||||
| --- | ||||
| type:       "job" | ||||
| name:       "basestrap" | ||||
| interface:  "python" | ||||
| script:     "main.py" | ||||
							
								
								
									
										17
									
								
								modules/freebsddisk/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | ||||
| #   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() | ||||
| endif() | ||||
|  | ||||
| calamares_add_plugin( freebsddisk | ||||
|     TYPE viewmodule | ||||
|     EXPORT_MACRO PLUGINDLLEXPORT_PRO | ||||
|     SOURCES | ||||
|         FreeBSDDiskViewStep.cpp | ||||
|     RESOURCES | ||||
|         freebsddisk.qrc | ||||
|     SHARED_LIB | ||||
| ) | ||||
							
								
								
									
										42
									
								
								modules/freebsddisk/FreeBSDDiskViewStep.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | ||||
| /* === 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: 2020 Adriaan de Groot <groot@kde.org> | ||||
|  *   SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  *   License-Filename: LICENSES/GPL-3.0 | ||||
|  */ | ||||
|  | ||||
| #include "FreeBSDDiskViewStep.h" | ||||
|  | ||||
| FreeBSDDiskViewStep::FreeBSDDiskViewStep( QObject* parent ) | ||||
|     : Calamares::QmlViewStep( parent ) | ||||
| { | ||||
| } | ||||
|  | ||||
| FreeBSDDiskViewStep::~FreeBSDDiskViewStep() {} | ||||
|  | ||||
| QString | ||||
| FreeBSDDiskViewStep::prettyName() const | ||||
| { | ||||
|     return tr( "Disk Setup" ); | ||||
| } | ||||
|  | ||||
| void | ||||
| FreeBSDDiskViewStep::setConfigurationMap( const QVariantMap& configurationMap ) | ||||
| { | ||||
|     Calamares::QmlViewStep::setConfigurationMap( configurationMap );  // call parent implementation last | ||||
| } | ||||
|  | ||||
| CALAMARES_PLUGIN_FACTORY_DEFINITION( FreeBSDDiskViewStepFactory, registerPlugin< FreeBSDDiskViewStep >(); ) | ||||
							
								
								
									
										44
									
								
								modules/freebsddisk/FreeBSDDiskViewStep.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,44 @@ | ||||
| /* === 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: 2020 Adriaan de Groot <groot@kde.org> | ||||
|  *   SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  *   License-Filename: LICENSES/GPL-3.0 | ||||
|  */ | ||||
|  | ||||
|  | ||||
| #ifndef FREEBSDDISKVIEWSTEP_H | ||||
| #define FREEBSDDISKVIEWSTEP_H | ||||
|  | ||||
| #include "DllMacro.h" | ||||
| #include "utils/PluginFactory.h" | ||||
| #include "viewpages/QmlViewStep.h" | ||||
|  | ||||
| class PLUGINDLLEXPORT FreeBSDDiskViewStep : public Calamares::QmlViewStep | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     FreeBSDDiskViewStep( QObject* parent = nullptr ); | ||||
|     virtual ~FreeBSDDiskViewStep() override; | ||||
|  | ||||
|     QString prettyName() const override; | ||||
|  | ||||
|     void setConfigurationMap( const QVariantMap& configurationMap ) override; | ||||
| }; | ||||
|  | ||||
| CALAMARES_PLUGIN_FACTORY_DECLARATION( FreeBSDDiskViewStepFactory ) | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										10
									
								
								modules/freebsddisk/freebsddisk.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| # 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. | ||||
| # | ||||
| --- | ||||
| qmlSearch: both | ||||
|  | ||||
							
								
								
									
										35
									
								
								modules/freebsddisk/freebsddisk.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,35 @@ | ||||
| /* === 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: 2020 Adriaan de Groot <groot@kde.org> | ||||
|  *   SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  *   License-Filename: LICENSES/GPL-3.0 | ||||
|  */ | ||||
|  | ||||
| import io.calamares.ui 1.0 | ||||
|  | ||||
| import QtQuick 2.7 | ||||
| import QtQuick.Controls 2.2 | ||||
| import QtQuick.Window 2.2 | ||||
| import QtQuick.Layouts 1.3 | ||||
| import QtQuick.Controls.Material 2.1 | ||||
|  | ||||
| Item { | ||||
|     Text { | ||||
|         anchors.top: parent.top | ||||
|         anchors.topMargin: 10 | ||||
|         text: "Select a disk on which to install FreeBSD." | ||||
|     } | ||||
| } | ||||
							
								
								
									
										5
									
								
								modules/freebsddisk/freebsddisk.qrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| <!DOCTYPE RCC><RCC version="1.0"> | ||||
| <qresource> | ||||
|   <file alias="freebsddisk.qml">freebsddisk.qml</file> | ||||
| </qresource> | ||||
| </RCC> | ||||
							
								
								
									
										15
									
								
								modules/mobile/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| # SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| calamares_add_plugin( mobile | ||||
|     TYPE viewmodule | ||||
|     EXPORT_MACRO PLUGINDLLEXPORT_PRO | ||||
|     SOURCES | ||||
|         Config.cpp | ||||
|         MobileQmlViewStep.cpp | ||||
|         PartitionJob.cpp | ||||
|         UsersJob.cpp | ||||
|     RESOURCES | ||||
|         mobile.qrc | ||||
|     SHARED_LIB | ||||
| ) | ||||
							
								
								
									
										221
									
								
								modules/mobile/Config.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,221 @@ | ||||
| /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later */ | ||||
| #include "Config.h" | ||||
| #include "PartitionJob.h" | ||||
| #include "UsersJob.h" | ||||
|  | ||||
| #include "ViewManager.h" | ||||
| #include "utils/Logger.h" | ||||
| #include "utils/Variant.h" | ||||
|  | ||||
| #include <QVariant> | ||||
|  | ||||
| Config::Config( QObject* parent ) | ||||
|     : QObject( parent ) | ||||
| { | ||||
| } | ||||
|  | ||||
| void | ||||
| 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_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" ); | ||||
| } | ||||
|  | ||||
| Calamares::JobList | ||||
| Config::createJobs() | ||||
| { | ||||
|     QList< Calamares::job_ptr > list; | ||||
|     QString cmdSshd = m_isSshEnabled ? m_cmdSshdEnable : m_cmdSshdDisable; | ||||
|  | ||||
|     /* 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, | ||||
|                                       m_username, | ||||
|                                       m_userPassword, | ||||
|                                       m_sshdUsername, | ||||
|                                       m_sshdPassword ); | ||||
|     list.append( Calamares::job_ptr( j ) ); | ||||
|  | ||||
|     return list; | ||||
| } | ||||
|  | ||||
| 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 | ||||
|      * them right after each other. But we need the "unpackfs" module to run | ||||
|      * inbetween, that's why as workaround, the partition job is started here. | ||||
|      * To solve this properly, we would need to place the partition job in an | ||||
|      * own module and pass everything via globalstorage. But then we might as | ||||
|      * well refactor everything so we can unify the mobile's partition job with | ||||
|      * the proper partition job from Calamares. */ | ||||
|     Calamares::Job* j = new PartitionJob( m_cmdInternalStoragePrepare, | ||||
|                                           m_cmdLuksFormat, | ||||
|                                           m_cmdLuksOpen, | ||||
|                                           cmdMkfsRoot, | ||||
|                                           m_cmdMount, | ||||
|                                           m_targetDeviceRoot, | ||||
|                                           m_targetDeviceRootInternal, | ||||
|                                           m_installFromExternalToInternal, | ||||
|                                           m_isFdeEnabled, | ||||
|                                           m_fdePassword ); | ||||
|     Calamares::JobResult res = j->exec(); | ||||
|  | ||||
|     if ( res ) | ||||
|     { | ||||
|         v->next(); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         v->onInstallationFailed( res.message(), res.details() ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void | ||||
| Config::setUsername( const QString& username ) | ||||
| { | ||||
|     m_username = username; | ||||
|     emit usernameChanged( m_username ); | ||||
| } | ||||
|  | ||||
| void | ||||
| Config::setUserPassword( const QString& userPassword ) | ||||
| { | ||||
|     m_userPassword = userPassword; | ||||
|     emit userPasswordChanged( m_userPassword ); | ||||
| } | ||||
|  | ||||
| void | ||||
| Config::setSshdUsername( const QString& sshdUsername ) | ||||
| { | ||||
|     m_sshdUsername = sshdUsername; | ||||
|     emit sshdUsernameChanged( m_sshdUsername ); | ||||
| } | ||||
|  | ||||
| void | ||||
| Config::setSshdPassword( const QString& sshdPassword ) | ||||
| { | ||||
|     m_sshdPassword = sshdPassword; | ||||
|     emit sshdPasswordChanged( m_sshdPassword ); | ||||
| } | ||||
|  | ||||
| void | ||||
| Config::setIsSshEnabled( const bool isSshEnabled ) | ||||
| { | ||||
|     m_isSshEnabled = isSshEnabled; | ||||
| } | ||||
|  | ||||
| void | ||||
| Config::setFdePassword( const QString& fdePassword ) | ||||
| { | ||||
|     m_fdePassword = fdePassword; | ||||
|     emit fdePasswordChanged( m_fdePassword ); | ||||
| } | ||||
|  | ||||
| void | ||||
| Config::setIsFdeEnabled( const bool isFdeEnabled ) | ||||
| { | ||||
|     m_isFdeEnabled = isFdeEnabled; | ||||
| } | ||||
|  | ||||
| void | ||||
| 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 ); | ||||
| } | ||||
							
								
								
									
										207
									
								
								modules/mobile/Config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,207 @@ | ||||
| /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later */ | ||||
| #pragma once | ||||
|  | ||||
| #include "Job.h" | ||||
|  | ||||
| #include <QObject> | ||||
| #include <memory> | ||||
|  | ||||
| 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 ) | ||||
|     Q_PROPERTY( QString device READ device CONSTANT FINAL ) | ||||
|     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 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 ) | ||||
|     Q_PROPERTY( QString sshdUsername READ sshdUsername WRITE setSshdUsername NOTIFY sshdUsernameChanged ) | ||||
|     Q_PROPERTY( QString sshdPassword READ sshdPassword WRITE setSshdPassword NOTIFY sshdPasswordChanged ) | ||||
|     Q_PROPERTY( bool isSshEnabled READ isSshEnabled WRITE setIsSshEnabled ) | ||||
|  | ||||
|     /* full disk encryption */ | ||||
|     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 cmdMount READ cmdMount CONSTANT FINAL ) | ||||
|     Q_PROPERTY( QString targetDeviceRoot READ targetDeviceRoot CONSTANT FINAL ) | ||||
|     Q_PROPERTY( QString targetDeviceRootInternal READ targetDeviceRootInternal CONSTANT FINAL ) | ||||
|     Q_PROPERTY( | ||||
|         bool installFromExternalToInternal READ installFromExternalToInternal WRITE setInstallFromExternalToInternal ) | ||||
|  | ||||
|     /* users job */ | ||||
|     Q_PROPERTY( QString cmdSshdEnable READ cmdSshdEnable CONSTANT FINAL ) | ||||
|     Q_PROPERTY( QString cmdSshdDisable READ cmdSshdDisable CONSTANT FINAL ) | ||||
|  | ||||
| public: | ||||
|     Config( QObject* parent = nullptr ); | ||||
|     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; } | ||||
|     QString device() const { return m_device; } | ||||
|     QString userInterface() const { return m_userInterface; } | ||||
|     QString version() const { return m_version; } | ||||
|  | ||||
|     /* 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 + credentials */ | ||||
|     bool featureSshd() { return m_featureSshd; } | ||||
|     QString sshdUsername() const { return m_sshdUsername; } | ||||
|     QString sshdPassword() const { return m_sshdPassword; } | ||||
|     bool isSshEnabled() { return m_isSshEnabled; } | ||||
|     void setSshdUsername( const QString& sshdUsername ); | ||||
|     void setSshdPassword( const QString& sshdPassword ); | ||||
|     void setIsSshEnabled( bool isSshEnabled ); | ||||
|  | ||||
|     /* full disk encryption */ | ||||
|     QString fdePassword() const { return m_fdePassword; } | ||||
|     bool isFdeEnabled() { return m_isFdeEnabled; } | ||||
|     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 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; } | ||||
|     bool installFromExternalToInternal() { return m_installFromExternalToInternal; } | ||||
|     void setInstallFromExternalToInternal( const bool val ); | ||||
|  | ||||
|     /* 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; | ||||
|     QString m_device; | ||||
|     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 + credentials */ | ||||
|     bool m_featureSshd = false; | ||||
|     QString m_sshdUsername; | ||||
|     QString m_sshdPassword; | ||||
|     bool m_isSshEnabled = false; | ||||
|  | ||||
|     /* full disk encryption */ | ||||
|     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_cmdMkfsRootBtrfs; | ||||
|     QString m_cmdMkfsRootExt4; | ||||
|     QString m_cmdMkfsRootF2fs; | ||||
|     QString m_cmdMount; | ||||
|     QString m_targetDeviceRoot; | ||||
|     QString m_targetDeviceRootInternal; | ||||
|     bool m_installFromExternalToInternal = false; | ||||
|  | ||||
|     /* users job */ | ||||
|     QString m_cmdPasswd; | ||||
|     QString m_cmdUsermod; | ||||
|     QString m_cmdSshdEnable; | ||||
|     QString m_cmdSshdDisable; | ||||
|     QString m_cmdSshdUseradd; | ||||
|  | ||||
| signals: | ||||
|     /* booleans we don't read from QML (like isSshEnabled) don't need a signal */ | ||||
|  | ||||
|     /* default user */ | ||||
|     void userPasswordChanged( QString userPassword ); | ||||
|     void usernameChanged( QString username ); | ||||
|  | ||||
|     /* ssh server + credentials */ | ||||
|     void sshdUsernameChanged( QString sshdUsername ); | ||||
|     void sshdPasswordChanged( QString sshdPassword ); | ||||
|  | ||||
|     /* full disk encryption */ | ||||
|     void fdePasswordChanged( QString fdePassword ); | ||||
|  | ||||
|     void fsTypeChanged( QString fsType ); | ||||
|     void fsIndexChanged( int fsIndex ); | ||||
| }; | ||||
							
								
								
									
										73
									
								
								modules/mobile/MobileQmlViewStep.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,73 @@ | ||||
| /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later */ | ||||
| #include "MobileQmlViewStep.h" | ||||
|  | ||||
| #include "Branding.h" | ||||
| #include "GlobalStorage.h" | ||||
| #include "locale/TranslationsModel.h" | ||||
| #include "modulesystem/ModuleManager.h" | ||||
| #include "utils/Dirs.h" | ||||
| #include "utils/Logger.h" | ||||
| #include "utils/Variant.h" | ||||
|  | ||||
| #include <QProcess> | ||||
|  | ||||
| CALAMARES_PLUGIN_FACTORY_DEFINITION( MobileQmlViewStepFactory, registerPlugin< MobileQmlViewStep >(); ) | ||||
|  | ||||
| void | ||||
| MobileQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) | ||||
| { | ||||
|     m_config->setConfigurationMap( configurationMap ); | ||||
|     Calamares::QmlViewStep::setConfigurationMap( configurationMap ); | ||||
| } | ||||
|  | ||||
| MobileQmlViewStep::MobileQmlViewStep( QObject* parent ) | ||||
|     : Calamares::QmlViewStep( parent ) | ||||
|     , m_config( new Config( this ) ) | ||||
| { | ||||
| } | ||||
|  | ||||
| void | ||||
| MobileQmlViewStep::onLeave() | ||||
| { | ||||
|     return; | ||||
| } | ||||
|  | ||||
| bool | ||||
| MobileQmlViewStep::isNextEnabled() const | ||||
| { | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| bool | ||||
| MobileQmlViewStep::isBackEnabled() const | ||||
| { | ||||
|     return false; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool | ||||
| MobileQmlViewStep::isAtBeginning() const | ||||
| { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool | ||||
| MobileQmlViewStep::isAtEnd() const | ||||
| { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| Calamares::JobList | ||||
| MobileQmlViewStep::jobs() const | ||||
| { | ||||
|     return m_config->createJobs(); | ||||
| } | ||||
|  | ||||
| QObject* | ||||
| MobileQmlViewStep::getConfig() | ||||
| { | ||||
|     return m_config; | ||||
| } | ||||
							
								
								
									
										39
									
								
								modules/mobile/MobileQmlViewStep.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | ||||
| /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later */ | ||||
| #ifndef PARTITION_QMLVIEWSTEP_H | ||||
| #define PARTITION_QMLVIEWSTEP_H | ||||
| #include "Config.h" | ||||
|  | ||||
| #include "utils/PluginFactory.h" | ||||
| #include "viewpages/QmlViewStep.h" | ||||
|  | ||||
| #include <DllMacro.h> | ||||
|  | ||||
| #include <QObject> | ||||
| #include <QVariantMap> | ||||
|  | ||||
| class PLUGINDLLEXPORT MobileQmlViewStep : public Calamares::QmlViewStep | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit MobileQmlViewStep( QObject* parent = nullptr ); | ||||
|  | ||||
|     bool isNextEnabled() const override; | ||||
|     bool isBackEnabled() const override; | ||||
|     bool isAtBeginning() const override; | ||||
|     bool isAtEnd() const override; | ||||
|  | ||||
|     Calamares::JobList jobs() const override; | ||||
|  | ||||
|     void setConfigurationMap( const QVariantMap& configurationMap ) override; | ||||
|     void onLeave() override; | ||||
|     QObject* getConfig() override; | ||||
|  | ||||
| private: | ||||
|     Config* m_config; | ||||
| }; | ||||
|  | ||||
| CALAMARES_PLUGIN_FACTORY_DECLARATION( MobileQmlViewStepFactory ) | ||||
|  | ||||
| #endif  // PARTITION_QMLVIEWSTEP_H | ||||
							
								
								
									
										136
									
								
								modules/mobile/PartitionJob.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,136 @@ | ||||
| /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later */ | ||||
| #include "PartitionJob.h" | ||||
|  | ||||
| #include "GlobalStorage.h" | ||||
| #include "JobQueue.h" | ||||
| #include "Settings.h" | ||||
| #include "utils/CalamaresUtilsSystem.h" | ||||
| #include "utils/Logger.h" | ||||
|  | ||||
| #include <QDir> | ||||
| #include <QFileInfo> | ||||
|  | ||||
|  | ||||
| PartitionJob::PartitionJob( const QString& cmdInternalStoragePrepare, | ||||
|                             const QString& cmdLuksFormat, | ||||
|                             const QString& cmdLuksOpen, | ||||
|                             const QString& cmdMkfsRoot, | ||||
|                             const QString& cmdMount, | ||||
|                             const QString& targetDeviceRoot, | ||||
|                             const QString& targetDeviceRootInternal, | ||||
|                             bool installFromExternalToInternal, | ||||
|                             bool isFdeEnabled, | ||||
|                             const QString& password ) | ||||
|     : Calamares::Job() | ||||
|     , m_cmdInternalStoragePrepare( cmdInternalStoragePrepare ) | ||||
|     , m_cmdLuksFormat( cmdLuksFormat ) | ||||
|     , m_cmdLuksOpen( cmdLuksOpen ) | ||||
|     , m_cmdMkfsRoot( cmdMkfsRoot ) | ||||
|     , m_cmdMount( cmdMount ) | ||||
|     , m_targetDeviceRoot( targetDeviceRoot ) | ||||
|     , m_targetDeviceRootInternal( targetDeviceRootInternal ) | ||||
|     , m_installFromExternalToInternal( installFromExternalToInternal ) | ||||
|     , m_isFdeEnabled( isFdeEnabled ) | ||||
|     , m_password( password ) | ||||
| { | ||||
| } | ||||
|  | ||||
|  | ||||
| QString | ||||
| PartitionJob::prettyName() const | ||||
| { | ||||
|     return "Creating and formatting installation partition"; | ||||
| } | ||||
|  | ||||
| /* Fill the "global storage", so the following jobs (like unsquashfs) work. | ||||
|    The code is similar to modules/partition/jobs/FillGlobalStorageJob.cpp in | ||||
|    Calamares. */ | ||||
| void | ||||
| FillGlobalStorage( const QString device, const QString pathMount ) | ||||
| { | ||||
|     using namespace Calamares; | ||||
|  | ||||
|     GlobalStorage* gs = JobQueue::instance()->globalStorage(); | ||||
|     QVariantList partitions; | ||||
|     QVariantMap partition; | ||||
|  | ||||
|     /* See mapForPartition() in FillGlobalStorageJob.cpp */ | ||||
|     partition[ "device" ] = device; | ||||
|     partition[ "mountPoint" ] = "/"; | ||||
|     partition[ "claimed" ] = true; | ||||
|  | ||||
|     /* Ignored by calamares modules used in combination with the "mobile" | ||||
|      * module, so we can get away with leaving them empty for now. */ | ||||
|     partition[ "uuid" ] = ""; | ||||
|     partition[ "fsName" ] = ""; | ||||
|     partition[ "fs" ] = ""; | ||||
|  | ||||
|     partitions << partition; | ||||
|     gs->insert( "partitions", partitions ); | ||||
|     gs->insert( "rootMountPoint", pathMount ); | ||||
| } | ||||
|  | ||||
| Calamares::JobResult | ||||
| PartitionJob::exec() | ||||
| { | ||||
|     using namespace Calamares; | ||||
|     using namespace CalamaresUtils; | ||||
|     using namespace std; | ||||
|  | ||||
|     const QString pathMount = "/mnt/install"; | ||||
|     const QString cryptName = "calamares_crypt"; | ||||
|     QString cryptDev = "/dev/mapper/" + cryptName; | ||||
|     QString passwordStdin = m_password + "\n"; | ||||
|     QString dev = m_targetDeviceRoot; | ||||
|     QList< QPair< QStringList, QString > > commands = {}; | ||||
|  | ||||
|     if ( m_installFromExternalToInternal ) | ||||
|     { | ||||
|         dev = m_targetDeviceRootInternal; | ||||
|  | ||||
|         commands.append( { | ||||
|             { { "sh", "-c", m_cmdInternalStoragePrepare }, QString() }, | ||||
|         } ); | ||||
|     } | ||||
|  | ||||
|     commands.append( { { { "mkdir", "-p", pathMount }, QString() } } ); | ||||
|  | ||||
|     if ( m_isFdeEnabled ) | ||||
|     { | ||||
|         commands.append( { | ||||
|             { { "sh", "-c", m_cmdLuksFormat + " " + dev }, passwordStdin }, | ||||
|             { { "sh", "-c", m_cmdLuksOpen + " " + dev + " " + cryptName }, passwordStdin }, | ||||
|             { { "sh", "-c", m_cmdMkfsRoot + " " + cryptDev }, QString() }, | ||||
|             { { "sh", "-c", m_cmdMount + " " + cryptDev + " " + pathMount }, QString() }, | ||||
|         } ); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         commands.append( { { { "sh", "-c", m_cmdMkfsRoot + " " + dev }, QString() }, | ||||
|                            { { "sh", "-c", m_cmdMount + " " + dev + " " + pathMount }, QString() } } ); | ||||
|     } | ||||
|  | ||||
|     foreach ( auto command, commands ) | ||||
|     { | ||||
|         const QStringList args = command.first; | ||||
|         const QString stdInput = command.second; | ||||
|         const QString pathRoot = "/"; | ||||
|  | ||||
|         ProcessResult res | ||||
|             = System::runCommand( System::RunLocation::RunInHost, args, pathRoot, stdInput, chrono::seconds( 600 ) ); | ||||
|         if ( res.getExitCode() ) | ||||
|         { | ||||
|             return JobResult::error( "Command failed:<br><br>" | ||||
|                                      "'" | ||||
|                                      + args.join( " " ) | ||||
|                                      + "'<br><br>" | ||||
|                                        " with output:<br><br>" | ||||
|                                        "'" | ||||
|                                      + res.getOutput() + "'" ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     FillGlobalStorage( m_isFdeEnabled ? cryptDev : dev, pathMount ); | ||||
|     return JobResult::ok(); | ||||
| } | ||||
							
								
								
									
										38
									
								
								modules/mobile/PartitionJob.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | ||||
| /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later */ | ||||
| #pragma once | ||||
| #include "Job.h" | ||||
|  | ||||
|  | ||||
| class PartitionJob : public Calamares::Job | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     PartitionJob( const QString& cmdInternalStoragePrepare, | ||||
|                   const QString& cmdLuksFormat, | ||||
|                   const QString& cmdLuksOpen, | ||||
|                   const QString& cmdMkfsRoot, | ||||
|                   const QString& cmdMount, | ||||
|                   const QString& targetDeviceRoot, | ||||
|                   const QString& targetDeviceRootInternal, | ||||
|                   bool installFromExternalToInternal, | ||||
|                   bool isFdeEnabled, | ||||
|                   const QString& password ); | ||||
|  | ||||
|     QString prettyName() const override; | ||||
|     Calamares::JobResult exec() override; | ||||
|  | ||||
|     Calamares::JobList createJobs(); | ||||
|  | ||||
| private: | ||||
|     QString m_cmdInternalStoragePrepare; | ||||
|     QString m_cmdLuksFormat; | ||||
|     QString m_cmdLuksOpen; | ||||
|     QString m_cmdMkfsRoot; | ||||
|     QString m_cmdMount; | ||||
|     QString m_targetDeviceRoot; | ||||
|     QString m_targetDeviceRootInternal; | ||||
|     bool m_installFromExternalToInternal; | ||||
|     bool m_isFdeEnabled; | ||||
|     QString m_password; | ||||
| }; | ||||
							
								
								
									
										92
									
								
								modules/mobile/UsersJob.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,92 @@ | ||||
| /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later */ | ||||
| #include "UsersJob.h" | ||||
|  | ||||
| #include "GlobalStorage.h" | ||||
| #include "JobQueue.h" | ||||
| #include "Settings.h" | ||||
| #include "utils/CalamaresUtilsSystem.h" | ||||
| #include "utils/Logger.h" | ||||
|  | ||||
| #include <QDir> | ||||
| #include <QFileInfo> | ||||
|  | ||||
|  | ||||
| UsersJob::UsersJob( bool featureSshd, | ||||
|                     const QString& cmdPasswd, | ||||
|                     const QString& cmdUsermod, | ||||
|                     const QString& cmdSshd, | ||||
|                     const QString& cmdSshdUseradd, | ||||
|                     bool isSshEnabled, | ||||
|                     const QString& username, | ||||
|                     const QString& password, | ||||
|                     const QString& sshdUsername, | ||||
|                     const QString& sshdPassword ) | ||||
|     : Calamares::Job() | ||||
|     , m_featureSshd( featureSshd ) | ||||
|     , m_cmdPasswd( cmdPasswd ) | ||||
|     , m_cmdUsermod( cmdUsermod ) | ||||
|     , m_cmdSshd( cmdSshd ) | ||||
|     , m_cmdSshdUseradd( cmdSshdUseradd ) | ||||
|     , m_isSshEnabled( isSshEnabled ) | ||||
|     , m_username( username ) | ||||
|     , m_password( password ) | ||||
|     , m_sshdUsername( sshdUsername ) | ||||
|     , m_sshdPassword( sshdPassword ) | ||||
| { | ||||
| } | ||||
|  | ||||
|  | ||||
| QString | ||||
| UsersJob::prettyName() const | ||||
| { | ||||
|     return "Configuring users"; | ||||
| } | ||||
|  | ||||
| Calamares::JobResult | ||||
| UsersJob::exec() | ||||
| { | ||||
|     using namespace Calamares; | ||||
|     using namespace CalamaresUtils; | ||||
|     using namespace std; | ||||
|  | ||||
|     QList< QPair< QStringList, QString > > commands = { | ||||
|         { { "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() } ); | ||||
|  | ||||
|         if ( m_isSshEnabled ) | ||||
|         { | ||||
|             commands.append( { { "sh", "-c", m_cmdSshdUseradd + " " + m_sshdUsername }, QString() } ); | ||||
|             commands.append( | ||||
|                 { { "sh", "-c", m_cmdPasswd + " " + m_sshdUsername }, m_sshdPassword + "\n" + m_sshdPassword + "\n" } ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     foreach ( auto command, commands ) | ||||
|     { | ||||
|         auto location = System::RunLocation::RunInTarget; | ||||
|         const QString pathRoot = "/"; | ||||
|         const QStringList args = command.first; | ||||
|         const QString stdInput = command.second; | ||||
|  | ||||
|         ProcessResult res = System::runCommand( location, args, pathRoot, stdInput, chrono::seconds( 30 ) ); | ||||
|         if ( res.getExitCode() ) | ||||
|         { | ||||
|             return JobResult::error( "Command failed:<br><br>" | ||||
|                                      "'" | ||||
|                                      + args.join( " " ) | ||||
|                                      + "'<br><br>" | ||||
|                                        " with output:<br><br>" | ||||
|                                        "'" | ||||
|                                      + res.getOutput() + "'" ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return JobResult::ok(); | ||||
| } | ||||
							
								
								
									
										38
									
								
								modules/mobile/UsersJob.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | ||||
| /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later */ | ||||
| #pragma once | ||||
| #include "Job.h" | ||||
|  | ||||
|  | ||||
| class UsersJob : public Calamares::Job | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     UsersJob( bool featureSshd, | ||||
|               const QString& cmdPasswd, | ||||
|               const QString& cmdUsermod, | ||||
|               const QString& cmdSshd, | ||||
|               const QString& cmdSshdUseradd, | ||||
|               bool isSshEnabled, | ||||
|               const QString& username, | ||||
|               const QString& password, | ||||
|               const QString& sshdUsername, | ||||
|               const QString& sshdPassword ); | ||||
|  | ||||
|     QString prettyName() const override; | ||||
|     Calamares::JobResult exec() override; | ||||
|  | ||||
|     Calamares::JobList createJobs(); | ||||
|  | ||||
| private: | ||||
|     bool m_featureSshd; | ||||
|     QString m_cmdPasswd; | ||||
|     QString m_cmdUsermod; | ||||
|     QString m_cmdSshd; | ||||
|     QString m_cmdSshdUseradd; | ||||
|     bool m_isSshEnabled; | ||||
|     QString m_username; | ||||
|     QString m_password; | ||||
|     QString m_sshdUsername; | ||||
|     QString m_sshdPassword; | ||||
| }; | ||||
							
								
								
									
										65
									
								
								modules/mobile/fde_confirm.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,65 @@ | ||||
| /* 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: mainText | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: parent.top | ||||
|         anchors.topMargin: 10 | ||||
|         wrapMode: Text.WordWrap | ||||
|  | ||||
|         text: "To protect your data in case your device gets stolen," + | ||||
|               " it is recommended to enable full disk encryption.<br>" + | ||||
|               "<br>" + | ||||
|               "If you enable full disk encryption, you will be asked for" + | ||||
|               " a password. Without this password, it is not possible to" + | ||||
|               " boot your device or access any data on it. Make sure that" + | ||||
|               " you don't lose this password!" | ||||
|  | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         id: firstButton | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: mainText.bottom | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Enable") | ||||
|         onClicked: { | ||||
|             config.isFdeEnabled = true; | ||||
|             navNext(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: firstButton.bottom | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Disable") | ||||
|         onClicked: { | ||||
|             config.isFdeEnabled = false; | ||||
|             navNextFeature(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										80
									
								
								modules/mobile/fde_pass.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,80 @@ | ||||
| /* 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 | ||||
|  | ||||
|     TextField { | ||||
|         id: password | ||||
|         anchors.top: parent.top | ||||
|         placeholderText: qsTr("Password") | ||||
|         inputMethodHints: Qt.ImhPreferLowercase | ||||
|         echoMode: TextInput.Password | ||||
|         onTextChanged: validatePassword(password, passwordRepeat, | ||||
|                                         errorText) | ||||
|         text: config.fdePassword | ||||
|  | ||||
|         onActiveFocusChanged: { | ||||
|             if(activeFocus) { | ||||
|                 Qt.inputMethod.update(Qt.ImQueryInput); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 50 | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     TextField { | ||||
|         id: passwordRepeat | ||||
|         anchors.top: password.bottom | ||||
|         placeholderText: qsTr("Password (repeat)") | ||||
|         echoMode: TextInput.Password | ||||
|         onTextChanged: validatePassword(password, passwordRepeat, | ||||
|                                         errorText) | ||||
|         text: config.fdePassword | ||||
|  | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     Text { | ||||
|         id: errorText | ||||
|         anchors.top: passwordRepeat.bottom | ||||
|         visible: false | ||||
|  | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|         wrapMode: Text.WordWrap | ||||
|     } | ||||
|     Button { | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: errorText.bottom | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Continue") | ||||
|         onClicked: { | ||||
|             if (validatePassword(password, passwordRepeat, errorText)) { | ||||
|                 config.fdePassword = password.text; | ||||
|                 navNext(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										60
									
								
								modules/mobile/install_confirm.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,60 @@ | ||||
| /* 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: mainText | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: parent.top | ||||
|         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>"; | ||||
|  | ||||
|             if (config.installFromExternalToInternal) { | ||||
|                 ret += "<b>After the installation, your device will shutdown" + | ||||
|                        " automatically. You must remove the external storage" + | ||||
|                        " (SD card) before booting again.</b>" + | ||||
|                        "<br><br>" + | ||||
|                        "Otherwise, your device will boot into the installer" + | ||||
|                        " again, and not into the installed system." | ||||
|             } else { | ||||
|                 ret += "Afterwards, it will reboot into the installed system."; | ||||
|             } | ||||
|  | ||||
|             return ret; | ||||
|         }()) | ||||
|  | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         id: firstButton | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: mainText.bottom | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Install") | ||||
|         onClicked: navFinish() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										64
									
								
								modules/mobile/install_target.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,64 @@ | ||||
| /* 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: mainText | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: parent.top | ||||
|         anchors.topMargin: 10 | ||||
|         wrapMode: Text.WordWrap | ||||
|  | ||||
|         text: "The installation was started from an external storage medium." + | ||||
|               "<br>" + | ||||
|               "You can either install to the same medium and overwrite the" + | ||||
|               " installer, or install to the internal storage.<br>" + | ||||
|               "<br>" + | ||||
|               "Where would you like to install " + config.osName + "?" | ||||
|  | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         id: firstButton | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: mainText.bottom | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Internal (eMMC)") | ||||
|         onClicked: { | ||||
|             config.installFromExternalToInternal = true; | ||||
|             navNext(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: firstButton.bottom | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("External (SD card)") | ||||
|         onClicked: { | ||||
|             config.installFromExternalToInternal = false; | ||||
|             navNextFeature(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										58
									
								
								modules/mobile/install_target_confirm.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,58 @@ | ||||
| /* 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: mainText | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: parent.top | ||||
|         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: 200 | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         id: firstButton | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: mainText.bottom | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Yes") | ||||
|         onClicked: { | ||||
|             navNext(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: firstButton.bottom | ||||
|         anchors.topMargin: 10 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("No") | ||||
|         onClicked: { | ||||
|             navBack(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										173
									
								
								modules/mobile/mobile.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,173 @@ | ||||
| # 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 | ||||
| ####### | ||||
|  | ||||
| ## Operating System Name | ||||
| # osName: "(unknown)" | ||||
|  | ||||
| ## 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 | ||||
| ####### | ||||
|  | ||||
| ## Architecture (e.g. aarch64) | ||||
| # arch: "(unknown)" | ||||
|  | ||||
| ## Name of the device (e.g. PinePhone) | ||||
| # device: "(unknown)" | ||||
|  | ||||
| ## Partition that will be formatted and mounted (optionally with FDE) for the | ||||
| ## rootfs | ||||
| # targetDeviceRoot: "/dev/unknown" | ||||
|  | ||||
| ## Partition that will be formatted and mounted (optionally with FDE) for the | ||||
| ## rootfs, on internal storage. The installer OS must not set this, if it was | ||||
| ## booted from the internal storage (this is not checked in the mobile | ||||
| ## module!). | ||||
| ## If this is set, the user gets asked whether they want to install on internal | ||||
| ## or external storage. If the user chose internal storage, | ||||
| ## cmdInternalStoragePrepare (see below) runs before this partition gets | ||||
| ## formatted (see below). A note is displayed, that the device is powered off | ||||
| ## after installation and that the user should remove the external storage | ||||
| ## medium. So you need to adjust the installer OS to poweroff in that case, and | ||||
| ## not reboot. See postmarketos-ondev.git for reference. | ||||
| # targetDeviceRootInternal: "" | ||||
|  | ||||
| ###### | ||||
| ### Installer Features | ||||
| ###### | ||||
|  | ||||
| ## Ask whether sshd should be enabled or not. If enabled, add a dedicated ssh | ||||
| ## user with proper username and password and suggest to change to key-based | ||||
| ## 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 | ||||
| ####### | ||||
|  | ||||
| ## Format the target partition with LUKS | ||||
| ## Arguments: <device> | ||||
| ## Stdin: password with \n | ||||
| # cmdLuksFormat: "cryptsetup luksFormat --use-random" | ||||
|  | ||||
| ## Open the formatted partition | ||||
| ## Arguments: <device> <mapping name> | ||||
| ## Stdin: password with \n | ||||
| # cmdLuksOpen: "cryptsetup luksOpen" | ||||
|  | ||||
| ## Format the rootfs with a file system | ||||
| ## Arguments: <device> | ||||
| ## 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> | ||||
| # cmdMount: "mount" | ||||
|  | ||||
| ## When user selects installation from external storage to internal storage | ||||
| ## (see targetDeviceRootInternal above), use this command to prepare the | ||||
| ## internal storage medium. The command must create a partition table with | ||||
| ## two partitions (boot, root) and fill the boot partition. See the | ||||
| ## ondev-internal-storage-prepare.sh in postmarketos-ondev as example. | ||||
| # cmdInternalStoragePrepare: "ondev-internal-storage-prepare" | ||||
|  | ||||
| ####### | ||||
| ### 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 | ||||
| # cmdPasswd: "passwd" | ||||
|  | ||||
| ## Enable or disable sshd | ||||
| # cmdSshdEnable: "systemctl enable sshd.service" | ||||
| # cmdSshdDisable: "systemctl disable sshd.service" | ||||
|  | ||||
| ## Create the user for sshd | ||||
| ## Arguments: <username> | ||||
| # cmdSshdUseradd: "useradd -G wheel -m" | ||||
							
								
								
									
										390
									
								
								modules/mobile/mobile.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,390 @@ | ||||
| /* 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 | ||||
|  | ||||
| Page | ||||
| { | ||||
|     property var screen: "welcome" | ||||
|     property var screenPrevious: [] | ||||
|     property var titles: { | ||||
|         "welcome": null, /* titlebar disabled */ | ||||
|         "install_target": "Installation target", | ||||
|         "install_target_confirm": "Warning", | ||||
|         "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", | ||||
|         "wait": null | ||||
|     } | ||||
|     property var features: [ | ||||
|         {"name": "welcome", | ||||
|          "screens": ["welcome"]}, | ||||
|         {"name": "installTarget", | ||||
|          "screens": ["install_target", "install_target_confirm"]}, | ||||
|         {"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", | ||||
|          "screens": ["install_confirm", "wait"]} | ||||
|     ] | ||||
|     property var featureIdByScreen: (function() { | ||||
|         /* Put "features" above into an index of screen name -> feature id: | ||||
|          * 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++) { | ||||
|                 ret[ features[i]["screens"][j] ] = i; | ||||
|             } | ||||
|         } | ||||
|         return ret; | ||||
|     }()) | ||||
|     /* 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" + | ||||
|         /* layer 2 */ "1234567890" + "@#$%&-_+()" + ",\"':;!?" + | ||||
|         /* layer 3 */ "~`|·√πτ÷×¶" + "©®£€¥^°*{}" + "\\/<>=[]" + | ||||
|         /* bottom row */ " ." | ||||
|  | ||||
|     Item { | ||||
|         id: appContainer | ||||
|         anchors.left: parent.left | ||||
|         anchors.top: parent.top | ||||
|         anchors.right: parent.right | ||||
|         anchors.bottom: inputPanel.top | ||||
|         Item { | ||||
|             width: parent.width | ||||
|             height: parent.height | ||||
|  | ||||
|             Rectangle { | ||||
|                 id: mobileNavigation | ||||
|                 width: parent.width | ||||
|                 height: 30 | ||||
|                 color: "#e6e4e1" | ||||
|                 Layout.fillWidth: true | ||||
|  | ||||
|                 border.width: 1 | ||||
|                 border.color: "#a7a7a7" | ||||
|  | ||||
|                 anchors.left: parent.left | ||||
|                 anchors.right: parent.right | ||||
|  | ||||
|                 RowLayout { | ||||
|                     width: parent.width | ||||
|                     height: parent.height | ||||
|                     spacing: 6 | ||||
|  | ||||
|                     Button { | ||||
|                         Layout.leftMargin: 6 | ||||
|                         id: mobileBack | ||||
|                         text: "<" | ||||
|  | ||||
|                         background: Rectangle { | ||||
|                             implicitWidth: 10 | ||||
|                             implicitHeight: 7 | ||||
|                             border.color: "#c1bab5" | ||||
|                             border.width: 1 | ||||
|                             radius: 4 | ||||
|                             color: mobileBack.down ? "#dbdbdb" : "#f2f2f2" | ||||
|                         } | ||||
|  | ||||
|                         onClicked: navBack() | ||||
|                     } | ||||
|                     Rectangle { | ||||
|                         implicitHeight: 10 | ||||
|                         Layout.fillWidth: true | ||||
|                         color: "#e6e4e1" | ||||
|  | ||||
|                         Text { | ||||
|                             id: mobileTitle | ||||
|                             text: "" | ||||
|                             color: "#303638" | ||||
|                             anchors.verticalCenter: parent.verticalCenter | ||||
|                             anchors.horizontalCenter: parent.horizontalCenter | ||||
|                         } | ||||
|                     } | ||||
|                     Rectangle { | ||||
|                         color: "#e6e4e1" | ||||
|                         Layout.rightMargin: 6 | ||||
|                         implicitWidth: 32 | ||||
|                         implicitHeight: 30 | ||||
|                         id: filler | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Loader { | ||||
|                 id: load | ||||
|                 anchors.left: parent.left | ||||
|                 anchors.top: mobileNavigation.bottom | ||||
|                 anchors.right: parent.right | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     InputPanel { | ||||
|         id: inputPanel | ||||
|         y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height | ||||
|         visible: config.builtinVirtualKeyboard | ||||
|         anchors.left: parent.left | ||||
|         anchors.right: parent.right | ||||
|     } | ||||
|  | ||||
|     Timer { | ||||
|         id: timer | ||||
|     } | ||||
|  | ||||
|     function skipFeatureInstallTarget() { | ||||
|         return config.targetDeviceRootInternal == ""; | ||||
|     } | ||||
|  | ||||
|     /* Navigation related */ | ||||
|     function navTo(name, historyPush=true) { | ||||
|         console.log("Navigating to screen: " + name); | ||||
|         if (historyPush) | ||||
|             screenPrevious.push(screen); | ||||
|         screen = name; | ||||
|         load.source = name + ".qml"; | ||||
|         mobileNavigation.visible = (titles[name] !== null); | ||||
|         mobileTitle.text = "<b>" + titles[name] + "</b>"; | ||||
|         Qt.inputMethod.hide(); | ||||
|     } | ||||
|     function navFinish() { | ||||
|         /* Show a waiting screen and wait a second (so it can render). The big | ||||
|          * comment in Config.cpp::runPartitionJobThenLeave() explains why this | ||||
|          * is necessary. */ | ||||
|         navTo("wait"); | ||||
|         timer.interval = 1000; | ||||
|         timer.repeat = false; | ||||
|         timer.triggered.connect(function() { | ||||
|             /* Trigger Config.cpp::runPartitionJobThenLeave(). (We could expose | ||||
|              * the function directly with qmlRegisterSingletonType somehow, but | ||||
|              * I haven't seen existing Calamares code do that with the Config | ||||
|              * object, so just use the side effect of setting the variable, as | ||||
|              * done in existing code of Calamares modules.) */ | ||||
|             config.runPartitionJobThenLeave = 1 | ||||
|         }); | ||||
|         timer.start(); | ||||
|     } | ||||
|     function navNextFeature() { | ||||
|         var id; | ||||
|  | ||||
|         /* Skip disabled features */ | ||||
|         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); | ||||
|  | ||||
|             /* Check config.Feature<Name> */ | ||||
|             var configOption = "feature" + nameUp; | ||||
|             if (config[configOption] === false) { | ||||
|                 console.log("Skipping feature (disabled in config): " + name); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             /* Check skipFeature<Name>() */ | ||||
|             var funcName = "skipFeature" + nameUp; | ||||
|             if (eval("typeof " + funcName) === "function" | ||||
|                 && eval(funcName + "()")) { | ||||
|                 console.log("Skipping feature (skip function): " + name); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         console.log("Navigating to feature: " + features[id]["name"]); | ||||
|         return navTo(features[id]["screens"][0]); | ||||
|     } | ||||
|     function navNext() { | ||||
|         var featureId = featureIdByScreen[screen]; | ||||
|         var featureScreens = features[featureId]["screens"]; | ||||
|         for (var i = 0; i<featureScreens.length; i++) { | ||||
|             /* Seek ahead until i is current screen */ | ||||
|             if (featureScreens[i] != screen) | ||||
|                 continue; | ||||
|  | ||||
|             /* Navigate to next screen in same feature */ | ||||
|             if (i + 1 < featureScreens.length) { | ||||
|                 var screenNext = featureScreens[i + 1]; | ||||
|                 return navTo(screenNext); | ||||
|             } | ||||
|  | ||||
|             /* Screen is last in feature */ | ||||
|             return navNextFeature(); | ||||
|         } | ||||
|         console.log("ERROR: navNext() failed for screen: " + screen); | ||||
|     } | ||||
|     function navBack() { | ||||
|         if (screenPrevious.length) | ||||
|             return navTo(screenPrevious.pop(), false); | ||||
|         ViewManager.back(); | ||||
|     } | ||||
|     function onActivate() { | ||||
|         navTo(screen, false); | ||||
|     } | ||||
|  | ||||
|     /* Input validation: show/clear failures */ | ||||
|     function validationFailure(errorText, message="") { | ||||
|         errorText.text = message; | ||||
|         errorText.visible = true; | ||||
|         return false; | ||||
|     } | ||||
|     function validationFailureClear(errorText) { | ||||
|         errorText.text = ""; | ||||
|         errorText.visible = false; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /* Input validation: user-screens (fde_pass, user_pass, ssh_credentials) */ | ||||
|     function validatePin(userPin, userPinRepeat, errorText) { | ||||
|         var pin = userPin.text; | ||||
|         var repeat = userPinRepeat.text; | ||||
|  | ||||
|         if (pin == "") | ||||
|             return validationFailure(errorText); | ||||
|  | ||||
|         if (!pin.match(/^[0-9]*$/)) | ||||
|             return validationFailure(errorText, | ||||
|                                      "Only digits are allowed."); | ||||
|  | ||||
|         if (pin.length < 5) | ||||
|             return validationFailure(errorText, | ||||
|                                      "Too short: needs at least 5 digits."); | ||||
|  | ||||
|         if (repeat == "") | ||||
|             return validationFailure(errorText); | ||||
|  | ||||
|         if (repeat != pin) | ||||
|             return validationFailure(errorText, | ||||
|                                      "The PINs don't match."); | ||||
|  | ||||
|         return validationFailureClear(errorText); | ||||
|     } | ||||
|     function validateUsername(username, errorText, extraReservedUsernames = []) { | ||||
|         var name = username.text; | ||||
|         var reserved = config.reservedUsernames.concat(extraReservedUsernames); | ||||
|  | ||||
|         /* Validate characters */ | ||||
|         for (var i = 0; i < name.length; i++) { | ||||
|             if (i) { | ||||
|                 if (!name[i].match(/^[a-z0-9_-]$/)) | ||||
|                     return validationFailure(errorText, | ||||
|                                              "Characters must be lowercase" + | ||||
|                                              " letters, numbers,<br>" + | ||||
|                                              " underscores or minus signs."); | ||||
|             } else { | ||||
|                 if (!name[i].match(/^[a-z_]$/)) | ||||
|                     return validationFailure(errorText, | ||||
|                                              "First character must be a" + | ||||
|                                              " lowercase letter or an" + | ||||
|                                              " underscore."); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Validate against reserved usernames */ | ||||
|         for (var i = 0; i < reserved.length; i++) { | ||||
|             if (name == reserved[i]) | ||||
|                 return validationFailure(errorText, "Username '" + | ||||
|                                                     reserved[i] + | ||||
|                                                     "' 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; | ||||
|  | ||||
|         if (pass == "") | ||||
|             return validationFailure(errorText); | ||||
|  | ||||
|         if (pass.length < 6) | ||||
|             return validationFailure(errorText, | ||||
|                                      "Too short: needs at least 6" + | ||||
|                                      " digits/characters."); | ||||
|  | ||||
|         if (repeat == "") | ||||
|             return validationFailure(errorText); | ||||
|  | ||||
|         if (pass != repeat) | ||||
|             return validationFailure(errorText, "Passwords don't match."); | ||||
|  | ||||
|         return validationFailureClear(errorText); | ||||
|     } | ||||
|     function check_chars(input) { | ||||
|         for (var i = 0; i < input.length; i++) { | ||||
|             if (allowed_chars.indexOf(input[i]) == -1) | ||||
|                 return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|     function allowed_chars_multiline() { | ||||
|         /* return allowed_chars split across multiple lines */ | ||||
|         var step = 20; | ||||
|         var ret = ""; | ||||
|         for (var i = 0; i < allowed_chars.length + step; i += step) | ||||
|             ret += allowed_chars.slice(i, i + step) + "\n"; | ||||
|         return ret.trim(); | ||||
|     } | ||||
|     function validatePassword(password, passwordRepeat, errorText) { | ||||
|         var pass = password.text; | ||||
|         var repeat = passwordRepeat.text; | ||||
|  | ||||
|         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 can possibly" + | ||||
|                                      " not be typed in after installation:\n" + | ||||
|                                      "\n" + | ||||
|                                      allowed_chars_multiline()); | ||||
|  | ||||
|         if (pass.length < 6) | ||||
|             return validationFailure(errorText, | ||||
|                                      "Too short: needs at least 6" + | ||||
|                                      " digits/characters."); | ||||
|  | ||||
|         if (repeat == "") | ||||
|             return validationFailure(errorText); | ||||
|  | ||||
|         if (pass != repeat) | ||||
|             return validationFailure(errorText, "Passwords don't match."); | ||||
|  | ||||
|         return validationFailureClear(errorText); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								modules/mobile/mobile.qrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| <!DOCTYPE RCC><RCC version="1.0"> | ||||
| <qresource> | ||||
|   <file>mobile.qml</file> | ||||
|  | ||||
|   <file>welcome.qml</file> | ||||
|  | ||||
|   <file>install_target.qml</file> <!-- install from external to internal? --> | ||||
|   <file>install_target_confirm.qml</file> <!-- overwrite internal storage? --> | ||||
|  | ||||
|   <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) --> | ||||
|   <file>install_confirm.qml</file> <!-- final confirmation before install --> | ||||
|  | ||||
|   <file>wait.qml</file> <!-- please wait while partitioning --> | ||||
| </qresource> | ||||
| </RCC> | ||||
							
								
								
									
										68
									
								
								modules/mobile/ssh_confirm.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | ||||
| /* 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: mainText | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: parent.top | ||||
|         anchors.topMargin: 30 | ||||
|         wrapMode: Text.WordWrap | ||||
|  | ||||
|         text: "If you don't know what SSH is, choose 'disable'.<br>" + | ||||
|               "<br>" + | ||||
|               "With 'enable', you will be asked for a second username and" + | ||||
|               " password. You will be able to login to the SSH server with" + | ||||
|               " these credentials via USB (172.16.42.1), Wi-Fi and possibly" + | ||||
|               " cellular network. It is recommended to replace the password" + | ||||
|               " with an SSH key after the installation.<br>" + | ||||
|               "<br>" + | ||||
|               "More information:<br>" + | ||||
|               "https://postmarketos.org/ssh" | ||||
|  | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         id: firstButton | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: mainText.bottom | ||||
|         anchors.topMargin: 40 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Enable") | ||||
|         onClicked: { | ||||
|             config.isSshEnabled = true; | ||||
|             navNext(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Button { | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: firstButton.bottom | ||||
|         anchors.topMargin: 40 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Disable") | ||||
|         onClicked: { | ||||
|             config.isSshEnabled = false; | ||||
|             navNextFeature(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										107
									
								
								modules/mobile/ssh_credentials.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,107 @@ | ||||
| /* 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 | ||||
|  | ||||
|     TextField { | ||||
|         id: username | ||||
|         anchors.top: parent.top | ||||
|         placeholderText: qsTr("SSH username") | ||||
|         inputMethodHints: Qt.ImhPreferLowercase | ||||
|         onTextChanged: validateSshdUsername(username, errorTextUsername) | ||||
|         text: config.sshdUsername | ||||
|  | ||||
|         onActiveFocusChanged: { | ||||
|             if(activeFocus) { | ||||
|                 Qt.inputMethod.update(Qt.ImQueryInput); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 50 | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     Text { | ||||
|         id: errorTextUsername | ||||
|         anchors.top: username.bottom | ||||
|         visible: false | ||||
|         wrapMode: Text.WordWrap | ||||
|  | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 50 | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     TextField { | ||||
|         id: password | ||||
|         anchors.top: errorTextUsername.bottom | ||||
|         placeholderText: qsTr("SSH password") | ||||
|         echoMode: TextInput.Password | ||||
|         onTextChanged: validateSshdPassword(password, passwordRepeat, | ||||
|                                            errorTextPassword) | ||||
|         text: config.sshdPassword | ||||
|  | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 50 | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     TextField { | ||||
|         id: passwordRepeat | ||||
|         anchors.top: password.bottom | ||||
|         placeholderText: qsTr("SSH password (repeat)") | ||||
|         echoMode: TextInput.Password | ||||
|         onTextChanged: validateSshdPassword(password, passwordRepeat, | ||||
|                                            errorTextPassword) | ||||
|         text: config.sshdPassword | ||||
|  | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 50 | ||||
|         width: 200 | ||||
|     } | ||||
|  | ||||
|     Text { | ||||
|         id: errorTextPassword | ||||
|         anchors.top: passwordRepeat.bottom | ||||
|         visible: false | ||||
|         wrapMode: Text.WordWrap | ||||
|  | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 50 | ||||
|         width: 200 | ||||
|     } | ||||
|     Button { | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.top: errorTextPassword.bottom | ||||
|         anchors.topMargin: 40 | ||||
|         width: 200 | ||||
|  | ||||
|         text: qsTr("Continue") | ||||
|         onClicked: { | ||||
|             if (validateSshdUsername(username, errorTextUsername) && | ||||
|                 validateSshdPassword(password, passwordRepeat, | ||||
|                                     errorTextPassword)) { | ||||
|                 config.sshdUsername = username.text; | ||||
|                 config.sshdPassword = password.text; | ||||
|  | ||||
|                 navNext(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								modules/mobile/wait.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,45 @@ | ||||
| /* 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 | ||||
|  | ||||
| Page | ||||
| { | ||||
|     id: fdeWait | ||||
|  | ||||
|     Item { | ||||
|         anchors.left: parent.left | ||||
|         anchors.top: parent.top | ||||
|         anchors.right: parent.right | ||||
|         width: parent.width | ||||
|         height: parent.height | ||||
|  | ||||
|         Image { | ||||
|             id: logo | ||||
|             anchors.horizontalCenter: parent.horizontalCenter | ||||
|             anchors.top: parent.top | ||||
|             anchors.topMargin: 10 | ||||
|             height: 50 | ||||
|             fillMode: Image.PreserveAspectFit | ||||
|             source: "file:///usr/share/calamares/branding/default-mobile/logo.png" | ||||
|         } | ||||
|         Text { | ||||
|             id: waitText | ||||
|             anchors.horizontalCenter: parent.horizontalCenter | ||||
|             anchors.top: logo.bottom | ||||
|             anchors.topMargin: 50 | ||||
|             wrapMode: Text.WordWrap | ||||
|             text: "Formatting and mounting target partition. This may" + | ||||
|                   " take up to ten minutes, please be patient." | ||||
|             width: 200 | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										65
									
								
								modules/mobile/welcome.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,65 @@ | ||||
| /* 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 | ||||
|  | ||||
| Page | ||||
| { | ||||
|     id: welcome | ||||
|  | ||||
|     Item { | ||||
|         id: appContainer | ||||
|         anchors.left: parent.left | ||||
|         anchors.top: parent.top | ||||
|         anchors.right: parent.right | ||||
|         Item { | ||||
|             width: parent.width | ||||
|             height: parent.height | ||||
|  | ||||
|             Image { | ||||
|                 id: logo | ||||
|                 anchors.horizontalCenter: parent.horizontalCenter | ||||
|                 anchors.top: parent.top | ||||
|                 anchors.topMargin: 10 | ||||
|                 height: 50 | ||||
|                 fillMode: Image.PreserveAspectFit | ||||
|                 source: "file:///usr/share/calamares/branding/default-mobile/logo.png" | ||||
|             } | ||||
|             Text { | ||||
|                 id: mainText | ||||
|                 anchors.horizontalCenter: parent.horizontalCenter | ||||
|                 anchors.top: logo.bottom | ||||
|                 anchors.topMargin: 10 | ||||
|                 horizontalAlignment: Text.AlignRight | ||||
|                 text: "You are about to install<br>" + | ||||
|                       "<b>" + config.osName + | ||||
|                       " " + config.version + "</b><br>" + | ||||
|                       "user interface " + | ||||
|                       "<b>" + config.userInterface + "</b><br>" + | ||||
|                       "architecture " + | ||||
|                       "<b>" + config.arch + "</b><br>" + | ||||
|                       "on your <br>" + | ||||
|                       "<b>" + config.device + "</b><br>" | ||||
|                 width: 200 | ||||
|             } | ||||
|  | ||||
|             Button { | ||||
|                 anchors.horizontalCenter: parent.horizontalCenter | ||||
|                 anchors.top: mainText.bottom | ||||
|                 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 | ||||
| @@ -10,9 +10,9 @@ set(_extra_src "") | ||||
| ### OPTIONAL AppData XML support in PackageModel | ||||
| # | ||||
| # | ||||
| option(BUILD_APPDATA "Support appdata: items in PackageChooser (requires QtXml)" ON) | ||||
| option(BUILD_APPDATA "Support appdata: items in PackageChooser (requires QtXml)" OFF) | ||||
| if(BUILD_APPDATA) | ||||
|     find_package(${qtname} COMPONENTS Xml) | ||||
|     find_package(${qtname} REQUIRED COMPONENTS Xml) | ||||
|     if(TARGET ${qtname}::Xml) | ||||
|         add_definitions(-DHAVE_APPDATA) | ||||
|         list(APPEND _extra_libraries ${qtname}::Xml) | ||||
| @@ -23,24 +23,8 @@ endif() | ||||
| ### OPTIONAL AppStream support in PackageModel | ||||
| # | ||||
| # | ||||
| option(BUILD_APPSTREAM "Support appstream: items in PackageChooser (requires libappstream-qt)" ON) | ||||
| if(BUILD_APPSTREAM) | ||||
|     find_package(AppStreamQt) | ||||
|     set_package_properties( | ||||
|         AppStreamQt | ||||
|         PROPERTIES | ||||
|         DESCRIPTION "Support for AppStream (cache) data" | ||||
|         URL "https://github.com/ximion/appstream" | ||||
|         PURPOSE "AppStream provides package data" | ||||
|         TYPE OPTIONAL | ||||
|     ) | ||||
|     if(AppStreamQt_FOUND) | ||||
|         add_definitions(-DHAVE_APPSTREAM_VERSION=${AppStreamQt_VERSION_MAJOR}) | ||||
|         list(APPEND _extra_libraries AppStreamQt) | ||||
|         list(APPEND _extra_src ItemAppStream.cpp) | ||||
|     endif() | ||||
| endif() | ||||
|  | ||||
| # include(AppStreamHelper) | ||||
| # | ||||
| calamares_add_plugin(packagechooser | ||||
|     TYPE viewmodule | ||||
|     EXPORT_MACRO PLUGINDLLEXPORT_PRO | ||||
| @@ -49,6 +33,8 @@ calamares_add_plugin(packagechooser | ||||
|         PackageChooserPage.cpp | ||||
|         PackageChooserViewStep.cpp | ||||
|         PackageModel.cpp | ||||
|         LoaderQueue.cpp | ||||
|         PackageTreeItem.cpp | ||||
|         ${_extra_src} | ||||
|     RESOURCES | ||||
|         packagechooser.qrc | ||||
| @@ -59,7 +45,11 @@ calamares_add_plugin(packagechooser | ||||
|     SHARED_LIB | ||||
| ) | ||||
|  | ||||
| # include(CalamaresAddTest) | ||||
| # 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 | ||||
|   | ||||
| @@ -10,13 +10,14 @@ | ||||
|  | ||||
| #include "Config.h" | ||||
|  | ||||
| #include "LoaderQueue.h" | ||||
|  | ||||
| #ifdef HAVE_APPDATA | ||||
| #include "ItemAppData.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef HAVE_APPSTREAM_VERSION | ||||
| #include "ItemAppStream.h" | ||||
| #include <AppStreamQt/pool.h> | ||||
| #include <memory> | ||||
| #endif | ||||
|  | ||||
| @@ -26,6 +27,9 @@ | ||||
| #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 | ||||
|  * | ||||
| @@ -87,12 +91,68 @@ PackageChooserMethodNames() | ||||
| 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 | ||||
| { | ||||
| @@ -324,6 +384,29 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) | ||||
|     { | ||||
|         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() ) | ||||
|         { | ||||
|   | ||||
| @@ -16,9 +16,16 @@ | ||||
| #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 | ||||
| @@ -55,6 +62,9 @@ class Config : public Calamares::ModuleSystem::Config | ||||
|     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; | ||||
| @@ -73,6 +83,10 @@ public: | ||||
|     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 | ||||
| @@ -101,14 +115,43 @@ public: | ||||
|     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 | ||||
|   | ||||
| @@ -7,20 +7,16 @@ | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| /** @brief Loading items from AppData XML files. | ||||
| /** @brief Loading items from AppStream database. | ||||
|  * | ||||
|  * Only used if QtXML is found, implements PackageItem::fromAppData(). | ||||
|  * Only used if AppStreamQt is found, implements PackageItem::fromAppStream(). | ||||
|  */ | ||||
| #include "PackageModel.h" | ||||
| #include "ItemAppStream.h" | ||||
|  | ||||
| #include "locale/TranslationsModel.h" | ||||
| #include "utils/Logger.h" | ||||
| #include "utils/Variant.h" | ||||
|  | ||||
| #include <AppStreamQt/image.h> | ||||
| #include <AppStreamQt/pool.h> | ||||
| #include <AppStreamQt/screenshot.h> | ||||
|  | ||||
| /// @brief Return number of pixels in a size, for < ordering purposes | ||||
| static inline quint64 | ||||
| sizeOrder( const QSize& size ) | ||||
|   | ||||
| @@ -12,10 +12,28 @@ | ||||
|  | ||||
| #include "PackageModel.h" | ||||
|  | ||||
| namespace AppStream | ||||
| { | ||||
| class Pool; | ||||
| }  // namespace AppStream | ||||
| /* | ||||
|  * 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. | ||||
|  * | ||||
|   | ||||
							
								
								
									
										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 | ||||
| @@ -13,6 +13,9 @@ | ||||
| #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 | ||||
| @@ -194,3 +197,379 @@ PackageListModel::data( const QModelIndex& index, int role ) const | ||||
|  | ||||
|     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(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -13,6 +13,11 @@ | ||||
| #include "locale/TranslatableConfiguration.h" | ||||
| #include "utils/NamedEnum.h" | ||||
|  | ||||
| #include "PackageTreeItem.h" | ||||
|  | ||||
| #include <QAbstractItemModel> | ||||
| #include <QString> | ||||
|  | ||||
| #include <QAbstractListModel> | ||||
| #include <QObject> | ||||
| #include <QPixmap> | ||||
| @@ -132,4 +137,78 @@ 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 | ||||
| Before Width: | Height: | Size: 8.1 KiB | 
| @@ -1,2 +0,0 @@ | ||||
| SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org> | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| Before Width: | Height: | Size: 1.7 KiB | 
| @@ -1,7 +1,5 @@ | ||||
| <RCC> | ||||
|     <qresource prefix="/"> | ||||
|         <file>images/no-selection.png</file> | ||||
|         <file>images/calamares.png</file> | ||||
|         <file>images/if.png</file> | ||||
|     </qresource> | ||||
| </RCC> | ||||
|   | ||||
| @@ -1,69 +0,0 @@ | ||||
| # === This file is part of Calamares - <https://calamares.io> === | ||||
| # | ||||
| #   SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org> | ||||
| #   SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us> | ||||
| #   SPDX-License-Identifier: BSD-2-Clause | ||||
| # | ||||
| if( NOT Calamares_WITH_QML ) | ||||
|     calamares_skip_module( "packagechooserq (QML is not supported in this build)" ) | ||||
|     return() | ||||
| endif() | ||||
|  | ||||
| find_package(${qtname} ${QT_VERSION} CONFIG REQUIRED Core) | ||||
|  | ||||
| # Add optional libraries here | ||||
| set(USER_EXTRA_LIB) | ||||
|  | ||||
| # include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../packagechooser ) | ||||
| set(_packagechooser ${CMAKE_CURRENT_SOURCE_DIR}/../packagechooser) | ||||
| include_directories(${_packagechooser}) | ||||
|  | ||||
| ### OPTIONAL AppData XML support in PackageModel | ||||
| # | ||||
| # | ||||
| option(BUILD_APPDATA "Support appdata: items in PackageChooser (requires QtXml)" ON) | ||||
| if(BUILD_APPDATA) | ||||
|     find_package(${qtname} COMPONENTS Xml) | ||||
|     if(TARGET ${qtname}::Xml) | ||||
|         add_definitions(-DHAVE_APPDATA) | ||||
|         list(APPEND _extra_libraries ${qtname}::Xml) | ||||
|         list(APPEND _extra_src ${_packagechooser}/ItemAppData.cpp) | ||||
|     endif() | ||||
| endif() | ||||
|  | ||||
| ### OPTIONAL AppStream support in PackageModel | ||||
| # | ||||
| # | ||||
| option(BUILD_APPSTREAM "Support appstream: items in PackageChooser (requires libappstream-qt)" ON) | ||||
| if(BUILD_APPSTREAM) | ||||
|     find_package(AppStreamQt) | ||||
|     set_package_properties( | ||||
|         AppStreamQt | ||||
|         PROPERTIES | ||||
|         DESCRIPTION "Support for AppStream (cache) data" | ||||
|         URL "https://github.com/ximion/appstream" | ||||
|         PURPOSE "AppStream provides package data" | ||||
|         TYPE OPTIONAL | ||||
|     ) | ||||
|     if(AppStreamQt_FOUND) | ||||
|         add_definitions(-DHAVE_APPSTREAM_VERSION=${AppStreamQt_VERSION_MAJOR}) | ||||
|         list(APPEND _extra_libraries AppStreamQt) | ||||
|         list(APPEND _extra_src ${_packagechooser}/ItemAppStream.cpp) | ||||
|     endif() | ||||
| endif() | ||||
|  | ||||
| calamares_add_plugin(packagechooserq | ||||
|     TYPE viewmodule | ||||
|     EXPORT_MACRO PLUGINDLLEXPORT_PRO | ||||
|     SOURCES | ||||
|         PackageChooserQmlViewStep.cpp | ||||
|         ${_packagechooser}/Config.cpp | ||||
|         ${_packagechooser}/PackageModel.cpp | ||||
|         ${_extra_src} | ||||
|     RESOURCES | ||||
|         packagechooserq${QT_VERSION_SUFFIX}.qrc | ||||
|     LINK_PRIVATE_LIBRARIES | ||||
|         calamaresui | ||||
|         ${_extra_libraries} | ||||
|     SHARED_LIB | ||||
| ) | ||||
| @@ -1,86 +0,0 @@ | ||||
| /* === This file is part of Calamares - <https://calamares.io> === | ||||
|  * | ||||
|  *   SPDX-FileCopyrightText: 2019 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 "PackageChooserQmlViewStep.h" | ||||
|  | ||||
| #include "GlobalStorage.h" | ||||
| #include "JobQueue.h" | ||||
| #include "locale/TranslatableConfiguration.h" | ||||
| #include "utils/Logger.h" | ||||
| #include "utils/System.h" | ||||
| #include "utils/Variant.h" | ||||
|  | ||||
| CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserQmlViewStepFactory, registerPlugin< PackageChooserQmlViewStep >(); ) | ||||
|  | ||||
| PackageChooserQmlViewStep::PackageChooserQmlViewStep( QObject* parent ) | ||||
|     : Calamares::QmlViewStep( parent ) | ||||
|     , m_config( new Config( this ) ) | ||||
| { | ||||
|     emit nextStatusChanged( true ); | ||||
| } | ||||
|  | ||||
| QString | ||||
| PackageChooserQmlViewStep::prettyName() const | ||||
| { | ||||
|     return m_config->prettyName(); | ||||
| } | ||||
|  | ||||
| QString | ||||
| PackageChooserQmlViewStep::prettyStatus() const | ||||
| { | ||||
|     //QString option = m_pkgc; | ||||
|     //return tr( "Install option: %1" ).arg( option ); | ||||
|     return m_config->prettyStatus(); | ||||
| } | ||||
|  | ||||
| bool | ||||
| PackageChooserQmlViewStep::isNextEnabled() const | ||||
| { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool | ||||
| PackageChooserQmlViewStep::isBackEnabled() const | ||||
| { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool | ||||
| PackageChooserQmlViewStep::isAtBeginning() const | ||||
| { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool | ||||
| PackageChooserQmlViewStep::isAtEnd() const | ||||
| { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| Calamares::JobList | ||||
| PackageChooserQmlViewStep::jobs() const | ||||
| { | ||||
|     Calamares::JobList l; | ||||
|     return l; | ||||
| } | ||||
|  | ||||
| void | ||||
| PackageChooserQmlViewStep::onLeave() | ||||
| { | ||||
|     m_config->updateGlobalStorage(); | ||||
| } | ||||
|  | ||||
| void | ||||
| PackageChooserQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) | ||||
| { | ||||
|     m_config->setDefaultId( moduleInstanceKey() ); | ||||
|     m_config->setConfigurationMap( configurationMap ); | ||||
|     Calamares::QmlViewStep::setConfigurationMap( configurationMap );  // call parent implementation last | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| /* === This file is part of Calamares - <https://calamares.io> === | ||||
|  * | ||||
|  *   SPDX-FileCopyrightText: 2019 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 PACKAGECHOOSERQMLVIEWSTEP_H | ||||
| #define PACKAGECHOOSERQMLVIEWSTEP_H | ||||
|  | ||||
| // Config from packagechooser module | ||||
| #include "Config.h" | ||||
|  | ||||
| #include "DllMacro.h" | ||||
| #include "locale/TranslatableConfiguration.h" | ||||
| #include "utils/PluginFactory.h" | ||||
| #include "viewpages/QmlViewStep.h" | ||||
|  | ||||
| #include <QVariantMap> | ||||
|  | ||||
| class Config; | ||||
| class PackageChooserPage; | ||||
|  | ||||
| class PLUGINDLLEXPORT PackageChooserQmlViewStep : public Calamares::QmlViewStep | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit PackageChooserQmlViewStep( QObject* parent = nullptr ); | ||||
|  | ||||
|     QString prettyName() const override; | ||||
|     QString prettyStatus() const 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; | ||||
|  | ||||
|     QObject* getConfig() override { return m_config; } | ||||
|  | ||||
| private: | ||||
|     Config* m_config; | ||||
| }; | ||||
|  | ||||
| CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserQmlViewStepFactory ) | ||||
|  | ||||
| #endif  // PACKAGECHOOSERQMLVIEWSTEP_H | ||||
| Before Width: | Height: | Size: 20 KiB | 
| Before Width: | Height: | Size: 47 KiB | 
| @@ -1,2 +0,0 @@ | ||||
| SPDX-FileCopyrightText: 2020 demmm <anke62@gmail.com> | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| Before Width: | Height: | Size: 184 KiB | 
| @@ -1,2 +0,0 @@ | ||||
| SPDX-FileCopyrightText: 2020 demmm <anke62@gmail.com> | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| Before Width: | Height: | Size: 34 KiB | 
| @@ -1,2 +0,0 @@ | ||||
| SPDX-FileCopyrightText: 2021 pngegg <https://www.pngegg.com/> | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| @@ -1,304 +0,0 @@ | ||||
| /* === 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 io.calamares.core 1.0 | ||||
| import io.calamares.ui 1.0 | ||||
|  | ||||
| import QtQuick | ||||
| import QtQuick.Controls | ||||
| import QtQuick.Layouts | ||||
|  | ||||
| Item { | ||||
|     width:  parent.width | ||||
|     height: parent.height | ||||
|  | ||||
|     Rectangle { | ||||
|         anchors.fill: parent | ||||
|         color: "#f2f2f2" | ||||
|  | ||||
|         ButtonGroup { | ||||
|             id: switchGroup | ||||
|         } | ||||
|  | ||||
|         Column { | ||||
|             id: column | ||||
|             anchors.centerIn: parent | ||||
|             spacing: 5 | ||||
|  | ||||
|             Rectangle { | ||||
|                 //id: rectangle | ||||
|                 width: 700 | ||||
|                 height: 150 | ||||
|                 color: "#ffffff" | ||||
|                 radius: 10 | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     width: 450 | ||||
|                     height: 104 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("OpenRC base system.<br/> | ||||
|                     Default option.") | ||||
|                     font.pointSize: 10 | ||||
|                     anchors.verticalCenterOffset: -10 | ||||
|                     anchors.horizontalCenterOffset: 100 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|  | ||||
|                 Switch { | ||||
|                     id: element1 | ||||
|                     x: 500 | ||||
|                     y: 110 | ||||
|                     width: 187 | ||||
|                     height: 14 | ||||
|                     text: qsTr("OpenRC") | ||||
|                     checked: true | ||||
|                     hoverEnabled: true | ||||
|                     ButtonGroup.group: switchGroup | ||||
|  | ||||
|                     indicator: Rectangle { | ||||
|                         implicitWidth: 40 | ||||
|                         implicitHeight: 14 | ||||
|                         radius: 10 | ||||
|                         color: element1.checked ? "#3498db" : "#B9B9B9" | ||||
|                         border.color: element1.checked ? "#3498db" : "#cccccc" | ||||
|  | ||||
|                         Rectangle { | ||||
|                             x: element1.checked ? parent.width - width : 0 | ||||
|                             y: (parent.height - height) / 2 | ||||
|                             width: 20 | ||||
|                             height: 20 | ||||
|                             radius: 10 | ||||
|                             color: element1.down ? "#cccccc" : "#ffffff" | ||||
|                             border.color: element1.checked ? (element1.down ? "#3498db" : "#3498db") : "#999999" | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     onCheckedChanged: { | ||||
|                         if ( checked ) { | ||||
|                             config.packageChoice = "openrc" | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Image { | ||||
|                     id: image1 | ||||
|                     x: 8 | ||||
|                     y: 25 | ||||
|                     height: 100 | ||||
|                     fillMode: Image.PreserveAspectFit | ||||
|                     source: "images/if.png" | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Rectangle { | ||||
|                 width: 700 | ||||
|                 height: 150 | ||||
|                 radius: 10 | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     width: 450 | ||||
|                     height: 104 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("Dinit base system.") | ||||
|                     font.pointSize: 10 | ||||
|                     anchors.verticalCenterOffset: -10 | ||||
|                     anchors.horizontalCenterOffset: 100 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|  | ||||
|                 Switch { | ||||
|                     id: element2 | ||||
|                     x: 500 | ||||
|                     y: 110 | ||||
|                     width: 187 | ||||
|                     height: 14 | ||||
|                     text: qsTr("Dinit") | ||||
|                     checked: false | ||||
|                     hoverEnabled: true | ||||
|                     ButtonGroup.group: switchGroup | ||||
|  | ||||
|                     indicator: Rectangle { | ||||
|                         implicitWidth: 40 | ||||
|                         implicitHeight: 14 | ||||
|                         radius: 10 | ||||
|                         color: element2.checked ? "#3498db" : "#B9B9B9" | ||||
|                         border.color: element2.checked ? "#3498db" : "#cccccc" | ||||
|  | ||||
|                         Rectangle { | ||||
|                             x: element2.checked ? parent.width - width : 0 | ||||
|                             y: (parent.height - height) / 2 | ||||
|                             width: 20 | ||||
|                             height: 20 | ||||
|                             radius: 10 | ||||
|                             color: element2.down ? "#cccccc" : "#ffffff" | ||||
|                             border.color: element2.checked ? (element2.down ? "#3498db" : "#3498db") : "#999999" | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     onCheckedChanged: { | ||||
|                         if ( checked ) { | ||||
|                             config.packageChoice = "dinit" | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Image { | ||||
|                     id: image2 | ||||
|                     x: 8 | ||||
|                     y: 25 | ||||
|                     height: 100 | ||||
|                     fillMode: Image.PreserveAspectFit | ||||
|                     source: "images/if.png" | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|  | ||||
|             Rectangle { | ||||
|                 width: 700 | ||||
|                 height: 150 | ||||
|                 color: "#ffffff" | ||||
|                 radius: 10 | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     width: 450 | ||||
|                     height: 104 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("Runit base system.") | ||||
|                     font.pointSize: 10 | ||||
|                     anchors.verticalCenterOffset: -10 | ||||
|                     anchors.horizontalCenterOffset: 100 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|  | ||||
|                 Switch { | ||||
|                     id: element3 | ||||
|                     x: 500 | ||||
|                     y: 110 | ||||
|                     width: 187 | ||||
|                     height: 14 | ||||
|                     text: qsTr("Runit") | ||||
|                     checked: false | ||||
|                     hoverEnabled: true | ||||
|                     ButtonGroup.group: switchGroup | ||||
|  | ||||
|                     indicator: Rectangle { | ||||
|                         implicitWidth: 40 | ||||
|                         implicitHeight: 14 | ||||
|                         radius: 10 | ||||
|                         color: element3.checked ? "#3498db" : "#B9B9B9" | ||||
|                         border.color: element3.checked ? "#3498db" : "#cccccc" | ||||
|  | ||||
|                         Rectangle { | ||||
|                             x: element3.checked ? parent.width - width : 0 | ||||
|                             y: (parent.height - height) / 2 | ||||
|                             width: 20 | ||||
|                             height: 20 | ||||
|                             radius: 10 | ||||
|                             color: element3.down ? "#cccccc" : "#ffffff" | ||||
|                             border.color: element3.checked ? (element3.down ? "#3498db" : "#3498db") : "#999999" | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     onCheckedChanged: { | ||||
|                         if ( checked ) { | ||||
|                             config.packageChoice = "runit" | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Image { | ||||
|                     id: image3 | ||||
|                     x: 8 | ||||
|                     y: 25 | ||||
|                     height: 100 | ||||
|                     fillMode: Image.PreserveAspectFit | ||||
|                     source: "images/if.png" | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Rectangle { | ||||
|                 width: 700 | ||||
|                 height: 150 | ||||
|                 color: "#ffffff" | ||||
|                 radius: 10 | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     width: 450 | ||||
|                     height: 104 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("S6 base system.") | ||||
|                     font.pointSize: 10 | ||||
|                     anchors.verticalCenterOffset: -10 | ||||
|                     anchors.horizontalCenterOffset: 100 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|  | ||||
|                 Switch { | ||||
|                     id: element4 | ||||
|                     x: 500 | ||||
|                     y: 110 | ||||
|                     width: 187 | ||||
|                     height: 14 | ||||
|                     text: qsTr("S6") | ||||
|                     checked: false | ||||
|                     hoverEnabled: true | ||||
|                     ButtonGroup.group: switchGroup | ||||
|  | ||||
|                     indicator: Rectangle { | ||||
|                         implicitWidth: 40 | ||||
|                         implicitHeight: 14 | ||||
|                         radius: 10 | ||||
|                         color: element4.checked ? "#3498db" : "#B9B9B9" | ||||
|                         border.color: element4.checked ? "#3498db" : "#cccccc" | ||||
|  | ||||
|                         Rectangle { | ||||
|                             x: element4.checked ? parent.width - width : 0 | ||||
|                             y: (parent.height - height) / 2 | ||||
|                             width: 20 | ||||
|                             height: 20 | ||||
|                             radius: 10 | ||||
|                             color: element4.down ? "#cccccc" : "#ffffff" | ||||
|                             border.color: element4.checked ? (element4.down ? "#3498db" : "#3498db") : "#999999" | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     onCheckedChanged: { | ||||
|                         if ( checked ) { | ||||
|                             config.packageChoice = "s6" | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Image { | ||||
|                     id: image4 | ||||
|                     x: 8 | ||||
|                     y: 25 | ||||
|                     height: 100 | ||||
|                     fillMode: Image.PreserveAspectFit | ||||
|                     source: "images/if.png" | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Rectangle { | ||||
|                 width: 700 | ||||
|                 height: 25 | ||||
|                 color: "#f2f2f2" | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     height: 25 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("Please select an option for your install, or use the default: OpenRC.") | ||||
|                     font.pointSize: 10 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,9 +0,0 @@ | ||||
| <RCC> | ||||
|     <qresource> | ||||
|         <file alias="packagechooserq.qml">packagechooserq-qt6.qml</file> | ||||
|         <file>images/libreoffice.jpg</file> | ||||
|         <file>images/no-selection.png</file> | ||||
|         <file>images/plasma.png</file> | ||||
|         <file>images/if.png</file> | ||||
|     </qresource> | ||||
| </RCC> | ||||
| @@ -1,66 +0,0 @@ | ||||
| # SPDX-FileCopyrightText: no | ||||
| # SPDX-License-Identifier: CC0-1.0 | ||||
| # | ||||
| # Configuration for the low-density software chooser, QML implementation | ||||
| # | ||||
| # The example QML implementation uses single-selection, rather than | ||||
| # a model for the available packages. That makes it simpler: the | ||||
| # QML itself codes the available options, descriptions and images | ||||
| # -- after all, this is **low density** selection, so a custom UI | ||||
| # can make sense for the few choices that need to be made. | ||||
| # | ||||
| # | ||||
|  | ||||
| --- | ||||
| # 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 packagechooserq module, and no special instance is set, | ||||
| #   resulting GS key is probably *packagechooser_packagechooserq*. | ||||
| #   (Do note that the prefix of the GS key remains "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. | ||||
| # | ||||
| # 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" | ||||
|  | ||||
| # The *packageChoice* value is used for setting the default selection | ||||
| # in the QML view; this should match one of the keys used in the QML | ||||
| # module for package names. | ||||
| # | ||||
| # (e.g. the sample QML uses "no_office_suite", "minimal_install" and | ||||
| # "libreoffice" as possible choices). | ||||
| # | ||||
| packageChoice: libreoffice | ||||
|  | ||||
| @@ -1,304 +0,0 @@ | ||||
| /* === 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 io.calamares.core 1.0 | ||||
| import io.calamares.ui 1.0 | ||||
|  | ||||
| import QtQuick 2.15 | ||||
| import QtQuick.Controls 2.15 | ||||
| import QtQuick.Layouts 1.3 | ||||
|  | ||||
| Item { | ||||
|     width:  parent.width | ||||
|     height: parent.height | ||||
|  | ||||
|     Rectangle { | ||||
|         anchors.fill: parent | ||||
|         color: "#f2f2f2" | ||||
|  | ||||
|         ButtonGroup { | ||||
|             id: switchGroup | ||||
|         } | ||||
|  | ||||
|         Column { | ||||
|             id: column | ||||
|             anchors.centerIn: parent | ||||
|             spacing: 5 | ||||
|  | ||||
|             Rectangle { | ||||
|                 //id: rectangle | ||||
|                 width: 700 | ||||
|                 height: 150 | ||||
|                 color: "#ffffff" | ||||
|                 radius: 10 | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     width: 450 | ||||
|                     height: 104 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("OpenRC base system.<br/> | ||||
|                     Default option.") | ||||
|                     font.pointSize: 10 | ||||
|                     anchors.verticalCenterOffset: -10 | ||||
|                     anchors.horizontalCenterOffset: 100 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|  | ||||
|                 Switch { | ||||
|                     id: element1 | ||||
|                     x: 500 | ||||
|                     y: 110 | ||||
|                     width: 187 | ||||
|                     height: 14 | ||||
|                     text: qsTr("OpenRC") | ||||
|                     checked: true | ||||
|                     hoverEnabled: true | ||||
|                     ButtonGroup.group: switchGroup | ||||
|  | ||||
|                     indicator: Rectangle { | ||||
|                         implicitWidth: 40 | ||||
|                         implicitHeight: 14 | ||||
|                         radius: 10 | ||||
|                         color: element1.checked ? "#3498db" : "#B9B9B9" | ||||
|                         border.color: element1.checked ? "#3498db" : "#cccccc" | ||||
|  | ||||
|                         Rectangle { | ||||
|                             x: element1.checked ? parent.width - width : 0 | ||||
|                             y: (parent.height - height) / 2 | ||||
|                             width: 20 | ||||
|                             height: 20 | ||||
|                             radius: 10 | ||||
|                             color: element1.down ? "#cccccc" : "#ffffff" | ||||
|                             border.color: element1.checked ? (element1.down ? "#3498db" : "#3498db") : "#999999" | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     onCheckedChanged: { | ||||
|                         if ( checked ) { | ||||
|                             config.packageChoice = "openrc" | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Image { | ||||
|                     id: image1 | ||||
|                     x: 8 | ||||
|                     y: 25 | ||||
|                     height: 100 | ||||
|                     fillMode: Image.PreserveAspectFit | ||||
|                     source: "images/if.png" | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Rectangle { | ||||
|                 width: 700 | ||||
|                 height: 150 | ||||
|                 radius: 10 | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     width: 450 | ||||
|                     height: 104 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("Dinit base system.") | ||||
|                     font.pointSize: 10 | ||||
|                     anchors.verticalCenterOffset: -10 | ||||
|                     anchors.horizontalCenterOffset: 100 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|  | ||||
|                 Switch { | ||||
|                     id: element2 | ||||
|                     x: 500 | ||||
|                     y: 110 | ||||
|                     width: 187 | ||||
|                     height: 14 | ||||
|                     text: qsTr("Dinit") | ||||
|                     checked: false | ||||
|                     hoverEnabled: true | ||||
|                     ButtonGroup.group: switchGroup | ||||
|  | ||||
|                     indicator: Rectangle { | ||||
|                         implicitWidth: 40 | ||||
|                         implicitHeight: 14 | ||||
|                         radius: 10 | ||||
|                         color: element2.checked ? "#3498db" : "#B9B9B9" | ||||
|                         border.color: element2.checked ? "#3498db" : "#cccccc" | ||||
|  | ||||
|                         Rectangle { | ||||
|                             x: element2.checked ? parent.width - width : 0 | ||||
|                             y: (parent.height - height) / 2 | ||||
|                             width: 20 | ||||
|                             height: 20 | ||||
|                             radius: 10 | ||||
|                             color: element2.down ? "#cccccc" : "#ffffff" | ||||
|                             border.color: element2.checked ? (element2.down ? "#3498db" : "#3498db") : "#999999" | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     onCheckedChanged: { | ||||
|                         if ( checked ) { | ||||
|                             config.packageChoice = "dinit" | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Image { | ||||
|                     id: image2 | ||||
|                     x: 8 | ||||
|                     y: 25 | ||||
|                     height: 100 | ||||
|                     fillMode: Image.PreserveAspectFit | ||||
|                     source: "images/if.png" | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|  | ||||
|             Rectangle { | ||||
|                 width: 700 | ||||
|                 height: 150 | ||||
|                 color: "#ffffff" | ||||
|                 radius: 10 | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     width: 450 | ||||
|                     height: 104 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("Runit base system.") | ||||
|                     font.pointSize: 10 | ||||
|                     anchors.verticalCenterOffset: -10 | ||||
|                     anchors.horizontalCenterOffset: 100 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|  | ||||
|                 Switch { | ||||
|                     id: element3 | ||||
|                     x: 500 | ||||
|                     y: 110 | ||||
|                     width: 187 | ||||
|                     height: 14 | ||||
|                     text: qsTr("Runit") | ||||
|                     checked: false | ||||
|                     hoverEnabled: true | ||||
|                     ButtonGroup.group: switchGroup | ||||
|  | ||||
|                     indicator: Rectangle { | ||||
|                         implicitWidth: 40 | ||||
|                         implicitHeight: 14 | ||||
|                         radius: 10 | ||||
|                         color: element3.checked ? "#3498db" : "#B9B9B9" | ||||
|                         border.color: element3.checked ? "#3498db" : "#cccccc" | ||||
|  | ||||
|                         Rectangle { | ||||
|                             x: element3.checked ? parent.width - width : 0 | ||||
|                             y: (parent.height - height) / 2 | ||||
|                             width: 20 | ||||
|                             height: 20 | ||||
|                             radius: 10 | ||||
|                             color: element3.down ? "#cccccc" : "#ffffff" | ||||
|                             border.color: element3.checked ? (element3.down ? "#3498db" : "#3498db") : "#999999" | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     onCheckedChanged: { | ||||
|                         if ( checked ) { | ||||
|                             config.packageChoice = "runit" | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Image { | ||||
|                     id: image3 | ||||
|                     x: 8 | ||||
|                     y: 25 | ||||
|                     height: 100 | ||||
|                     fillMode: Image.PreserveAspectFit | ||||
|                     source: "images/if.png" | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Rectangle { | ||||
|                 width: 700 | ||||
|                 height: 150 | ||||
|                 color: "#ffffff" | ||||
|                 radius: 10 | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     width: 450 | ||||
|                     height: 104 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("S6 base system.") | ||||
|                     font.pointSize: 10 | ||||
|                     anchors.verticalCenterOffset: -10 | ||||
|                     anchors.horizontalCenterOffset: 100 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|  | ||||
|                 Switch { | ||||
|                     id: element4 | ||||
|                     x: 500 | ||||
|                     y: 110 | ||||
|                     width: 187 | ||||
|                     height: 14 | ||||
|                     text: qsTr("S6") | ||||
|                     checked: false | ||||
|                     hoverEnabled: true | ||||
|                     ButtonGroup.group: switchGroup | ||||
|  | ||||
|                     indicator: Rectangle { | ||||
|                         implicitWidth: 40 | ||||
|                         implicitHeight: 14 | ||||
|                         radius: 10 | ||||
|                         color: element4.checked ? "#3498db" : "#B9B9B9" | ||||
|                         border.color: element4.checked ? "#3498db" : "#cccccc" | ||||
|  | ||||
|                         Rectangle { | ||||
|                             x: element4.checked ? parent.width - width : 0 | ||||
|                             y: (parent.height - height) / 2 | ||||
|                             width: 20 | ||||
|                             height: 20 | ||||
|                             radius: 10 | ||||
|                             color: element4.down ? "#cccccc" : "#ffffff" | ||||
|                             border.color: element4.checked ? (element4.down ? "#3498db" : "#3498db") : "#999999" | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     onCheckedChanged: { | ||||
|                         if ( checked ) { | ||||
|                             config.packageChoice = "s6" | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Image { | ||||
|                     id: image4 | ||||
|                     x: 8 | ||||
|                     y: 25 | ||||
|                     height: 100 | ||||
|                     fillMode: Image.PreserveAspectFit | ||||
|                     source: "images/if.png" | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Rectangle { | ||||
|                 width: 700 | ||||
|                 height: 25 | ||||
|                 color: "#f2f2f2" | ||||
|                 border.width: 0 | ||||
|                 Text { | ||||
|                     height: 25 | ||||
|                     anchors.centerIn: parent | ||||
|                     text: qsTr("Please select an option for your install, or use the default: OpenRC.") | ||||
|                     font.pointSize: 10 | ||||
|                     wrapMode: Text.WordWrap | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,9 +0,0 @@ | ||||
| <RCC> | ||||
|     <qresource> | ||||
|         <file>packagechooserq.qml</file> | ||||
|         <file>images/libreoffice.jpg</file> | ||||
|         <file>images/no-selection.png</file> | ||||
|         <file>images/plasma.png</file> | ||||
|         <file>images/if.png</file> | ||||
|     </qresource> | ||||
| </RCC> | ||||
| @@ -1,80 +0,0 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # === This file is part of Calamares - <http://github.com/calamares> === | ||||
| # | ||||
| #   Copyright 2014 - 2016, Philip Müller <philm@manjaro.org> | ||||
| #   Copyright 2016, Artoo <artoo@manjaro.org> | ||||
| #   Copyright 2018, Artoo <artoo@artixlinux.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/>. | ||||
|  | ||||
|  | ||||
| from os.path import join, exists | ||||
|  | ||||
| import libcalamares | ||||
| from libcalamares.utils import target_env_call | ||||
|  | ||||
|  | ||||
| class ConfigController: | ||||
|     """Configuration controller | ||||
|     """ | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.root = libcalamares.globalstorage.value("rootMountPoint") | ||||
|  | ||||
|     def terminate(self, proc): | ||||
|         """Send SIGKILL to the given proccess | ||||
|         """ | ||||
|         target_env_call(['killall', '-9', proc]) | ||||
|  | ||||
|     def sedFile(self, pattern, file): | ||||
|         """Sed the given file with the given pattern | ||||
|         """ | ||||
|         target_env_call(["sed", "-e", pattern, "-i", file]) | ||||
|  | ||||
|     def configure(self): | ||||
|         """Configure the services | ||||
|         """ | ||||
|         if exists(join(self.root, "/etc/conf.d/keymaps")): | ||||
|             exp = 's|^.*keymap=.*|keymap="{}"|'.format( | ||||
|                 libcalamares.globalstorage.value("keyboardLayout") | ||||
|             ) | ||||
|             self.sedFile(exp, "/etc/conf.d/keymaps") | ||||
|  | ||||
|         if exists(join(self.root, "/etc/conf.d/xdm")): | ||||
|             for dm in libcalamares.globalstorage.value("displayManagers"): | ||||
|                 exp = 's|^.*DISPLAYMANAGER=.*|DISPLAYMANAGER="{}"|'.format(dm) | ||||
|                 self.sedFile(exp, "/etc/conf.d/xdm") | ||||
|  | ||||
|  | ||||
|     def run(self): | ||||
|         """Run the controller | ||||
|  | ||||
|         Workaround for pacman-key bug | ||||
|         FS#45351 https://bugs.archlinux.org/task/45351 | ||||
|         We have to kill gpg-agent because if it stays | ||||
|         around we can't reliably unmount | ||||
|         the target partition. | ||||
|         """ | ||||
|         self.configure() | ||||
|         self.terminate('gpg-agent') | ||||
|  | ||||
|  | ||||
| def run(): | ||||
|     """ Misc postinstall configurations """ | ||||
|  | ||||
|     config = ConfigController() | ||||
|  | ||||
|     return config.run() | ||||
| @@ -1,7 +0,0 @@ | ||||
| # Syntax is YAML 1.2 | ||||
| --- | ||||
| type:       "job" | ||||
| name:       "postcfg" | ||||
| interface:  "python" | ||||
| script:     "main.py"   #assumed relative to the current directory | ||||
| noconfig:   true | ||||
| @@ -1,104 +0,0 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # === This file is part of Calamares - <https://calamares.io> === | ||||
| # | ||||
| #   SPDX-FileCopyrightText: 2016 Artoo <artoo@manjaro.org> | ||||
| #   SPDX-FileCopyrightText: 2017 Philip Müller <philm@manjaro.org> | ||||
| #   SPDX-FileCopyrightText: 2018 Artoo <artoo@artixlinux.org> | ||||
| #   SPDX-FileCopyrightText: 2018-2019 Adriaan de Groot <groot@kde.org> | ||||
| #   SPDX-FileCopyrightText: 2023-2024 Artoo <artoo@artixlinux.org> | ||||
| #   SPDX-License-Identifier: GPL-3.0-or-later | ||||
| # | ||||
| #   Calamares is Free Software: see the License-Identifier above. | ||||
| # | ||||
|  | ||||
| import libcalamares | ||||
|  | ||||
| import gettext | ||||
| _ = gettext.translation("calamares-python", | ||||
|                         localedir=libcalamares.utils.gettext_path(), | ||||
|                         languages=libcalamares.utils.gettext_languages(), | ||||
|                         fallback=True).gettext | ||||
|  | ||||
|  | ||||
| def pretty_name(): | ||||
|     return _("Configure services") | ||||
|  | ||||
|  | ||||
| def manage_services(services, manager, cmd): | ||||
|     """ | ||||
|     For each entry in @p services, run "<command> <action> <name>", | ||||
|     where each service is a mapping of service name, action, and a flag. | ||||
|  | ||||
|     Returns a failure message, or None if this was successful. | ||||
|     Services that are not mandatory have their failures suppressed | ||||
|     silently. | ||||
|     """ | ||||
|     for sv in services: | ||||
|         if isinstance(sv, str): | ||||
|             name = sv | ||||
|             action = "enable" | ||||
|             mandatory = False | ||||
|             alias = "add" | ||||
|             target = "default" | ||||
|         else: | ||||
|             if "name" not in sv: | ||||
|                 libcalamares.utils.error("The key 'name' is missing from the mapping {_sv!s}. Continuing to the next service.".format(_sv=str(sv))) | ||||
|                 continue | ||||
|             name = sv["name"] | ||||
|             action = sv.get("action", "enable") | ||||
|             mandatory = sv.get("mandatory", False) | ||||
|             alias = sv.get("alias", "add") | ||||
|             target = sv.get("target", "default") | ||||
|  | ||||
|         command = [ cmd ] | ||||
|  | ||||
|         match manager: | ||||
|             case "openrc": | ||||
|                 args = [alias, name, target] | ||||
|             case "s6": | ||||
|                 args = [alias, target, name] | ||||
|             case _: | ||||
|                 args = [action, name] | ||||
|  | ||||
|         command.extend(args) | ||||
|  | ||||
|         libcalamares.utils.debug("Manager command: {!s}".format(command)) | ||||
|  | ||||
|         ec = libcalamares.utils.target_env_call(command) | ||||
|  | ||||
|         if ec != 0: | ||||
|             libcalamares.utils.warning( | ||||
|                 "Cannot {} service {}".format(action, name) | ||||
|             ) | ||||
|             libcalamares.utils.warning( | ||||
|                 "service {} call in chroot returned error code {}".format(action, ec) | ||||
|             ) | ||||
|             if mandatory: | ||||
|                 title = _("Cannot modify service") | ||||
|                 diagnostic = _("<code>service {_action!s}</code> call in chroot returned error code {_ec!s}.").format(_action=action, _ec=ec) | ||||
|                 description = _("Cannot {_action!s} service <code>{_name!s}</code>.").format(_action=action, _name=name) | ||||
|                 return ( | ||||
|                     title, | ||||
|                     description + " " + diagnostic | ||||
|                 ) | ||||
|  | ||||
|  | ||||
| def run(): | ||||
|     """ | ||||
|     Setup services | ||||
|     """ | ||||
|     manager = libcalamares.job.configuration.get("manager") | ||||
|  | ||||
|     if libcalamares.globalstorage.contains("initProvider"): | ||||
|         manager = libcalamares.globalstorage.value("initProvider") | ||||
|  | ||||
|     commands = libcalamares.job.configuration.get("commands") | ||||
|     cmd = commands.get(manager) | ||||
|     services = libcalamares.job.configuration.get("services", []) | ||||
|  | ||||
|     r = manage_services(services, manager, cmd) | ||||
|     if r is not None: | ||||
|         return r | ||||
|  | ||||
| @@ -1,7 +0,0 @@ | ||||
| # SPDX-FileCopyrightText: no | ||||
| # SPDX-License-Identifier: CC0-1.0 | ||||
| --- | ||||
| type:       "job" | ||||
| name:       "services-artix" | ||||
| interface:  "python" | ||||
| script:     "main.py" | ||||
| @@ -1,74 +0,0 @@ | ||||
| # SPDX-FileCopyrightText: no | ||||
| # SPDX-License-Identifier: CC0-1.0 | ||||
| # | ||||
| # openrc services module to modify service runlevels via rc-update in the chroot | ||||
| # | ||||
| # Services can be added (to any runlevel, or multiple runlevels) or deleted. | ||||
| # Handle del with care and only use it if absolutely necessary. | ||||
| # | ||||
| # if a service is listed in the conf but is not present/detected on the target system, | ||||
| # or a runlevel does not exist, it will be ignored and skipped; a warning is logged. | ||||
| # | ||||
| --- | ||||
| # | ||||
| # Which service manager to use, options are: | ||||
| #  - openrc | ||||
| #  - dinit | ||||
| #  - s6 | ||||
| #  - runit | ||||
| # | ||||
|  | ||||
| manager: openrc | ||||
|  | ||||
| commands: | ||||
|     openrc: rc-update | ||||
|     dinit: artix-svc | ||||
|     s6: s6-service | ||||
|     runit: rsm | ||||
|  | ||||
| # There is one key for this module: *services*. Its value is a list of entries. | ||||
| # Each entry has three keys: | ||||
| #   - *name* is the (string) name of the manager service that is being changed. | ||||
| #     Use quotes. You can use any valid initProvider service here (for example, | ||||
| #     "NetworkManager", "cupsd", "lightdm", "gdm", etc.) | ||||
| #   - *action* is the (string) action that you want to perform over the service | ||||
| #     (for example, "enable", "disable"). Please | ||||
| #     ensure that the action can actually run under chroot (otherwise it is | ||||
| #     pointless) | ||||
| #   - *mandatory* is a boolean option, which states whether the change | ||||
| #     must be done successfully. If manager reports an error while changing | ||||
| #     a mandatory entry, the installation will fail. When mandatory is false, | ||||
| #     errors for that manager service are ignored. If mandatory | ||||
| #     is not specified, the default is false. | ||||
| # | ||||
| # The order of operations is the same as the order in which entries | ||||
| # appear in the list | ||||
|  | ||||
| # # This example enables NetworkManager.service (and fails if it can't), | ||||
| # # disables cups.socket (and ignores failure). Then it enables the | ||||
| # # graphical target (e.g. so that SDDM runs for login), and | ||||
| # # finally masks pacman-init (an ArchLinux-only service). | ||||
| # # | ||||
| # services: | ||||
| #   - name: "NetworkManager" | ||||
| #     action: "enable" | ||||
| #     alias: "add" | ||||
| #     target: "default" | ||||
| #     mandatory: true | ||||
| # | ||||
| #   - name: "cupsd" | ||||
| #     action: "disable" | ||||
| #     alias: "del" | ||||
| #     target: "default" | ||||
| #     # The property "mandatory" is taken to be false by default here | ||||
| #     # because it is not specified | ||||
| # | ||||
| #   - name: "sddm" | ||||
| #     action: "enable" | ||||
| #     alias: "add" | ||||
| #     target: "default" | ||||
| #     # The property "mandatory" is taken to be false by default here | ||||
| #     # because it is not specified | ||||
|  | ||||
| services: [] | ||||
|  | ||||
| @@ -1,133 +0,0 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # === This file is part of Calamares - <https://github.com/calamares> === | ||||
| # | ||||
| #   Copyright 2018-2019, Adriaan de Groot <groot@kde.org> | ||||
| #   Copyright 2021, Artoo <artoo@artixlinux.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/>. | ||||
|  | ||||
| import libcalamares | ||||
|  | ||||
| from libcalamares.utils import target_env_call, warning | ||||
| from os.path import exists, join | ||||
|  | ||||
|  | ||||
| import gettext | ||||
| _ = gettext.translation("calamares-python", | ||||
|                         localedir=libcalamares.utils.gettext_path(), | ||||
|                         languages=libcalamares.utils.gettext_languages(), | ||||
|                         fallback=True).gettext | ||||
|  | ||||
|  | ||||
| def pretty_name(): | ||||
|     return _("Configure Dinit services") | ||||
|  | ||||
|  | ||||
| class DinitController: | ||||
|     """ | ||||
|     This is the dinit service controller. | ||||
|     All of its state comes from global storage and the job | ||||
|     configuration at initialization time. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.root = libcalamares.globalstorage.value('rootMountPoint') | ||||
|  | ||||
|         # Translate the entries in the config to the actions passed to sv-helper | ||||
|         self.services = dict() | ||||
|         self.services["enable"] = libcalamares.job.configuration.get('services', []) | ||||
|         self.services["disable"] = libcalamares.job.configuration.get('disable', []) | ||||
|  | ||||
|         self.initdDir = libcalamares.job.configuration['initdDir'] | ||||
|         self.runsvDir = libcalamares.job.configuration['runsvDir'] | ||||
|  | ||||
|  | ||||
|     def make_failure_description(self, state, name): | ||||
|         """ | ||||
|         Returns a generic "could not <foo>" failure message, specialized | ||||
|         for the action @p state and the specific service @p name. | ||||
|         """ | ||||
|         if state == "enable": | ||||
|             description = _("Cannot enable service {name!s}.") | ||||
|         elif state == "disable": | ||||
|             description = _("Cannot disable service {name!s}.") | ||||
|         else: | ||||
|             description = _("Unknown service-action <code>{arg!s}</code> for service {name!s}.") | ||||
|  | ||||
|         return description.format(arg=state, name=name) | ||||
|  | ||||
|  | ||||
|     def update(self, state): | ||||
|         """ | ||||
|         Process each service listed | ||||
|         in services for the given @p state. | ||||
|         """ | ||||
|  | ||||
|         for svc in self.services.get(state, []): | ||||
|             if isinstance(svc, str): | ||||
|                 name = svc | ||||
|                 mandatory = False | ||||
|             else: | ||||
|                 name = svc["name"] | ||||
|                 mandatory = svc.get("mandatory", False) | ||||
|  | ||||
|             service_path = self.root + self.initdDir + "/" + name | ||||
|             runlevel_path = self.root + self.runsvDir | ||||
|             src = self.initdDir + "/" + name | ||||
|             dest = self.runsvDir + "/" | ||||
|  | ||||
|             if state == 'enable': | ||||
|                 cmd = ["ln", "-sv", src, dest] | ||||
|             elif state == 'disable': | ||||
|                 cmd = ["rm", "-rv", dest] | ||||
|  | ||||
|             if exists(service_path): | ||||
|                 if exists(runlevel_path): | ||||
|                     ec = target_env_call(cmd) | ||||
|                     if ec != 0: | ||||
|                         warning("Cannot {} service {}".format(state, name)) | ||||
|                         warning("{} returned error code {!s}".format(cmd, ec)) | ||||
|                         if mandatory: | ||||
|                             title = _("Cannot modify service") | ||||
|                             diagnostic = _("<code>cmd {arg!s}</code> call in chroot returned error code {num!s}.").format(arg=state, num=ec) | ||||
|                             return (title, | ||||
|                                     self.make_failure_description(state, name) + " " + diagnostic | ||||
|                                     ) | ||||
|             else: | ||||
|                 warning("Target service {} does not exist in {}.".format(name, self.initdDir)) | ||||
|                 if mandatory: | ||||
|                     title = _("Target service does not exist") | ||||
|                     diagnostic = _("The path for service {name!s} is <code>{path!s}</code>, which does not exist.").format(name=name, path=service_path) | ||||
|                     return (title, | ||||
|                             self.make_failure_description(state, name) + " " + diagnostic | ||||
|                             ) | ||||
|  | ||||
|  | ||||
|     def run(self): | ||||
|         """Run the controller | ||||
|         """ | ||||
|  | ||||
|         for state in ("enable", "disable"): | ||||
|             r = self.update(state) | ||||
|             if r is not None: | ||||
|                 return r | ||||
|  | ||||
| def run(): | ||||
|     """ | ||||
|     Setup services | ||||
|     """ | ||||
|  | ||||
|     return DinitController().run() | ||||
| @@ -1,5 +0,0 @@ | ||||
| --- | ||||
| type:       "job" | ||||
| name:       "services-dinit" | ||||
| interface:  "python" | ||||
| script:     "main.py" | ||||
| @@ -1,41 +0,0 @@ | ||||
| # runit services module to modify service runlevels via symlinks in the chroot | ||||
| # | ||||
| # Services can be added (to any runlevel, or multiple runlevels) or deleted. | ||||
| # Handle disable with care and only use it if absolutely necessary. | ||||
| # | ||||
| # if a service is listed in the conf but is not present/detected on the target system, | ||||
| # it will be ignored and skipped; a warning is logged. | ||||
| # | ||||
| --- | ||||
| # initdDir: holds the runit service directory location | ||||
| initdDir: /etc/dinit.d | ||||
|  | ||||
| # runsvDir: holds the runlevels directory location | ||||
| runsvDir: /etc/dinit.d/boot.d | ||||
|  | ||||
| # services: a list of entries to **enable** | ||||
| # disable: a list of entries to **disable** | ||||
| # | ||||
| # Each entry has three fields: | ||||
| #   - name: the service name | ||||
| #   - (optional) mandatory: if set to true, a failure to modify | ||||
| #     the service will result in installation failure, rather than just | ||||
| #     a warning. The default is false. | ||||
| # | ||||
| # an entry may also be a single string, which is interpreted | ||||
| # as the name field. | ||||
| # | ||||
| # # Example services and disable settings: | ||||
| # # - add foo1, but it must succeed | ||||
| # # - add foo2 | ||||
| # # - remove foo3 | ||||
| # # - remove foo4 | ||||
| # services: | ||||
| #     - name: foo1 | ||||
| #       mandatory: true | ||||
| #     - name: foo2 | ||||
| # disable: | ||||
| #     - name: foo3 | ||||
| #     - foo4 | ||||
| services: [] | ||||
| disable: [] | ||||
| @@ -1,144 +0,0 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # === This file is part of Calamares - <https://github.com/calamares> === | ||||
| # | ||||
| #   Copyright 2018-2019, Adriaan de Groot <groot@kde.org> | ||||
| #   Copyright 2019, Artoo <artoo@artixlinux.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/>. | ||||
|  | ||||
| import libcalamares | ||||
|  | ||||
| from libcalamares.utils import target_env_call, warning | ||||
| from os.path import exists, join | ||||
|  | ||||
|  | ||||
| import gettext | ||||
| _ = gettext.translation("calamares-python", | ||||
|                         localedir=libcalamares.utils.gettext_path(), | ||||
|                         languages=libcalamares.utils.gettext_languages(), | ||||
|                         fallback=True).gettext | ||||
|  | ||||
|  | ||||
| def pretty_name(): | ||||
|     return _("Configure Runit services") | ||||
|  | ||||
|  | ||||
| class RunitController: | ||||
|     """ | ||||
|     This is the runit service controller. | ||||
|     All of its state comes from global storage and the job | ||||
|     configuration at initialization time. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.root = libcalamares.globalstorage.value('rootMountPoint') | ||||
|  | ||||
|         # Translate the entries in the config to the actions passed to sv-helper | ||||
|         self.services = dict() | ||||
|         self.services["enable"] = libcalamares.job.configuration.get('services', []) | ||||
|         self.services["disable"] = libcalamares.job.configuration.get('disable', []) | ||||
|  | ||||
|         self.svDir = libcalamares.job.configuration['svDir'] | ||||
|         self.runsvDir = libcalamares.job.configuration['runsvDir'] | ||||
|  | ||||
|  | ||||
|     def make_failure_description(self, state, name, runlevel): | ||||
|         """ | ||||
|         Returns a generic "could not <foo>" failure message, specialized | ||||
|         for the action @p state and the specific service @p name in @p runlevel. | ||||
|         """ | ||||
|         if state == "enable": | ||||
|             description = _("Cannot enable service {name!s} to run-level {level!s}.") | ||||
|         elif state == "disable": | ||||
|             description = _("Cannot disable service {name!s} from run-level {level!s}.") | ||||
|         else: | ||||
|             description = _("Unknown service-action <code>{arg!s}</code> for service {name!s} in run-level {level!s}.") | ||||
|  | ||||
|         return description.format(arg=state, name=name, level=runlevel) | ||||
|  | ||||
|  | ||||
|     def update(self, state): | ||||
|         """ | ||||
|         Call sv-helper for each service listed | ||||
|         in services for the given @p state. | ||||
|         """ | ||||
|  | ||||
|         for svc in self.services.get(state, []): | ||||
|             if isinstance(svc, str): | ||||
|                 name = svc | ||||
|                 runlevel = "default" | ||||
|                 mandatory = False | ||||
|             else: | ||||
|                 name = svc["name"] | ||||
|                 runlevel = svc.get("runlevel", "default") | ||||
|                 mandatory = svc.get("mandatory", False) | ||||
|  | ||||
|             service_path = self.root + self.svDir + "/" + name | ||||
|             runlevel_path = self.root + self.runsvDir + "/" + runlevel | ||||
|             src = self.svDir + "/" + name | ||||
|             dest = self.runsvDir + "/" + runlevel + "/" | ||||
|  | ||||
|             if state == 'enable': | ||||
|                 cmd = ["ln", "-sv", src, dest] | ||||
|             elif state == 'disable': | ||||
|                 cmd = ["rm", "-rv", dest] | ||||
|  | ||||
|             if exists(service_path): | ||||
|                 if exists(runlevel_path): | ||||
|                     ec = target_env_call(cmd) | ||||
|                     if ec != 0: | ||||
|                         warning("Cannot {} service {} to {}".format(state, name, runlevel)) | ||||
|                         warning("{} returned error code {!s}".format(cmd, ec)) | ||||
|                         if mandatory: | ||||
|                             title = _("Cannot modify service") | ||||
|                             diagnostic = _("<code>cmd {arg!s}</code> call in chroot returned error code {num!s}.").format(arg=state, num=ec) | ||||
|                             return (title, | ||||
|                                     self.make_failure_description(state, name, runlevel) + " " + diagnostic | ||||
|                                     ) | ||||
|                 else: | ||||
|                     warning("Target runlevel {} does not exist for {}.".format(runlevel, name)) | ||||
|                     if mandatory: | ||||
|                         title = _("Target runlevel does not exist") | ||||
|                         diagnostic = _("The path for runlevel {level!s} is <code>{path!s}</code>, which does not exist.").format(level=runlevel, path=runlevel_path) | ||||
|  | ||||
|                         return (title, | ||||
|                                 self.make_failure_description(state, name, runlevel) + " " + diagnostic | ||||
|                                 ) | ||||
|             else: | ||||
|                 warning("Target service {} does not exist in {}.".format(name, self.svDir)) | ||||
|                 if mandatory: | ||||
|                     title = _("Target service does not exist") | ||||
|                     diagnostic = _("The path for service {name!s} is <code>{path!s}</code>, which does not exist.").format(name=name, path=service_path) | ||||
|                     return (title, | ||||
|                             self.make_failure_description(state, name, runlevel) + " " + diagnostic | ||||
|                             ) | ||||
|  | ||||
|  | ||||
|     def run(self): | ||||
|         """Run the controller | ||||
|         """ | ||||
|  | ||||
|         for state in ("enable", "disable"): | ||||
|             r = self.update(state) | ||||
|             if r is not None: | ||||
|                 return r | ||||
|  | ||||
| def run(): | ||||
|     """ | ||||
|     Setup services | ||||
|     """ | ||||
|  | ||||
|     return RunitController().run() | ||||
| @@ -1,5 +0,0 @@ | ||||
| --- | ||||
| type:       "job" | ||||
| name:       "services-runit" | ||||
| interface:  "python" | ||||
| script:     "main.py" | ||||