summary refs log tree commit diff
path: root/gnu/installer/newt
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/installer/newt')
-rw-r--r--gnu/installer/newt/ethernet.scm81
-rw-r--r--gnu/installer/newt/final.scm86
-rw-r--r--gnu/installer/newt/hostname.scm26
-rw-r--r--gnu/installer/newt/keymap.scm122
-rw-r--r--gnu/installer/newt/locale.scm217
-rw-r--r--gnu/installer/newt/menu.scm44
-rw-r--r--gnu/installer/newt/network.scm173
-rw-r--r--gnu/installer/newt/page.scm530
-rw-r--r--gnu/installer/newt/partition.scm766
-rw-r--r--gnu/installer/newt/services.scm48
-rw-r--r--gnu/installer/newt/timezone.scm83
-rw-r--r--gnu/installer/newt/user.scm175
-rw-r--r--gnu/installer/newt/utils.scm43
-rw-r--r--gnu/installer/newt/welcome.scm118
-rw-r--r--gnu/installer/newt/wifi.scm243
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))))))