diff options
author | Ricardo Wurmus <rekado@elephly.net> | 2019-02-06 13:03:26 +0100 |
---|---|---|
committer | Ricardo Wurmus <rekado@elephly.net> | 2019-02-06 13:03:26 +0100 |
commit | ba88eea2b3a8a33ecd7fc0ec64e3917c6c2fe21d (patch) | |
tree | 75c68e44d3d76440f416552711b1a47ec83e411e /gnu/installer/newt | |
parent | f380f9d55e6757c242acf6c71c4a3ccfcdb066b2 (diff) | |
parent | 4aeb7f34c948f32363f2ae29c6942c6328df758c (diff) | |
download | guix-patches-ba88eea2b3a8a33ecd7fc0ec64e3917c6c2fe21d.tar guix-patches-ba88eea2b3a8a33ecd7fc0ec64e3917c6c2fe21d.tar.gz |
Merge branch 'master' into core-updates
Diffstat (limited to 'gnu/installer/newt')
-rw-r--r-- | gnu/installer/newt/ethernet.scm | 81 | ||||
-rw-r--r-- | gnu/installer/newt/final.scm | 86 | ||||
-rw-r--r-- | gnu/installer/newt/hostname.scm | 26 | ||||
-rw-r--r-- | gnu/installer/newt/keymap.scm | 122 | ||||
-rw-r--r-- | gnu/installer/newt/locale.scm | 217 | ||||
-rw-r--r-- | gnu/installer/newt/menu.scm | 44 | ||||
-rw-r--r-- | gnu/installer/newt/network.scm | 173 | ||||
-rw-r--r-- | gnu/installer/newt/page.scm | 530 | ||||
-rw-r--r-- | gnu/installer/newt/partition.scm | 766 | ||||
-rw-r--r-- | gnu/installer/newt/services.scm | 48 | ||||
-rw-r--r-- | gnu/installer/newt/timezone.scm | 83 | ||||
-rw-r--r-- | gnu/installer/newt/user.scm | 175 | ||||
-rw-r--r-- | gnu/installer/newt/utils.scm | 43 | ||||
-rw-r--r-- | gnu/installer/newt/welcome.scm | 118 | ||||
-rw-r--r-- | gnu/installer/newt/wifi.scm | 243 |
15 files changed, 2755 insertions, 0 deletions
diff --git a/gnu/installer/newt/ethernet.scm b/gnu/installer/newt/ethernet.scm new file mode 100644 index 0000000000..d1f357243b --- /dev/null +++ b/gnu/installer/newt/ethernet.scm @@ -0,0 +1,81 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt ethernet) + #:use-module (gnu installer connman) + #:use-module (gnu installer steps) + #:use-module (gnu installer newt utils) + #:use-module (gnu installer newt page) + #:use-module (guix i18n) + #:use-module (ice-9 format) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (newt) + #:export (run-ethernet-page)) + +(define (ethernet-services) + "Return all the connman services of ethernet type." + (let ((services (connman-services))) + (filter (lambda (service) + (and (string=? (service-type service) "ethernet") + (not (string-null? (service-name service))))) + services))) + +(define (ethernet-service->text service) + "Return a string describing the given ethernet SERVICE." + (let* ((name (service-name service)) + (path (service-path service)) + (full-name (string-append name "-" path)) + (state (service-state service)) + (connected? (or (string=? state "online") + (string=? state "ready")))) + (format #f "~c ~a~%" + (if connected? #\* #\ ) + full-name))) + +(define (connect-ethernet-service service) + "Connect to the given ethernet SERVICE. Display a connecting page while the +connection is pending." + (let* ((service-name (service-name service)) + (form (draw-connecting-page service-name))) + (connman-connect service) + (destroy-form-and-pop form) + service)) + +(define (run-ethernet-page) + (let ((services (ethernet-services))) + (if (null? services) + (begin + (run-error-page + (G_ "No ethernet service available, please try again.") + (G_ "No service")) + (raise + (condition + (&installer-step-abort)))) + (run-listbox-selection-page + #:info-text (G_ "Please select an ethernet network.") + #:title (G_ "Ethernet connection") + #:listbox-items services + #:listbox-item->text ethernet-service->text + #:button-text (G_ "Exit") + #:button-callback-procedure + (lambda _ + (raise + (condition + (&installer-step-abort)))) + #:listbox-callback-procedure connect-ethernet-service)))) diff --git a/gnu/installer/newt/final.scm b/gnu/installer/newt/final.scm new file mode 100644 index 0000000000..645c1e8689 --- /dev/null +++ b/gnu/installer/newt/final.scm @@ -0,0 +1,86 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt final) + #:use-module (gnu installer final) + #:use-module (gnu installer parted) + #:use-module (gnu installer steps) + #:use-module (gnu installer utils) + #:use-module (gnu installer newt page) + #:use-module (gnu installer newt utils) + #:use-module (guix i18n) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (newt) + #:export (run-final-page)) + +(define (run-config-display-page) + (let ((width (%configuration-file-width)) + (height (nearest-exact-integer + (/ (screen-rows) 2)))) + (run-file-textbox-page + #:info-text (G_ "We're now ready to proceed with the installation! \ +A system configuration file has been generated, it is displayed below. \ +The new system will be created from this file once you've pressed OK. \ +This will take a few minutes.") + #:title (G_ "Configuration file") + #:file (%installer-configuration-file) + #:info-textbox-width width + #:file-textbox-width width + #:file-textbox-height height + #:exit-button-callback-procedure + (lambda () + (raise + (condition + (&installer-step-abort))))))) + +(define (run-install-success-page) + (message-window + (G_ "Installation complete") + (G_ "Reboot") + (G_ "Congratulations! Installation is now complete. \ +You may remove the device containing the installation image and \ +press the button to reboot."))) + +(define (run-install-failed-page) + (choice-window + (G_ "Installation failed") + (G_ "Restart installer") + (G_ "Retry system install") + (G_ "The final system installation step failed. You can retry the \ +last step, or restart the installer."))) + +(define (run-install-shell) + (clear-screen) + (newt-suspend) + (let ((install-ok? (install-system))) + (newt-resume) + install-ok?)) + +(define (run-final-page result prev-steps) + (let* ((configuration (format-configuration prev-steps result)) + (user-partitions (result-step result 'partition)) + (install-ok? + (with-mounted-partitions + user-partitions + (configuration->file configuration) + (run-config-display-page) + (run-install-shell)))) + (if install-ok? + (run-install-success-page) + (run-install-failed-page)))) diff --git a/gnu/installer/newt/hostname.scm b/gnu/installer/newt/hostname.scm new file mode 100644 index 0000000000..7783fa6360 --- /dev/null +++ b/gnu/installer/newt/hostname.scm @@ -0,0 +1,26 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt hostname) + #:use-module (gnu installer newt page) + #:use-module (guix i18n) + #:export (run-hostname-page)) + +(define (run-hostname-page) + (run-input-page (G_ "Please enter the system hostname.") + (G_ "Hostname"))) diff --git a/gnu/installer/newt/keymap.scm b/gnu/installer/newt/keymap.scm new file mode 100644 index 0000000000..6211af2bc5 --- /dev/null +++ b/gnu/installer/newt/keymap.scm @@ -0,0 +1,122 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt keymap) + #:use-module (gnu installer keymap) + #:use-module (gnu installer steps) + #:use-module (gnu installer newt page) + #:use-module (guix i18n) + #:use-module (guix records) + #:use-module (newt) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:export (run-keymap-page)) + +(define (run-layout-page layouts layout->text) + (let ((title (G_ "Layout"))) + (run-listbox-selection-page + #:title title + #:info-text (G_ "Please choose your keyboard layout.") + #:listbox-items layouts + #:listbox-item->text layout->text + #:sort-listbox-items? #f + #:button-text (G_ "Exit") + #:button-callback-procedure + (lambda _ + (raise + (condition + (&installer-step-abort))))))) + +(define (run-variant-page variants variant->text) + (let ((title (G_ "Variant"))) + (run-listbox-selection-page + #:title title + #:info-text (G_ "Please choose a variant for your keyboard layout.") + #:listbox-items variants + #:listbox-item->text variant->text + #:sort-listbox-items? #f + #:button-text (G_ "Back") + #:button-callback-procedure + (lambda _ + (raise + (condition + (&installer-step-abort))))))) + +(define (sort-layouts layouts) + "Sort LAYOUTS list by putting the US layout ahead and return it." + (call-with-values + (lambda () + (partition + (lambda (layout) + (let ((name (x11-keymap-layout-name layout))) + (string=? name "us"))) + layouts)) + (cut append <> <>))) + +(define (sort-variants variants) + "Sort VARIANTS list by putting the internation variant ahead and return it." + (call-with-values + (lambda () + (partition + (lambda (variant) + (let ((name (x11-keymap-variant-name variant))) + (string=? name "altgr-intl"))) + variants)) + (cut append <> <>))) + +(define* (run-keymap-page layouts) + "Run a page asking the user to select a keyboard layout and variant. LAYOUTS +is a list of supported X11-KEYMAP-LAYOUT. Return a list of two elements, the +names of the selected keyboard layout and variant." + (define keymap-steps + (list + (installer-step + (id 'layout) + (compute + (lambda _ + (run-layout-page + (sort-layouts layouts) + (lambda (layout) + (x11-keymap-layout-description layout)))))) + ;; Propose the user to select a variant among those supported by the + ;; previously selected layout. + (installer-step + (id 'variant) + (compute + (lambda (result _) + (let* ((layout (result-step result 'layout)) + (variants (x11-keymap-layout-variants layout))) + ;; Return #f if the layout does not have any variant. + (and (not (null? variants)) + (run-variant-page + (sort-variants variants) + (lambda (variant) + (x11-keymap-variant-description + variant)))))))))) + + (define (format-result result) + (let ((layout (x11-keymap-layout-name + (result-step result 'layout))) + (variant (and=> (result-step result 'variant) + (lambda (variant) + (x11-keymap-variant-name variant))))) + (list layout (or variant "")))) + (format-result + (run-installer-steps #:steps keymap-steps))) diff --git a/gnu/installer/newt/locale.scm b/gnu/installer/newt/locale.scm new file mode 100644 index 0000000000..4fa07df81e --- /dev/null +++ b/gnu/installer/newt/locale.scm @@ -0,0 +1,217 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt locale) + #:use-module (gnu installer locale) + #:use-module (gnu installer steps) + #:use-module (gnu installer newt page) + #:use-module (guix i18n) + #:use-module (newt) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (ice-9 match) + #:export (run-locale-page)) + +(define (run-language-page languages language->text) + (let ((title (G_ "Locale language"))) + (run-listbox-selection-page + #:title title + #:info-text (G_ "Choose the locale's language to be used for the \ +installation process. A locale is a regional variant of your language \ +encompassing number, date and currency format, among other details. + +Based on the language you choose, you will possibly be asked to \ +select a locale's territory, codeset and modifier in the next \ +steps. The locale will also be used as the default one for the \ +installed system.") + #:info-textbox-width 70 + #:listbox-items languages + #:listbox-item->text language->text + #:sort-listbox-items? #f + #:button-text (G_ "Exit") + #:button-callback-procedure + (lambda _ + (raise + (condition + (&installer-step-abort))))))) + +(define (run-territory-page territories territory->text) + (let ((title (G_ "Locale location"))) + (run-listbox-selection-page + #:title title + #:info-text (G_ "Choose your locale's location. This is a shortlist of \ +locations based on the language you selected.") + #:listbox-items territories + #:listbox-item->text territory->text + #:button-text (G_ "Back") + #:button-callback-procedure + (lambda _ + (raise + (condition + (&installer-step-abort))))))) + +(define (run-codeset-page codesets) + (let ((title (G_ "Locale codeset"))) + (run-listbox-selection-page + #:title title + #:info-text (G_ "Choose your locale's codeset. If UTF-8 is available, \ + it should be preferred.") + #:listbox-items codesets + #:listbox-item->text identity + #:listbox-default-item "UTF-8" + #:button-text (G_ "Back") + #:button-callback-procedure + (lambda _ + (raise + (condition + (&installer-step-abort))))))) + +(define (run-modifier-page modifiers modifier->text) + (let ((title (G_ "Locale modifier"))) + (run-listbox-selection-page + #:title title + #:info-text (G_ "Choose your locale's modifier. The most frequent \ +modifier is euro. It indicates that you want to use Euro as the currency \ +symbol.") + #:listbox-items modifiers + #:listbox-item->text modifier->text + #:button-text (G_ "Back") + #:button-callback-procedure + (lambda _ + (raise + (condition + (&installer-step-abort))))))) + +(define* (run-locale-page #:key + supported-locales + iso639-languages + iso3166-territories) + "Run a page asking the user to select a locale language and possibly +territory, codeset and modifier. Use SUPPORTED-LOCALES as the list of glibc +available locales. ISO639-LANGUAGES is an association list associating a +locale code to a locale name. ISO3166-TERRITORIES is an association list +associating a territory code with a territory name. The formated locale, under +glibc format is returned." + + (define (break-on-locale-found locales) + "Raise the &installer-step-break condition if LOCALES contains exactly one +element." + (and (= (length locales) 1) + (raise + (condition (&installer-step-break))))) + + (define (filter-locales locales result) + "Filter the list of locale records LOCALES using the RESULT returned by +the installer-steps defined below." + (filter + (lambda (locale) + (and-map identity + `(,(string=? (locale-language locale) + (result-step result 'language)) + ,@(if (result-step-done? result 'territory) + (list (equal? (locale-territory locale) + (result-step result 'territory))) + '()) + ,@(if (result-step-done? result 'codeset) + (list (equal? (locale-codeset locale) + (result-step result 'codeset))) + '()) + ,@(if (result-step-done? result 'modifier) + (list (equal? (locale-modifier locale) + (result-step result 'modifier))) + '())))) + locales)) + + (define (result->locale-string locales result) + "Supposing that LOCALES contains exactly one locale record, turn it into a +glibc locale string and return it." + (match (filter-locales locales result) + ((locale) + (locale->locale-string locale)))) + + (define (sort-languages languages) + "Extract some languages from LANGUAGES list and place them ahead." + (let* ((first-languages '("en")) + (other-languages (lset-difference equal? + languages + first-languages))) + `(,@first-languages ,@other-languages))) + + (define locale-steps + (list + (installer-step + (id 'language) + (compute + (lambda _ + (run-language-page + (sort-languages + (delete-duplicates (map locale-language supported-locales))) + (cut language-code->language-name iso639-languages <>))))) + (installer-step + (id 'territory) + (compute + (lambda (result _) + (let ((locales (filter-locales supported-locales result))) + ;; Stop the process if the language returned by the previous step + ;; is matching one and only one supported locale. + (break-on-locale-found locales) + + ;; Otherwise, ask the user to select a territory among those + ;; supported by the previously selected language. + (run-territory-page + (delete-duplicates (map locale-territory locales)) + (lambda (territory-code) + (if territory-code + (territory-code->territory-name iso3166-territories + territory-code) + (G_ "No location")))))))) + (installer-step + (id 'codeset) + (compute + (lambda (result _) + (let ((locales (filter-locales supported-locales result))) + ;; Same as above but we now have a language and a territory to + ;; narrow down the search of a locale. + (break-on-locale-found locales) + + ;; Otherwise, ask for a codeset. + (run-codeset-page + (delete-duplicates (map locale-codeset locales))))))) + (installer-step + (id 'modifier) + (compute + (lambda (result _) + (let ((locales (filter-locales supported-locales result))) + ;; Same thing with a language, a territory and a codeset this time. + (break-on-locale-found locales) + + ;; Otherwise, ask for a modifier. + (run-modifier-page + (delete-duplicates (map locale-modifier locales)) + (lambda (modifier) + (or modifier (G_ "No modifier")))))))))) + + ;; If run-installer-steps returns locally, it means that the user had to go + ;; through all steps (language, territory, codeset and modifier) to select a + ;; locale. In that case, like if we exited by raising &installer-step-break + ;; condition, turn the result into a glibc locale string and return it. + (result->locale-string + supported-locales + (run-installer-steps #:steps locale-steps))) diff --git a/gnu/installer/newt/menu.scm b/gnu/installer/newt/menu.scm new file mode 100644 index 0000000000..161266a94a --- /dev/null +++ b/gnu/installer/newt/menu.scm @@ -0,0 +1,44 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt menu) + #:use-module (gnu installer steps) + #:use-module (gnu installer newt page) + #:use-module (guix i18n) + #:use-module (newt) + #:export (run-menu-page)) + +(define (run-menu-page steps) + "Run a menu page, asking the user to select where to resume the install +process from." + (define (steps->items steps) + (filter (lambda (step) + (installer-step-description step)) + steps)) + + (run-listbox-selection-page + #:info-text (G_ "Choose where you want to resume the install.\ +You can also abort the installation by pressing the Abort button.") + #:title (G_ "Installation menu") + #:listbox-items (steps->items steps) + #:listbox-item->text installer-step-description + #:sort-listbox-items? #f + #:button-text (G_ "Abort") + #:button-callback-procedure (lambda () + (newt-finish) + (primitive-exit 1)))) diff --git a/gnu/installer/newt/network.scm b/gnu/installer/newt/network.scm new file mode 100644 index 0000000000..f263b7df9d --- /dev/null +++ b/gnu/installer/newt/network.scm @@ -0,0 +1,173 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt network) + #:use-module (gnu installer connman) + #:use-module (gnu installer steps) + #:use-module (gnu installer utils) + #:use-module (gnu installer newt ethernet) + #:use-module (gnu installer newt page) + #:use-module (gnu installer newt wifi) + #:use-module (guix i18n) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-11) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (newt) + #:export (run-network-page)) + +;; Maximum length of a technology name. +(define technology-name-max-length (make-parameter 20)) + +(define (technology->text technology) + "Return a string describing the given TECHNOLOGY." + (let* ((name (technology-name technology)) + (padded-name (string-pad-right name + (technology-name-max-length)))) + (format #f "~a~%" padded-name))) + +(define (run-technology-page) + "Run a page to ask the user which technology shall be used to access +Internet and return the selected technology. For now, only technologies with +\"ethernet\" or \"wifi\" types are supported." + (define (technology-items) + (filter (lambda (technology) + (let ((type (technology-type technology))) + (or + (string=? type "ethernet") + (string=? type "wifi")))) + (connman-technologies))) + + (let ((items (technology-items))) + (if (null? items) + (case (choice-window + (G_ "Internet access") + (G_ "Continue") + (G_ "Exit") + (G_ "The install process requires an internet access, but no \ +network device were found. Do you want to continue anyway?")) + ((1) (raise + (condition + (&installer-step-break)))) + ((2) (raise + (condition + (&installer-step-abort))))) + (run-listbox-selection-page + #:info-text (G_ "The install process requires an internet access.\ + Please select a network device.") + #:title (G_ "Internet access") + #:listbox-items items + #:listbox-item->text technology->text + #:button-text (G_ "Exit") + #:button-callback-procedure + (lambda _ + (raise + (condition + (&installer-step-abort)))))))) + +(define (find-technology-by-type technologies type) + "Find and return a technology with the given TYPE in TECHNOLOGIES list." + (find (lambda (technology) + (string=? (technology-type technology) + type)) + technologies)) + +(define (wait-technology-powered technology) + "Wait and display a progress bar until the given TECHNOLOGY is powered." + (let ((name (technology-name technology)) + (full-value 5)) + (run-scale-page + #:title (G_ "Powering technology") + #:info-text (format #f "Waiting for technology ~a to be powered." name) + #:scale-full-value full-value + #:scale-update-proc + (lambda (value) + (let* ((technologies (connman-technologies)) + (type (technology-type technology)) + (updated-technology + (find-technology-by-type technologies type)) + (technology-powered? updated-technology)) + (sleep 1) + (if technology-powered? + full-value + (+ value 1))))))) + +(define (wait-service-online) + "Display a newt scale until connman detects an Internet access. Do +FULL-VALUE tentatives, spaced by 1 second." + (let* ((full-value 5)) + (run-scale-page + #:title (G_ "Checking connectivity") + #:info-text (G_ "Waiting internet access is established.") + #:scale-full-value full-value + #:scale-update-proc + (lambda (value) + (sleep 1) + (if (connman-online?) + full-value + (+ value 1)))) + (unless (connman-online?) + (run-error-page + (G_ "The selected network does not provide an Internet \ +access, please try again.") + (G_ "Connection error")) + (raise + (condition + (&installer-step-abort)))))) + +(define (run-network-page) + "Run a page to allow the user to configure connman so that it can access the +Internet." + (define network-steps + (list + ;; Ask the user to choose between ethernet and wifi technologies. + (installer-step + (id 'select-technology) + (compute + (lambda _ + (run-technology-page)))) + ;; Enable the previously selected technology. + (installer-step + (id 'power-technology) + (compute + (lambda (result _) + (let ((technology (result-step result 'select-technology))) + (connman-enable-technology technology) + (wait-technology-powered technology))))) + ;; Propose the user to connect to one of the service available for the + ;; previously selected technology. + (installer-step + (id 'connect-service) + (compute + (lambda (result _) + (let* ((technology (result-step result 'select-technology)) + (type (technology-type technology))) + (cond + ((string=? "wifi" type) + (run-wifi-page)) + ((string=? "ethernet" type) + (run-ethernet-page))))))) + ;; Wait for connman status to switch to 'online, which means it can + ;; access Internet. + (installer-step + (id 'wait-online) + (compute (lambda _ + (wait-service-online)))))) + (run-installer-steps + #:steps network-steps + #:rewind-strategy 'start)) diff --git a/gnu/installer/newt/page.scm b/gnu/installer/newt/page.scm new file mode 100644 index 0000000000..edf0b8c999 --- /dev/null +++ b/gnu/installer/newt/page.scm @@ -0,0 +1,530 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt page) + #:use-module (gnu installer utils) + #:use-module (gnu installer newt utils) + #:use-module (guix i18n) + #:use-module (ice-9 match) + #:use-module (ice-9 receive) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:use-module (newt) + #:export (draw-info-page + draw-connecting-page + run-input-page + run-error-page + run-listbox-selection-page + run-scale-page + run-checkbox-tree-page + run-file-textbox-page)) + +;;; Commentary: +;;; +;;; Some helpers around guile-newt to draw or run generic pages. The +;;; difference between 'draw' and 'run' terms comes from newt library. A page +;;; is drawn when the form it contains does not expect any user +;;; interaction. In that case, it is necessary to call (newt-refresh) to force +;;; the page to be displayed. When a form is 'run', it is blocked waiting for +;;; any action from the user (press a button, input some text, ...). +;;; +;;; Code: + +(define (draw-info-page text title) + "Draw an informative page with the given TEXT as content. Set the title of +this page to TITLE." + (let* ((text-box + (make-reflowed-textbox -1 -1 text 40 + #:flags FLAG-BORDER)) + (grid (make-grid 1 1)) + (form (make-form))) + (set-grid-field grid 0 0 GRID-ELEMENT-COMPONENT text-box) + (add-component-to-form form text-box) + (make-wrapped-grid-window grid title) + (draw-form form) + ;; This call is imperative, otherwise the form won't be displayed. See the + ;; explanation in the above commentary. + (newt-refresh) + form)) + +(define (draw-connecting-page service-name) + "Draw a page to indicate a connection in in progress." + (draw-info-page + (format #f (G_ "Connecting to ~a, please wait.") service-name) + (G_ "Connection in progress"))) + +(define* (run-input-page text title + #:key + (allow-empty-input? #f) + (default-text #f) + (input-field-width 40)) + "Run a page to prompt user for an input. The given TEXT will be displayed +above the input field. The page title is set to TITLE. Unless +allow-empty-input? is set to #t, an error page will be displayed if the user +enters an empty input." + (let* ((text-box + (make-reflowed-textbox -1 -1 text + input-field-width + #:flags FLAG-BORDER)) + (grid (make-grid 1 3)) + (input-entry (make-entry -1 -1 20)) + (ok-button (make-button -1 -1 (G_ "OK"))) + (form (make-form))) + + (when default-text + (set-entry-text input-entry default-text)) + + (set-grid-field grid 0 0 GRID-ELEMENT-COMPONENT text-box) + (set-grid-field grid 0 1 GRID-ELEMENT-COMPONENT input-entry + #:pad-top 1) + (set-grid-field grid 0 2 GRID-ELEMENT-COMPONENT ok-button + #:pad-top 1) + + (add-components-to-form form text-box input-entry ok-button) + (make-wrapped-grid-window grid title) + (let ((error-page (lambda () + (run-error-page (G_ "Please enter a non empty input.") + (G_ "Empty input"))))) + (let loop () + (receive (exit-reason argument) + (run-form form) + (let ((input (entry-value input-entry))) + (if (and (not allow-empty-input?) + (eq? exit-reason 'exit-component) + (string=? input "")) + (begin + ;; Display the error page. + (error-page) + ;; Set the focus back to the input input field. + (set-current-component form input-entry) + (loop)) + (begin + (destroy-form-and-pop form) + input)))))))) + +(define (run-error-page text title) + "Run a page to inform the user of an error. The page contains the given TEXT +to explain the error and an \"OK\" button to acknowledge the error. The title +of the page is set to TITLE." + (let* ((text-box + (make-reflowed-textbox -1 -1 text 40 + #:flags FLAG-BORDER)) + (grid (make-grid 1 2)) + (ok-button (make-button -1 -1 "OK")) + (form (make-form))) + + (set-grid-field grid 0 0 GRID-ELEMENT-COMPONENT text-box) + (set-grid-field grid 0 1 GRID-ELEMENT-COMPONENT ok-button + #:pad-top 1) + + ;; Set the background color to red to indicate something went wrong. + (newt-set-color COLORSET-ROOT "white" "red") + (add-components-to-form form text-box ok-button) + (make-wrapped-grid-window grid title) + (run-form form) + ;; Restore the background to its original color. + (newt-set-color COLORSET-ROOT "white" "blue") + (destroy-form-and-pop form))) + +(define* (run-listbox-selection-page #:key + info-text + title + (info-textbox-width 50) + listbox-items + listbox-item->text + (listbox-height 20) + (listbox-default-item #f) + (listbox-allow-multiple? #f) + (sort-listbox-items? #t) + (allow-delete? #f) + (skip-item-procedure? + (const #f)) + button-text + (button-callback-procedure + (const #t)) + (button2-text #f) + (button2-callback-procedure + (const #t)) + (listbox-callback-procedure + identity) + (hotkey-callback-procedure + (const #t))) + "Run a page asking the user to select an item in a listbox. The page +contains, stacked vertically from the top to the bottom, an informative text +set to INFO-TEXT, a listbox and a button. The listbox will be filled with +LISTBOX-ITEMS converted to text by applying the procedure LISTBOX-ITEM->TEXT +on every item. The selected item from LISTBOX-ITEMS is returned. The button +text is set to BUTTON-TEXT and the procedure BUTTON-CALLBACK-PROCEDURE called +when it is pressed. The procedure LISTBOX-CALLBACK-PROCEDURE is called when an +item from the listbox is selected (by pressing the <ENTER> key). + +INFO-TEXTBOX-WIDTH is the width of the textbox where INFO-TEXT will be +displayed. LISTBOX-HEIGHT is the height of the listbox. + +If LISTBOX-DEFAULT-ITEM is set to the value of one of the items in +LISTBOX-ITEMS, it will be selected by default. Otherwise, the first element of +the listbox is selected. + +If LISTBOX-ALLOW-MULTIPLE? is set to #t, multiple items from the listbox can +be selected (using the <SPACE> key). It that case, a list containing the +selected items will be returned. + +If SORT-LISTBOX-ITEMS? is set to #t, the listbox items are sorted using +'string<=' procedure (after being converted to text). + +If ALLOW-DELETE? is #t, the form will return if the <DELETE> key is pressed, +otherwise nothing will happend. + +Each time the listbox current item changes, call SKIP-ITEM-PROCEDURE? with the +current listbox item as argument. If it returns #t, skip the element and jump +to the next/previous one depending on the previous item, otherwise do +nothing." + + (define (fill-listbox listbox items) + "Append the given ITEMS to LISTBOX, once they have been converted to text +with LISTBOX-ITEM->TEXT. Each item appended to the LISTBOX is given a key by +newt. Save this key by returning an association list under the form: + + ((NEWT-LISTBOX-KEY . ITEM) ...) + +where NEWT-LISTBOX-KEY is the key returned by APPEND-ENTRY-TO-LISTBOX, when +ITEM was inserted into LISTBOX." + (map (lambda (item) + (let* ((text (listbox-item->text item)) + (key (append-entry-to-listbox listbox text))) + (cons key item))) + items)) + + (define (sort-listbox-items listbox-items) + "Return LISTBOX-ITEMS sorted using the 'string<=' procedure on the text +corresponding to each item in the list." + (let* ((items (map (lambda (item) + (cons item (listbox-item->text item))) + listbox-items)) + (sorted-items + (sort items (lambda (a b) + (let ((text-a (cdr a)) + (text-b (cdr b))) + (string<= text-a text-b)))))) + (map car sorted-items))) + + ;; Store the last selected listbox item's key. + (define last-listbox-key (make-parameter #f)) + + (define (previous-key keys key) + (let ((index (list-index (cut eq? key <>) keys))) + (and index + (> index 0) + (list-ref keys (- index 1))))) + + (define (next-key keys key) + (let ((index (list-index (cut eq? key <>) keys))) + (and index + (< index (- (length keys) 1)) + (list-ref keys (+ index 1))))) + + (define (set-default-item listbox listbox-keys default-item) + "Set the default item of LISTBOX to DEFAULT-ITEM. LISTBOX-KEYS is the +association list returned by the FILL-LISTBOX procedure. It is used because +the current listbox item has to be selected by key." + (for-each (match-lambda + ((key . item) + (when (equal? item default-item) + (set-current-listbox-entry-by-key listbox key)))) + listbox-keys)) + + (let* ((listbox (make-listbox + -1 -1 + listbox-height + (logior FLAG-SCROLL FLAG-BORDER FLAG-RETURNEXIT + (if listbox-allow-multiple? + FLAG-MULTIPLE + 0)))) + (form (make-form)) + (info-textbox + (make-reflowed-textbox -1 -1 info-text + info-textbox-width + #:flags FLAG-BORDER)) + (button (make-button -1 -1 button-text)) + (button2 (and button2-text + (make-button -1 -1 button2-text))) + (grid (vertically-stacked-grid + GRID-ELEMENT-COMPONENT info-textbox + GRID-ELEMENT-COMPONENT listbox + GRID-ELEMENT-SUBGRID + (apply + horizontal-stacked-grid + GRID-ELEMENT-COMPONENT button + `(,@(if button2 + (list GRID-ELEMENT-COMPONENT button2) + '()))))) + (sorted-items (if sort-listbox-items? + (sort-listbox-items listbox-items) + listbox-items)) + (keys (fill-listbox listbox sorted-items))) + + ;; On every listbox element change, check if we need to skip it. If yes, + ;; depending on the 'last-listbox-key', jump forward or backward. If no, + ;; do nothing. + (add-component-callback + listbox + (lambda (component) + (let* ((current-key (current-listbox-entry listbox)) + (listbox-keys (map car keys)) + (last-key (last-listbox-key)) + (item (assoc-ref keys current-key)) + (prev-key (previous-key listbox-keys current-key)) + (next-key (next-key listbox-keys current-key))) + ;; Update last-listbox-key before a potential call to + ;; set-current-listbox-entry-by-key, because it will immediately + ;; cause this callback to be called for the new entry. + (last-listbox-key current-key) + (when (skip-item-procedure? item) + (when (eq? prev-key last-key) + (if next-key + (set-current-listbox-entry-by-key listbox next-key) + (set-current-listbox-entry-by-key listbox prev-key))) + (when (eq? next-key last-key) + (if prev-key + (set-current-listbox-entry-by-key listbox prev-key) + (set-current-listbox-entry-by-key listbox next-key))))))) + + (when listbox-default-item + (set-default-item listbox keys listbox-default-item)) + + (when allow-delete? + (form-add-hotkey form KEY-DELETE)) + + (add-form-to-grid grid form #t) + (make-wrapped-grid-window grid title) + + (receive (exit-reason argument) + (run-form form) + (dynamic-wind + (const #t) + (lambda () + (case exit-reason + ((exit-component) + (cond + ((components=? argument button) + (button-callback-procedure)) + ((and button2 + (components=? argument button2)) + (button2-callback-procedure)) + ((components=? argument listbox) + (if listbox-allow-multiple? + (let* ((entries (listbox-selection listbox)) + (items (map (lambda (entry) + (assoc-ref keys entry)) + entries))) + (listbox-callback-procedure items)) + (let* ((entry (current-listbox-entry listbox)) + (item (assoc-ref keys entry))) + (listbox-callback-procedure item)))))) + ((exit-hotkey) + (let* ((entry (current-listbox-entry listbox)) + (item (assoc-ref keys entry))) + (hotkey-callback-procedure argument item))))) + (lambda () + (destroy-form-and-pop form)))))) + +(define* (run-scale-page #:key + title + info-text + (info-textbox-width 50) + (scale-width 40) + (scale-full-value 100) + scale-update-proc + (max-scale-update 5)) + "Run a page with a progress bar (called 'scale' in newt). The given +INFO-TEXT is displayed in a textbox above the scale. The width of the textbox +is set to INFO-TEXTBOX-WIDTH. The width of the scale is set to +SCALE-WIDTH. SCALE-FULL-VALUE indicates the value that correspond to 100% of +the scale. + +The procedure SCALE-UPDATE-PROC shall return a new scale +value. SCALE-UPDATE-PROC will be called until the returned value is superior +or equal to SCALE-FULL-VALUE, but no more than MAX-SCALE-UPDATE times. An +error is raised if the MAX-SCALE-UPDATE limit is reached." + (let* ((info-textbox + (make-reflowed-textbox -1 -1 info-text + info-textbox-width + #:flags FLAG-BORDER)) + (scale (make-scale -1 -1 scale-width scale-full-value)) + (grid (vertically-stacked-grid + GRID-ELEMENT-COMPONENT info-textbox + GRID-ELEMENT-COMPONENT scale)) + (form (make-form))) + + (add-form-to-grid grid form #t) + (make-wrapped-grid-window grid title) + + (draw-form form) + ;; This call is imperative, otherwise the form won't be displayed. See the + ;; explanation in the above commentary. + (newt-refresh) + + (dynamic-wind + (const #t) + (lambda () + (let loop ((i max-scale-update) + (last-value 0)) + (let ((value (scale-update-proc last-value))) + (set-scale-value scale value) + ;; Same as above. + (newt-refresh) + (unless (>= value scale-full-value) + (if (> i 0) + (loop (- i 1) value) + (error "Max scale updates reached.")))))) + (lambda () + (destroy-form-and-pop form))))) + +(define* (run-checkbox-tree-page #:key + info-text + title + items + item->text + (info-textbox-width 50) + (checkbox-tree-height 10) + (ok-button-callback-procedure + (const #t)) + (exit-button-callback-procedure + (const #t))) + "Run a page allowing the user to select one or multiple items among ITEMS in +a checkbox list. The page contains vertically stacked from the top to the +bottom, an informative text set to INFO-TEXT, the checkbox list and two +buttons, 'Ok' and 'Exit'. The page title's is set to TITLE. ITEMS are +converted to text using ITEM->TEXT before being displayed in the checkbox +list. + +INFO-TEXTBOX-WIDTH is the width of the textbox where INFO-TEXT will be +displayed. CHECKBOX-TREE-HEIGHT is the height of the checkbox list. + +OK-BUTTON-CALLBACK-PROCEDURE is called when the 'Ok' button is pressed. +EXIT-BUTTON-CALLBACK-PROCEDURE is called when the 'Exit' button is +pressed. + +This procedure returns the list of checked items in the checkbox list among +ITEMS when 'Ok' is pressed." + (define (fill-checkbox-tree checkbox-tree items) + (map + (lambda (item) + (let* ((item-text (item->text item)) + (key (add-entry-to-checkboxtree checkbox-tree item-text 0))) + (cons key item))) + items)) + + (let* ((checkbox-tree + (make-checkboxtree -1 -1 + checkbox-tree-height + FLAG-BORDER)) + (info-textbox + (make-reflowed-textbox -1 -1 info-text + info-textbox-width + #:flags FLAG-BORDER)) + (ok-button (make-button -1 -1 (G_ "OK"))) + (exit-button (make-button -1 -1 (G_ "Exit"))) + (grid (vertically-stacked-grid + GRID-ELEMENT-COMPONENT info-textbox + GRID-ELEMENT-COMPONENT checkbox-tree + GRID-ELEMENT-SUBGRID + (horizontal-stacked-grid + GRID-ELEMENT-COMPONENT ok-button + GRID-ELEMENT-COMPONENT exit-button))) + (keys (fill-checkbox-tree checkbox-tree items)) + (form (make-form))) + + (add-form-to-grid grid form #t) + (make-wrapped-grid-window grid title) + + (receive (exit-reason argument) + (run-form form) + (dynamic-wind + (const #t) + (lambda () + (case exit-reason + ((exit-component) + (cond + ((components=? argument ok-button) + (let* ((entries (current-checkbox-selection checkbox-tree)) + (current-items (map (lambda (entry) + (assoc-ref keys entry)) + entries))) + (ok-button-callback-procedure) + current-items)) + ((components=? argument exit-button) + (exit-button-callback-procedure)))))) + (lambda () + (destroy-form-and-pop form)))))) + +(define* (run-file-textbox-page #:key + info-text + title + file + (info-textbox-width 50) + (file-textbox-width 50) + (file-textbox-height 30) + (exit-button? #t) + (ok-button-callback-procedure + (const #t)) + (exit-button-callback-procedure + (const #t))) + (let* ((info-textbox + (make-reflowed-textbox -1 -1 info-text + info-textbox-width + #:flags FLAG-BORDER)) + (file-text (read-all file)) + (file-textbox + (make-textbox -1 -1 + file-textbox-width + file-textbox-height + (logior FLAG-SCROLL FLAG-BORDER))) + (ok-button (make-button -1 -1 (G_ "OK"))) + (exit-button (make-button -1 -1 (G_ "Exit"))) + (grid (vertically-stacked-grid + GRID-ELEMENT-COMPONENT info-textbox + GRID-ELEMENT-COMPONENT file-textbox + GRID-ELEMENT-SUBGRID + (apply + horizontal-stacked-grid + GRID-ELEMENT-COMPONENT ok-button + `(,@(if exit-button? + (list GRID-ELEMENT-COMPONENT exit-button) + '()))))) + (form (make-form))) + + (set-textbox-text file-textbox file-text) + (add-form-to-grid grid form #t) + (make-wrapped-grid-window grid title) + + (receive (exit-reason argument) + (run-form form) + (dynamic-wind + (const #t) + (lambda () + (case exit-reason + ((exit-component) + (cond + ((components=? argument ok-button) + (ok-button-callback-procedure)) + ((and exit-button? + (components=? argument exit-button)) + (exit-button-callback-procedure)))))) + (lambda () + (destroy-form-and-pop form)))))) diff --git a/gnu/installer/newt/partition.scm b/gnu/installer/newt/partition.scm new file mode 100644 index 0000000000..d4c91edc66 --- /dev/null +++ b/gnu/installer/newt/partition.scm @@ -0,0 +1,766 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt partition) + #:use-module (gnu installer parted) + #:use-module (gnu installer steps) + #:use-module (gnu installer utils) + #:use-module (gnu installer newt page) + #:use-module (gnu installer newt utils) + #:use-module (guix i18n) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (newt) + #:use-module (parted) + #:export (run-partioning-page)) + +(define (button-exit-action) + "Raise the &installer-step-abort condition." + (raise + (condition + (&installer-step-abort)))) + +(define (run-scheme-page) + "Run a page asking the user for a partitioning scheme." + (let* ((items + '((root . "Everything is one partition") + (root-home . "Separate /home partition"))) + (result (run-listbox-selection-page + #:info-text (G_ "Please select a partitioning scheme.") + #:title (G_ "Partition scheme") + #:listbox-items items + #:listbox-item->text cdr + #:button-text (G_ "Exit") + #:button-callback-procedure button-exit-action))) + (car result))) + +(define (draw-formatting-page) + "Draw a page to indicate partitions are being formated." + (draw-info-page + (format #f (G_ "Partition formatting is in progress, please wait.")) + (G_ "Preparing partitions"))) + +(define (run-device-page devices) + "Run a page asking the user to select a device among those in the given +DEVICES list." + (define (device-items) + (map (lambda (device) + `(,device . ,(device-description device))) + devices)) + + (let* ((result (run-listbox-selection-page + #:info-text (G_ "Please select a disk.") + #:title (G_ "Disk") + #:listbox-items (device-items) + #:listbox-item->text cdr + #:button-text (G_ "Exit") + #:button-callback-procedure button-exit-action)) + (device (car result))) + device)) + +(define (run-label-page button-text button-callback) + "Run a page asking the user to select a partition table label." + (run-listbox-selection-page + #:info-text (G_ "Select a new partition table type. \ +Be careful, all data on the disk will be lost.") + #:title (G_ "Partition table") + #:listbox-items '("msdos" "gpt") + #:listbox-item->text identity + #:button-text button-text + #:button-callback-procedure button-callback)) + +(define (run-type-page partition) + "Run a page asking the user to select a partition type." + (let* ((disk (partition-disk partition)) + (partitions (disk-partitions disk)) + (other-extended-partitions? + (any extended-partition? partitions)) + (items + `(normal ,@(if other-extended-partitions? + '() + '(extended))))) + (run-listbox-selection-page + #:info-text (G_ "Please select a partition type.") + #:title (G_ "Partition type") + #:listbox-items items + #:listbox-item->text symbol->string + #:sort-listbox-items? #f + #:button-text (G_ "Exit") + #:button-callback-procedure button-exit-action))) + +(define (run-fs-type-page) + "Run a page asking the user to select a file-system type." + (run-listbox-selection-page + #:info-text (G_ "Please select the file-system type for this partition.") + #:title (G_ "File-system type") + #:listbox-items '(ext4 btrfs fat32 swap) + #:listbox-item->text user-fs-type-name + #:sort-listbox-items? #f + #:button-text (G_ "Exit") + #:button-callback-procedure button-exit-action)) + +(define (inform-can-create-partition? user-partition) + "Return #t if it is possible to create USER-PARTITION. This is determined by +calling CAN-CREATE-PARTITION? procedure. If an exception is raised, catch it +an inform the user with an appropriate error-page and return #f." + (guard (c ((max-primary-exceeded? c) + (run-error-page + (G_ "Primary partitions count exceeded.") + (G_ "Creation error")) + #f) + ((extended-creation-error? c) + (run-error-page + (G_ "Extended partition creation error.") + (G_ "Creation error")) + #f) + ((logical-creation-error? c) + (run-error-page + (G_ "Logical partition creation error.") + (G_ "Creation error")) + #f)) + (can-create-partition? user-partition))) + +(define (prompt-luks-passwords user-partitions) + "Prompt for the luks passwords of the encrypted partitions in +USER-PARTITIONS list. Return this list with password fields filled-in." + (map (lambda (user-part) + (let* ((crypt-label (user-partition-crypt-label user-part)) + (file-name (user-partition-file-name user-part)) + (password-page + (lambda () + (run-input-page + (format #f (G_ "Please enter the password for the \ +encryption of partition ~a (label: ~a).") file-name crypt-label) + (G_ "Password required")))) + (password-confirm-page + (lambda () + (run-input-page + (format #f (G_ "Please confirm the password for the \ +encryption of partition ~a (label: ~a).") file-name crypt-label) + (G_ "Password confirmation required"))))) + (if crypt-label + (let loop () + (let ((password (password-page)) + (confirmation (password-confirm-page))) + (if (string=? password confirmation) + (user-partition + (inherit user-part) + (crypt-password password)) + (begin + (run-error-page + (G_ "Password mismatch, please try again.") + (G_ "Password error")) + (loop))))) + user-part))) + user-partitions)) + +(define* (run-partition-page target-user-partition + #:key + (default-item #f)) + "Run a page allowing the user to edit the given TARGET-USER-PARTITION +record. If the argument DEFAULT-ITEM is passed, use it to select the current +listbox item. This is used to avoid the focus to switch back to the first +listbox entry while calling this procedure recursively." + + (define (numeric-size device size) + "Parse the given SIZE on DEVICE and return it." + (call-with-values + (lambda () + (unit-parse size device)) + (lambda (value range) + value))) + + (define (numeric-size-range device size) + "Parse the given SIZE on DEVICE and return the associated RANGE." + (call-with-values + (lambda () + (unit-parse size device)) + (lambda (value range) + range))) + + (define* (fill-user-partition-geom user-part + #:key + device (size #f) start end) + "Return the given USER-PART with the START, END and SIZE fields set to the +eponym arguments. Use UNIT-FORMAT-CUSTOM to format START and END arguments as +sectors on DEVICE." + (user-partition + (inherit user-part) + (size size) + (start (unit-format-custom device start UNIT-SECTOR)) + (end (unit-format-custom device end UNIT-SECTOR)))) + + (define (apply-user-partition-changes user-part) + "Set the name, file-system type and boot flag on the partition specified +by USER-PART, if it is applicable for the partition type." + (let* ((partition (user-partition-parted-object user-part)) + (disk (partition-disk partition)) + (disk-type (disk-disk-type disk)) + (device (disk-device disk)) + (has-name? (disk-type-check-feature + disk-type + DISK-TYPE-FEATURE-PARTITION-NAME)) + (name (user-partition-name user-part)) + (fs-type (filesystem-type-get + (user-fs-type-name + (user-partition-fs-type user-part)))) + (bootable? (user-partition-bootable? user-part)) + (esp? (user-partition-esp? user-part)) + (flag-bootable? + (partition-is-flag-available? partition PARTITION-FLAG-BOOT)) + (flag-esp? + (partition-is-flag-available? partition PARTITION-FLAG-ESP))) + (when (and has-name? name) + (partition-set-name partition name)) + (partition-set-system partition fs-type) + (when flag-bootable? + (partition-set-flag partition + PARTITION-FLAG-BOOT + (if bootable? 1 0))) + (when flag-esp? + (partition-set-flag partition + PARTITION-FLAG-ESP + (if esp? 1 0))) + #t)) + + (define (listbox-action listbox-item) + (let* ((item (car listbox-item)) + (partition (user-partition-parted-object + target-user-partition)) + (disk (partition-disk partition)) + (device (disk-device disk))) + (list + item + (case item + ((name) + (let* ((old-name (user-partition-name target-user-partition)) + (name + (run-input-page (G_ "Please enter the partition gpt name.") + (G_ "Partition name") + #:default-text old-name))) + (user-partition + (inherit target-user-partition) + (name name)))) + ((type) + (let ((new-type (run-type-page partition))) + (user-partition + (inherit target-user-partition) + (type new-type)))) + ((bootable) + (user-partition + (inherit target-user-partition) + (bootable? (not (user-partition-bootable? + target-user-partition))))) + ((esp?) + (let ((new-esp? (not (user-partition-esp? + target-user-partition)))) + (user-partition + (inherit target-user-partition) + (esp? new-esp?) + (mount-point (if new-esp? + (default-esp-mount-point) + ""))))) + ((crypt-label) + (let* ((label (user-partition-crypt-label + target-user-partition)) + (new-label + (and (not label) + (run-input-page + (G_ "Please enter the encrypted label") + (G_ "Encryption label"))))) + (user-partition + (inherit target-user-partition) + (need-formatting? #t) + (crypt-label new-label)))) + ((need-formatting?) + (user-partition + (inherit target-user-partition) + (need-formatting? + (not (user-partition-need-formatting? + target-user-partition))))) + ((size) + (let* ((old-size (user-partition-size target-user-partition)) + (max-size-value (partition-length partition)) + (max-size (unit-format device max-size-value)) + (start (partition-start partition)) + (size (run-input-page + (format #f (G_ "Please enter the size of the partition.\ + The maximum size is ~a.") max-size) + (G_ "Partition size") + #:default-text (or old-size max-size))) + (size-percentage (read-percentage size)) + (size-value (if size-percentage + (nearest-exact-integer + (/ (* max-size-value size-percentage) + 100)) + (numeric-size device size))) + (end (and size-value + (+ start size-value))) + (size-range (numeric-size-range device size)) + (size-range-ok? (and size-range + (< (+ start + (geometry-start size-range)) + (partition-end partition))))) + (cond + ((and size-percentage (> size-percentage 100)) + (run-error-page + (G_ "The percentage can not be superior to 100.") + (G_ "Size error")) + target-user-partition) + ((not size-value) + (run-error-page + (G_ "The requested size is incorrectly formatted, or too large.") + (G_ "Size error")) + target-user-partition) + ((not (or size-percentage size-range-ok?)) + (run-error-page + (G_ "The request size is superior to the maximum size.") + (G_ "Size error")) + target-user-partition) + (else + (fill-user-partition-geom target-user-partition + #:device device + #:size size + #:start start + #:end end))))) + ((fs-type) + (let ((fs-type (run-fs-type-page))) + (user-partition + (inherit target-user-partition) + (fs-type fs-type)))) + ((mount-point) + (let* ((old-mount (or (user-partition-mount-point + target-user-partition) + "")) + (mount + (run-input-page + (G_ "Please enter the desired mounting point for this \ +partition. Leave this field empty if you don't want to set a mounting point.") + (G_ "Mounting point") + #:default-text old-mount + #:allow-empty-input? #t))) + (user-partition + (inherit target-user-partition) + (mount-point (and (not (string=? mount "")) + mount))))))))) + + (define (button-action) + (let* ((partition (user-partition-parted-object + target-user-partition)) + (prev-part (partition-prev partition)) + (disk (partition-disk partition)) + (device (disk-device disk)) + (creation? (freespace-partition? partition)) + (start (partition-start partition)) + (end (partition-end partition)) + (new-user-partition + (if (user-partition-start target-user-partition) + target-user-partition + (fill-user-partition-geom target-user-partition + #:device device + #:start start + #:end end)))) + ;; It the backend PARTITION has free-space type, it means we are + ;; creating a new partition, otherwise, we are editing an already + ;; existing PARTITION. + (if creation? + (let* ((ok-create-partition? + (inform-can-create-partition? new-user-partition)) + (new-partition + (and ok-create-partition? + (mkpart disk + new-user-partition + #:previous-partition prev-part)))) + (and new-partition + (user-partition + (inherit new-user-partition) + (need-formatting? #t) + (file-name (partition-get-path new-partition)) + (disk-file-name (device-path device)) + (parted-object new-partition)))) + (and (apply-user-partition-changes new-user-partition) + new-user-partition)))) + + (let* ((items (user-partition-description target-user-partition)) + (partition (user-partition-parted-object + target-user-partition)) + (disk (partition-disk partition)) + (device (disk-device disk)) + (file-name (device-path device)) + (number-str (partition-print-number partition)) + (type (user-partition-type target-user-partition)) + (type-str (symbol->string type)) + (start (unit-format device (partition-start partition))) + (creation? (freespace-partition? partition)) + (default-item (and default-item + (find (lambda (item) + (eq? (car item) default-item)) + items))) + (result + (run-listbox-selection-page + #:info-text + (if creation? + (G_ (format #f "Creating ~a partition starting at ~a of ~a." + type-str start file-name)) + (G_ (format #f "You are currently editing partition ~a." + number-str))) + #:title (if creation? + (G_ "Partition creation") + (G_ "Partition edit")) + #:listbox-items items + #:listbox-item->text cdr + #:sort-listbox-items? #f + #:listbox-default-item default-item + #:button-text (G_ "OK") + #:listbox-callback-procedure listbox-action + #:button-callback-procedure button-action))) + (match result + ((item new-user-partition) + (run-partition-page new-user-partition + #:default-item item)) + (else result)))) + +(define* (run-disk-page disks + #:optional (user-partitions '()) + #:key (guided? #f)) + "Run a page allowing to edit the partition tables of the given DISKS. If +specified, USER-PARTITIONS is a list of <user-partition> records associated to +the partitions on DISKS." + + (define (other-logical-partitions? partitions) + "Return #t if at least one of the partition in PARTITIONS list is a +logical partition, return #f otherwise." + (any logical-partition? partitions)) + + (define (other-non-logical-partitions? partitions) + "Return #t is at least one of the partitions in PARTITIONS list is not a +logical partition, return #f otherwise." + (let ((non-logical-partitions + (remove logical-partition? partitions))) + (or (any normal-partition? non-logical-partitions) + (any freespace-partition? non-logical-partitions)))) + + (define (add-tree-symbols partitions descriptions) + "Concatenate tree symbols to the given DESCRIPTIONS list and return +it. The PARTITIONS list is the list of partitions described in +DESCRIPTIONS. The tree symbols are used to indicate the partition's disk and +for logical partitions, the extended partition which includes them." + (match descriptions + (() '()) + ((description . rest-descriptions) + (match partitions + ((partition . rest-partitions) + (if (null? rest-descriptions) + (list (if (logical-partition? partition) + (string-append " ┗━ " description) + (string-append "┗━ " description))) + (cons (cond + ((extended-partition? partition) + (if (other-non-logical-partitions? rest-partitions) + (string-append "┣┳ " description) + (string-append "┗┳ " description))) + ((logical-partition? partition) + (if (other-logical-partitions? rest-partitions) + (if (other-non-logical-partitions? rest-partitions) + (string-append "┃┣━ " description) + (string-append " ┣━ " description)) + (if (other-non-logical-partitions? rest-partitions) + (string-append "┃┗━ " description) + (string-append " ┗━ " description)))) + (else + (string-append "┣━ " description))) + (add-tree-symbols rest-partitions + rest-descriptions)))))))) + + (define (skip-item? item) + (eq? (car item) 'skip)) + + (define (disk-items) + "Return the list of strings describing DISKS." + (let loop ((disks disks)) + (match disks + (() '()) + ((disk . rest) + (let* ((device (disk-device disk)) + (partitions (disk-partitions disk)) + (partitions* + (filter-map + (lambda (partition) + (and (not (metadata-partition? partition)) + (not (small-freespace-partition? device + partition)) + partition)) + partitions)) + (descriptions (add-tree-symbols + partitions* + (partitions-descriptions partitions* + user-partitions))) + (partition-items (map cons partitions* descriptions))) + (append + `((,disk . ,(device-description device disk)) + ,@partition-items + ,@(if (null? rest) + '() + '((skip . "")))) + (loop rest))))))) + + (define (remove-user-partition-by-partition user-partitions partition) + "Return the USER-PARTITIONS list with the record with the given PARTITION +object removed. If PARTITION is an extended partition, also remove all logical +partitions from USER-PARTITIONS." + (remove (lambda (p) + (let ((cur-partition (user-partition-parted-object p))) + (or (equal? cur-partition partition) + (and (extended-partition? partition) + (logical-partition? cur-partition))))) + user-partitions)) + + (define (remove-user-partition-by-disk user-partitions disk) + "Return the USER-PARTITIONS list with the <user-partition> records located +on given DISK removed." + (remove (lambda (p) + (let* ((partition (user-partition-parted-object p)) + (cur-disk (partition-disk partition))) + (equal? cur-disk disk))) + user-partitions)) + + (define (update-user-partitions user-partitions new-user-partition) + "Update or insert NEW-USER-PARTITION record in USER-PARTITIONS list +depending if one of the <user-partition> record in USER-PARTITIONS has the +same PARTITION object as NEW-USER-PARTITION." + (let* ((partition (user-partition-parted-object new-user-partition)) + (user-partitions* + (remove-user-partition-by-partition user-partitions + partition))) + (cons new-user-partition user-partitions*))) + + (define (button-ok-action) + "Commit the modifications to all DISKS and return #t." + (for-each (lambda (disk) + (disk-commit disk)) + disks) + #t) + + (define (listbox-action listbox-item) + "A disk or a partition has been selected. If it's a disk, ask for a label +to create a new partition table. If it is a partition, propose the user to +edit it." + (let ((item (car listbox-item))) + (cond + ((disk? item) + (let ((label (run-label-page (G_ "Back") (const #f)))) + (if label + (let* ((device (disk-device item)) + (new-disk (mklabel device label)) + (commit-new-disk (disk-commit new-disk)) + (other-disks (remove (lambda (disk) + (equal? disk item)) + disks)) + (new-user-partitions + (remove-user-partition-by-disk user-partitions item))) + (disk-destroy item) + `((disks . ,(cons new-disk other-disks)) + (user-partitions . ,new-user-partitions))) + `((disks . ,disks) + (user-partitions . ,user-partitions))))) + ((partition? item) + (let* ((partition item) + (disk (partition-disk partition)) + (device (disk-device disk)) + (existing-user-partition + (find-user-partition-by-parted-object user-partitions + partition)) + (edit-user-partition + (or existing-user-partition + (partition->user-partition partition)))) + `((disks . ,disks) + (user-partitions . ,user-partitions) + (edit-user-partition . ,edit-user-partition))))))) + + (define (hotkey-action key listbox-item) + "The DELETE key has been pressed on a disk or a partition item." + (let ((item (car listbox-item)) + (default-result + `((disks . ,disks) + (user-partitions . ,user-partitions)))) + (cond + ((disk? item) + (let* ((device (disk-device item)) + (file-name (device-path device)) + (info-text + (format #f (G_ "Are you sure you want to delete everything on disk ~a?") + file-name)) + (result (choice-window (G_ "Delete disk") + (G_ "OK") + (G_ "Exit") + info-text))) + (case result + ((1) + (disk-delete-all item) + `((disks . ,disks) + (user-partitions + . ,(remove-user-partition-by-disk user-partitions item)))) + (else + default-result)))) + ((partition? item) + (if (freespace-partition? item) + (run-error-page (G_ "You cannot delete a free space area.") + (G_ "Delete partition")) + (let* ((disk (partition-disk item)) + (number-str (partition-print-number item)) + (info-text + (format #f (G_ "Are you sure you want to delete partition ~a?") + number-str)) + (result (choice-window (G_ "Delete partition") + (G_ "OK") + (G_ "Exit") + info-text))) + (case result + ((1) + (let ((new-user-partitions + (remove-user-partition-by-partition user-partitions + item))) + (disk-delete-partition disk item) + `((disks . ,disks) + (user-partitions . ,new-user-partitions)))) + (else + default-result)))))))) + + (let* ((info-text (G_ "You can change a disk's partition table by \ +selecting it and pressing ENTER. You can also edit a partition by selecting it \ +and pressing ENTER, or remove it by pressing DELETE. To create a new \ +partition, select a free space area and press ENTER. + +At least one partition must have its mounting point set to '/'.")) + (guided-info-text (format #f (G_ "This is the proposed \ +partitioning. It is still possible to edit it or to go back to install menu \ +by pressing the Exit button.~%~%"))) + (result + (run-listbox-selection-page + #:info-text (if guided? + (string-append guided-info-text info-text) + info-text) + + #:title (if guided? + (G_ "Guided partitioning") + (G_ "Manual partitioning")) + #:info-textbox-width 70 + #:listbox-items (disk-items) + #:listbox-item->text cdr + #:sort-listbox-items? #f + #:skip-item-procedure? skip-item? + #:allow-delete? #t + #:button-text (G_ "OK") + #:button-callback-procedure button-ok-action + #:button2-text (G_ "Exit") + #:button2-callback-procedure button-exit-action + #:listbox-callback-procedure listbox-action + #:hotkey-callback-procedure hotkey-action))) + (if (eq? result #t) + (let ((user-partitions-ok? + (guard + (c ((no-root-mount-point? c) + (run-error-page + (G_ "No root mount point found.") + (G_ "Missing mount point")) + #f)) + (check-user-partitions user-partitions)))) + (if user-partitions-ok? + (begin + (for-each (cut disk-destroy <>) disks) + user-partitions) + (run-disk-page disks user-partitions + #:guided? guided?))) + (let* ((result-disks (assoc-ref result 'disks)) + (result-user-partitions (assoc-ref result + 'user-partitions)) + (edit-user-partition (assoc-ref result + 'edit-user-partition)) + (can-create-partition? + (and edit-user-partition + (inform-can-create-partition? edit-user-partition))) + (new-user-partition (and edit-user-partition + can-create-partition? + (run-partition-page + edit-user-partition))) + (new-user-partitions + (if new-user-partition + (update-user-partitions result-user-partitions + new-user-partition) + result-user-partitions))) + (run-disk-page result-disks new-user-partitions + #:guided? guided?))))) + +(define (run-partioning-page) + "Run a page asking the user for a partitioning method." + (define (run-page devices) + (let* ((items + '((entire . "Guided - using the entire disk") + (entire-encrypted . "Guided - using the entire disk with encryption") + (manual . "Manual"))) + (result (run-listbox-selection-page + #:info-text (G_ "Please select a partitioning method.") + #:title (G_ "Partitioning method") + #:listbox-items items + #:listbox-item->text cdr + #:button-text (G_ "Exit") + #:button-callback-procedure button-exit-action)) + (method (car result))) + (cond + ((or (eq? method 'entire) + (eq? method 'entire-encrypted)) + (let* ((device (run-device-page devices)) + (disk-type (disk-probe device)) + (disk (if disk-type + (disk-new device) + (let* ((label (run-label-page + (G_ "Exit") + button-exit-action)) + (disk (mklabel device label))) + (disk-commit disk) + disk))) + (scheme (symbol-append method '- (run-scheme-page))) + (user-partitions (append + (auto-partition disk #:scheme scheme) + (create-special-user-partitions + (disk-partitions disk))))) + (run-disk-page (list disk) user-partitions + #:guided? #t))) + ((eq? method 'manual) + (let* ((disks (filter-map disk-new devices)) + (user-partitions (append-map + create-special-user-partitions + (map disk-partitions disks))) + (result-user-partitions (run-disk-page disks + user-partitions))) + result-user-partitions))))) + + (init-parted) + (let* ((non-install-devices (non-install-devices)) + (user-partitions (run-page non-install-devices)) + (user-partitions-with-pass (prompt-luks-passwords + user-partitions)) + (form (draw-formatting-page))) + ;; Make sure the disks are not in use before proceeding to formatting. + (free-parted non-install-devices) + (format-user-partitions user-partitions-with-pass) + (destroy-form-and-pop form) + user-partitions)) diff --git a/gnu/installer/newt/services.scm b/gnu/installer/newt/services.scm new file mode 100644 index 0000000000..6bcb6244ae --- /dev/null +++ b/gnu/installer/newt/services.scm @@ -0,0 +1,48 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt services) + #:use-module (gnu installer services) + #:use-module (gnu installer steps) + #:use-module (gnu installer newt page) + #:use-module (gnu installer newt utils) + #:use-module (guix i18n) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (newt) + #:export (run-services-page)) + +(define (run-desktop-environments-cbt-page) + "Run a page allowing the user to choose between various desktop +environments." + (run-checkbox-tree-page + #:info-text (G_ "Please select the desktop(s) environment(s) you wish to \ +install. If you select multiple desktops environments, we will be able to \ +choose the one to use on the log-in screen with F1.") + #:title (G_ "Desktop environment") + #:items %desktop-environments + #:item->text desktop-environment-name + #:checkbox-tree-height 5 + #:exit-button-callback-procedure + (lambda () + (raise + (condition + (&installer-step-abort)))))) + +(define (run-services-page) + (run-desktop-environments-cbt-page)) diff --git a/gnu/installer/newt/timezone.scm b/gnu/installer/newt/timezone.scm new file mode 100644 index 0000000000..6c96ee55b1 --- /dev/null +++ b/gnu/installer/newt/timezone.scm @@ -0,0 +1,83 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt timezone) + #:use-module (gnu installer steps) + #:use-module (gnu installer timezone) + #:use-module (gnu installer newt page) + #:use-module (guix i18n) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (ice-9 match) + #:use-module (ice-9 receive) + #:use-module (newt) + #:export (run-timezone-page)) + +;; Heigth of the listbox displaying timezones. +(define timezone-listbox-heigth (make-parameter 20)) + +;; Information textbox width. +(define info-textbox-width (make-parameter 40)) + +(define (fill-timezones listbox timezones) + "Fill the given LISTBOX with TIMEZONES. Return an association list +correlating listbox keys with timezones." + (map (lambda (timezone) + (let ((key (append-entry-to-listbox listbox timezone))) + (cons key timezone))) + timezones)) + +(define (run-timezone-page zonetab) + "Run a page displaying available timezones, grouped by regions. The user is +invited to select a timezone. The selected timezone, under Posix format is +returned." + (define (all-but-last list) + (reverse (cdr (reverse list)))) + + (define (run-page timezone-tree) + (define (loop path) + (let ((timezones (locate-childrens timezone-tree path))) + (run-listbox-selection-page + #:title (G_ "Timezone") + #:info-text (G_ "Please select a timezone.") + #:listbox-items timezones + #:listbox-item->text identity + #:button-text (if (null? path) + (G_ "Exit") + (G_ "Back")) + #:button-callback-procedure + (if (null? path) + (lambda _ + (raise + (condition + (&installer-step-abort)))) + (lambda _ + (loop (all-but-last path)))) + #:listbox-callback-procedure + (lambda (timezone) + (let* ((timezone* (append path (list timezone))) + (tz (timezone->posix-tz timezone*))) + (if (timezone-has-child? timezone-tree timezone*) + (loop timezone*) + tz)))))) + (loop '())) + + (let ((timezone-tree (zonetab->timezone-tree zonetab))) + (run-page timezone-tree))) diff --git a/gnu/installer/newt/user.scm b/gnu/installer/newt/user.scm new file mode 100644 index 0000000000..59b1913cfc --- /dev/null +++ b/gnu/installer/newt/user.scm @@ -0,0 +1,175 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt user) + #:use-module (gnu installer user) + #:use-module (gnu installer newt page) + #:use-module (gnu installer newt utils) + #:use-module (guix i18n) + #:use-module (newt) + #:use-module (ice-9 match) + #:use-module (ice-9 receive) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:export (run-user-page)) + +(define (run-user-add-page) + (define (pad-label label) + (string-pad-right label 20)) + + (let* ((label-name + (make-label -1 -1 (pad-label (G_ "Name")))) + (label-home-directory + (make-label -1 -1 (pad-label (G_ "Home directory")))) + (entry-width 30) + (entry-name (make-entry -1 -1 entry-width)) + (entry-home-directory (make-entry -1 -1 entry-width)) + (entry-grid (make-grid 2 2)) + (button-grid (make-grid 1 1)) + (ok-button (make-button -1 -1 (G_ "OK"))) + (grid (make-grid 1 2)) + (title (G_ "User creation")) + (set-entry-grid-field + (cut set-grid-field entry-grid <> <> GRID-ELEMENT-COMPONENT <>)) + (form (make-form))) + + (set-entry-grid-field 0 0 label-name) + (set-entry-grid-field 1 0 entry-name) + (set-entry-grid-field 0 1 label-home-directory) + (set-entry-grid-field 1 1 entry-home-directory) + + (set-grid-field button-grid 0 0 GRID-ELEMENT-COMPONENT ok-button) + + (add-component-callback + entry-name + (lambda (component) + (set-entry-text entry-home-directory + (string-append "/home/" (entry-value entry-name))))) + + (add-components-to-form form + label-name label-home-directory + entry-name entry-home-directory + ok-button) + + (make-wrapped-grid-window (vertically-stacked-grid + GRID-ELEMENT-SUBGRID entry-grid + GRID-ELEMENT-SUBGRID button-grid) + title) + (let ((error-page + (lambda () + (run-error-page (G_ "Empty inputs are not allowed.") + (G_ "Empty input"))))) + (receive (exit-reason argument) + (run-form form) + (dynamic-wind + (const #t) + (lambda () + (when (eq? exit-reason 'exit-component) + (cond + ((components=? argument ok-button) + (let ((name (entry-value entry-name)) + (home-directory (entry-value entry-home-directory))) + (if (or (string=? name "") + (string=? home-directory "")) + (begin + (error-page) + (run-user-add-page)) + (user + (name name) + (home-directory home-directory)))))))) + (lambda () + (destroy-form-and-pop form))))))) + +(define (run-user-page) + (define (run users) + (let* ((listbox (make-listbox + -1 -1 10 + (logior FLAG-SCROLL FLAG-BORDER))) + (info-textbox + (make-reflowed-textbox + -1 -1 + (G_ "Please add at least one user to system\ + using the 'Add' button.") + 40 #:flags FLAG-BORDER)) + (add-button (make-compact-button -1 -1 (G_ "Add"))) + (del-button (make-compact-button -1 -1 (G_ "Delete"))) + (listbox-button-grid + (apply + vertically-stacked-grid + GRID-ELEMENT-COMPONENT add-button + `(,@(if (null? users) + '() + (list GRID-ELEMENT-COMPONENT del-button))))) + (ok-button (make-button -1 -1 (G_ "OK"))) + (exit-button (make-button -1 -1 (G_ "Exit"))) + (title "User creation") + (grid + (vertically-stacked-grid + GRID-ELEMENT-COMPONENT info-textbox + GRID-ELEMENT-SUBGRID (horizontal-stacked-grid + GRID-ELEMENT-COMPONENT listbox + GRID-ELEMENT-SUBGRID listbox-button-grid) + GRID-ELEMENT-SUBGRID (horizontal-stacked-grid + GRID-ELEMENT-COMPONENT ok-button + GRID-ELEMENT-COMPONENT exit-button))) + (sorted-users (sort users (lambda (a b) + (string<= (user-name a) + (user-name b))))) + (listbox-elements + (map + (lambda (user) + `((key . ,(append-entry-to-listbox listbox + (user-name user))) + (user . ,user))) + sorted-users)) + (form (make-form))) + + + (add-form-to-grid grid form #t) + (make-wrapped-grid-window grid title) + (if (null? users) + (set-current-component form add-button) + (set-current-component form ok-button)) + + (receive (exit-reason argument) + (run-form form) + (dynamic-wind + (const #t) + (lambda () + (when (eq? exit-reason 'exit-component) + (cond + ((components=? argument add-button) + (run (cons (run-user-add-page) users))) + ((components=? argument del-button) + (let* ((current-user-key (current-listbox-entry listbox)) + (users + (map (cut assoc-ref <> 'user) + (remove (lambda (element) + (equal? (assoc-ref element 'key) + current-user-key)) + listbox-elements)))) + (run users))) + ((components=? argument ok-button) + (when (null? users) + (run-error-page (G_ "Please create at least one user.") + (G_ "No user")) + (run users)) + users)))) + (lambda () + (destroy-form-and-pop form)))))) + (run '())) diff --git a/gnu/installer/newt/utils.scm b/gnu/installer/newt/utils.scm new file mode 100644 index 0000000000..1c2ce4e628 --- /dev/null +++ b/gnu/installer/newt/utils.scm @@ -0,0 +1,43 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt utils) + #:use-module (ice-9 receive) + #:use-module (newt) + #:export (screen-columns + screen-rows + + destroy-form-and-pop + set-screen-size!)) + +;; Number of columns and rows of the terminal. +(define screen-columns (make-parameter 0)) +(define screen-rows (make-parameter 0)) + +(define (destroy-form-and-pop form) + "Destory the given FORM and pop the current window." + (destroy-form form) + (pop-window)) + +(define (set-screen-size!) + "Set the parameters 'screen-columns' and 'screen-rows' to the number of +columns and rows respectively of the current terminal." + (receive (columns rows) + (screen-size) + (screen-columns columns) + (screen-rows rows))) diff --git a/gnu/installer/newt/welcome.scm b/gnu/installer/newt/welcome.scm new file mode 100644 index 0000000000..eec98e291a --- /dev/null +++ b/gnu/installer/newt/welcome.scm @@ -0,0 +1,118 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 + +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt welcome) + #:use-module (gnu installer utils) + #:use-module (gnu installer newt utils) + #:use-module (guix build syscalls) + #:use-module (guix i18n) + #:use-module (ice-9 match) + #:use-module (ice-9 receive) + #:use-module (newt) + #:export (run-welcome-page)) + +;; Expected width and height for the logo. +(define logo-width (make-parameter 43)) +(define logo-height (make-parameter 19)) + +(define info-textbox-width (make-parameter 70)) +(define options-listbox-height (make-parameter 5)) + +(define* (run-menu-page title info-text logo + #:key + listbox-items + listbox-item->text) + "Run a page with the given TITLE, to ask the user to choose between +LISTBOX-ITEMS displayed in a listbox. The listbox items are converted to text +using LISTBOX-ITEM->TEXT procedure. Display the textual LOGO in the center of +the page. Contrary to other pages, we cannot resort to grid layouts, because +we want this page to occupy all the screen space available." + (define (fill-listbox listbox items) + (map (lambda (item) + (let* ((text (listbox-item->text item)) + (key (append-entry-to-listbox listbox text))) + (cons key item))) + items)) + + (let* ((logo-textbox + (make-textbox -1 -1 (logo-width) (logo-height) 0)) + (info-textbox + (make-reflowed-textbox -1 -1 + info-text + (info-textbox-width))) + (options-listbox + (make-listbox -1 -1 + (options-listbox-height) + (logior FLAG-BORDER FLAG-RETURNEXIT))) + (keys (fill-listbox options-listbox listbox-items)) + (grid (vertically-stacked-grid + GRID-ELEMENT-COMPONENT logo-textbox + GRID-ELEMENT-COMPONENT info-textbox + GRID-ELEMENT-COMPONENT options-listbox)) + (form (make-form))) + + (set-textbox-text logo-textbox (read-all logo)) + + (add-form-to-grid grid form #t) + (make-wrapped-grid-window grid title) + + (receive (exit-reason argument) + (run-form form) + (dynamic-wind + (const #t) + (lambda () + (when (eq? exit-reason 'exit-component) + (cond + ((components=? argument options-listbox) + (let* ((entry (current-listbox-entry options-listbox)) + (item (assoc-ref keys entry))) + (match item + ((text . proc) + (proc)))))))) + (lambda () + (destroy-form-and-pop form)))))) + +(define (run-welcome-page logo) + "Run a welcome page with the given textual LOGO displayed at the center of +the page. Ask the user to choose between manual installation, graphical +installation and reboot." + (run-menu-page + (G_ "GNU GuixSD install") + (G_ "Welcome to GNU GuixSD installer! + +Please note that the present graphical installer is still under heavy \ +development, so you might want to prefer using the shell based process. \ +The documentation is accessible at any time by pressing CTRL-ALT-F2.") + logo + #:listbox-items + `((,(G_ "Graphical install using a terminal based interface") + . + ,(const #t)) + (,(G_ "Install using the shell based process") + . + ,(lambda () + ;; Switch to TTY3, where a root shell is available for shell based + ;; install. The other root TTY's would have been ok too. + (system* "chvt" "3") + (run-welcome-page logo))) + (,(G_ "Reboot") + . + ,(lambda () + (newt-finish) + (reboot)))) + #:listbox-item->text car)) diff --git a/gnu/installer/newt/wifi.scm b/gnu/installer/newt/wifi.scm new file mode 100644 index 0000000000..59e40e327e --- /dev/null +++ b/gnu/installer/newt/wifi.scm @@ -0,0 +1,243 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix 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. +;;; +;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu installer newt wifi) + #:use-module (gnu installer connman) + #:use-module (gnu installer steps) + #:use-module (gnu installer newt utils) + #:use-module (gnu installer newt page) + #:use-module (guix i18n) + #:use-module (guix records) + #:use-module (ice-9 format) + #:use-module (ice-9 popen) + #:use-module (ice-9 receive) + #:use-module (ice-9 regex) + #:use-module (ice-9 rdelim) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (newt) + #:export (run-wifi-page)) + +;; This record associates a connman service to its key the listbox. +(define-record-type* <service-item> + service-item make-service-item + service-item? + (service service-item-service) ; connman <service> + (key service-item-key)) ; newt listbox-key + +(define (strength->string strength) + "Convert STRENGTH as an integer percentage into a text printable strength +bar using unicode characters. Taken from NetworkManager's +nmc_wifi_strength_bars." + (let ((quarter #\x2582) + (half #\x2584) + (three-quarter #\x2586) + (full #\x2588)) + (cond + ((> strength 80) + ;; ▂▄▆█ + (string quarter half three-quarter full)) + ((> strength 55) + ;; ▂▄▆_ + (string quarter half three-quarter #\_)) + ((> strength 30) + ;; ▂▄__ + (string quarter half #\_ #\_)) + ((> strength 5) + ;; ▂___ + (string quarter #\_ #\_ #\_)) + (else + ;; ____ + (string quarter #\_ #\_ #\_ #\_))))) + +(define (force-wifi-scan) + "Force a wifi scan. Raise a condition if no wifi technology is available." + (let* ((technologies (connman-technologies)) + (wifi-technology + (find (lambda (technology) + (string=? (technology-type technology) "wifi")) + technologies))) + (if wifi-technology + (connman-scan-technology wifi-technology) + (raise (condition + (&message + (message (G_ "Unable to find a wifi technology")))))))) + +(define (draw-scanning-page) + "Draw a page to indicate a wifi scan in in progress." + (draw-info-page (G_ "Scanning wifi for available networks, please wait.") + (G_ "Scan in progress"))) + +(define (run-wifi-password-page) + "Run a page prompting user for a password and return it." + (run-input-page (G_ "Please enter the wifi password.") + (G_ "Password required"))) + +(define (run-wrong-password-page service-name) + "Run a page to inform user of a wrong password input." + (run-error-page + (format #f (G_ "The password you entered for ~a is incorrect.") + service-name) + (G_ "Wrong password"))) + +(define (run-unknown-error-page service-name) + "Run a page to inform user that a connection error happened." + (run-error-page + (format #f + (G_ "An error occured while trying to connect to ~a, please retry.") + service-name) + (G_ "Connection error"))) + +(define (password-callback) + (run-wifi-password-page)) + +(define (connect-wifi-service listbox service-items) + "Connect to the wifi service selected in LISTBOX. SERVICE-ITEMS is the list +of <service-item> records present in LISTBOX." + (let* ((listbox-key (current-listbox-entry listbox)) + (item (find (lambda (item) + (eq? (service-item-key item) listbox-key)) + service-items)) + (service (service-item-service item)) + (service-name (service-name service)) + (form (draw-connecting-page service-name))) + (dynamic-wind + (const #t) + (lambda () + (guard (c ((connman-password-error? c) + (run-wrong-password-page service-name) + #f) + ((connman-already-connected-error? c) + #t) + ((connman-connection-error? c) + (run-unknown-error-page service-name) + #f)) + (connman-connect-with-auth service password-callback))) + (lambda () + (destroy-form-and-pop form))))) + +(define (run-wifi-scan-page) + "Force a wifi scan and draw a page during the operation." + (let ((form (draw-scanning-page))) + (force-wifi-scan) + (destroy-form-and-pop form))) + +(define (wifi-services) + "Return all the connman services of wifi type." + (let ((services (connman-services))) + (filter (lambda (service) + (and (string=? (service-type service) "wifi") + (not (string-null? (service-name service))))) + services))) + +(define* (fill-wifi-services listbox wifi-services) + "Append all the services in WIFI-SERVICES to the given LISTBOX." + (clear-listbox listbox) + (map (lambda (service) + (let* ((text (service->text service)) + (key (append-entry-to-listbox listbox text))) + (service-item + (service service) + (key key)))) + wifi-services)) + +;; Maximum length of a wifi service name. +(define service-name-max-length (make-parameter 20)) + +;; Heigth of the listbox displaying wifi services. +(define wifi-listbox-heigth (make-parameter 20)) + +;; Information textbox width. +(define info-textbox-width (make-parameter 40)) + +(define (service->text service) + "Return a string composed of the name and the strength of the given +SERVICE. A '*' preceding the service name indicates that it is connected." + (let* ((name (service-name service)) + (padded-name (string-pad-right name + (service-name-max-length))) + (strength (service-strength service)) + (strength-string (strength->string strength)) + (state (service-state service)) + (connected? (or (string=? state "online") + (string=? state "ready")))) + (format #f "~c ~a ~a~%" + (if connected? #\* #\ ) + padded-name + strength-string))) + +(define (run-wifi-page) + "Run a page displaying available wifi networks in a listbox. Connect to the +network when the corresponding listbox entry is selected. A button allow to +force a wifi scan." + (let* ((listbox (make-listbox + -1 -1 + (wifi-listbox-heigth) + (logior FLAG-SCROLL FLAG-BORDER FLAG-RETURNEXIT))) + (form (make-form)) + (buttons-grid (make-grid 1 1)) + (middle-grid (make-grid 2 1)) + (info-text (G_ "Please select a wifi network.")) + (info-textbox + (make-reflowed-textbox -1 -1 info-text + (info-textbox-width) + #:flags FLAG-BORDER)) + (exit-button (make-button -1 -1 (G_ "Exit"))) + (scan-button (make-button -1 -1 (G_ "Scan"))) + (services (wifi-services)) + (service-items '())) + + (if (null? services) + (append-entry-to-listbox listbox (G_ "No wifi detected")) + (set! service-items (fill-wifi-services listbox services))) + + (set-grid-field middle-grid 0 0 GRID-ELEMENT-COMPONENT listbox) + (set-grid-field middle-grid 1 0 GRID-ELEMENT-COMPONENT scan-button + #:anchor ANCHOR-TOP + #:pad-left 2) + (set-grid-field buttons-grid 0 0 GRID-ELEMENT-COMPONENT exit-button) + + (add-components-to-form form + info-textbox + listbox scan-button + exit-button) + (make-wrapped-grid-window + (basic-window-grid info-textbox middle-grid buttons-grid) + (G_ "Wifi")) + + (receive (exit-reason argument) + (run-form form) + (dynamic-wind + (const #t) + (lambda () + (when (eq? exit-reason 'exit-component) + (cond + ((components=? argument scan-button) + (run-wifi-scan-page) + (run-wifi-page)) + ((components=? argument exit-button) + (raise + (condition + (&installer-step-abort)))) + ((components=? argument listbox) + (let ((result (connect-wifi-service listbox service-items))) + (unless result + (run-wifi-page))))))) + (lambda () + (destroy-form-and-pop form)))))) |