gnu: services: Fix the NFS service.
[jackhill/guix/guix.git] / gnu / services / xorg.scm
index ea8433a..4590709 100644 (file)
@@ -1,7 +1,13 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2017 Andy Wingo <wingo@igalia.com>
-;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2015 Sou Bunnbu <iyzsong@gmail.com>
+;;; Copyright © 2018, 2019 Timothy Sample <samplet@ngyro.com>
+;;; Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
+;;; Copyright © 2019 Tim Gesthuizen <tim.gesthuizen@yahoo.de>
+;;; Copyright © 2020 shtwzrd <shtwzrd@protonmail.com>
+;;; Copyright © 2020 Jakub Kądziołka <kuba@kadziolka.net>
+;;; Copyright © 2020 Alex Griffin <a@ajgrf.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
   #:use-module (gnu services)
   #:use-module (gnu services shepherd)
   #:use-module (gnu system pam)
+  #:use-module (gnu system keyboard)
+  #:use-module (gnu services base)
   #:use-module (gnu services dbus)
-  #:use-module ((gnu packages base) #:select (canonical-package))
+  #:use-module (gnu packages base)
   #:use-module (gnu packages guile)
   #:use-module (gnu packages xorg)
+  #:use-module (gnu packages fonts)
   #:use-module (gnu packages gl)
+  #:use-module (gnu packages glib)
   #:use-module (gnu packages display-managers)
+  #:use-module (gnu packages freedesktop)
   #:use-module (gnu packages gnustep)
   #:use-module (gnu packages gnome)
   #:use-module (gnu packages admin)
   #:use-module (guix packages)
   #:use-module (guix derivations)
   #:use-module (guix records)
+  #:use-module (guix deprecation)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-26)
   #:use-module (ice-9 match)
-  #:export (xorg-configuration-file
+  #:export (xorg-configuration
+            xorg-configuration?
+            xorg-configuration-modules
+            xorg-configuration-fonts
+            xorg-configuration-drivers
+            xorg-configuration-resolutions
+            xorg-configuration-extra-config
+            xorg-configuration-server
+            xorg-configuration-server-arguments
+
             %default-xorg-modules
             %default-xorg-fonts
             xorg-wrapper
             slim-configuration-xauth
             slim-configuration-shepherd
             slim-configuration-auto-login-session
-            slim-configuration-startx
+            slim-configuration-xorg
+            slim-configuration-display
+            slim-configuration-vt
+            slim-configuration-sessreg
 
             slim-service-type
             slim-service
             screen-locker-service-type
             screen-locker-service
 
+            localed-configuration
+            localed-configuration?
+            localed-service-type
+
             gdm-configuration
             gdm-service-type
-            gdm-service))
+            gdm-service
+
+            handle-xorg-configuration
+            set-xorg-configuration))
 
 ;;; Commentary:
 ;;;
 ;;; Code:
 
 (define %default-xorg-modules
-  ;; Default list of modules loaded by the server.  Note that the order
-  ;; matters since it determines which driver is going to be used when there's
-  ;; a choice.
+  ;; Default list of modules loaded by the server.  When multiple drivers
+  ;; match, the first one in the list is loaded.
   (list xf86-video-vesa
         xf86-video-fbdev
+        xf86-video-amdgpu
         xf86-video-ati
         xf86-video-cirrus
         xf86-video-intel
                      "/share/fonts/X11/misc")
         (file-append font-adobe75dpi "/share/fonts/X11/75dpi")))
 
-(define* (xorg-configuration-file #:key
-                                  (modules %default-xorg-modules)
-                                  (fonts %default-xorg-fonts)
-                                  (drivers '()) (resolutions '())
-                                  (extra-config '()))
-  "Return a configuration file for the Xorg server containing search paths for
-all the common drivers.
-
-@var{modules} must be a list of @dfn{module packages} loaded by the Xorg
-server---e.g., @code{xf86-video-vesa}, @code{xf86-input-keyboard}, and so on.
-@var{fonts} must be a list of font directories to add to the server's
-@dfn{font path}.
-
-@var{drivers} must be either the empty list, in which case Xorg chooses a
-graphics driver automatically, or a list of driver names that will be tried in
-this order---e.g., @code{(\"modesetting\" \"vesa\")}.
-
-Likewise, when @var{resolutions} is the empty list, Xorg chooses an
-appropriate screen resolution; otherwise, it must be a list of
-resolutions---e.g., @code{((1024 768) (640 480))}.
-
-Last, @var{extra-config} is a list of strings or objects appended to the
-configuration file.  It is used to pass extra text to be
-added verbatim to the configuration file."
-  (define all-modules
-    ;; 'xorg-server' provides 'fbdevhw.so' etc.
-    (append modules (list xorg-server)))
-
-  (define build
-    #~(begin
-        (use-modules (ice-9 match)
-                     (srfi srfi-1)
-                     (srfi srfi-26))
-
-        (call-with-output-file #$output
-          (lambda (port)
-            (define drivers
-              '#$drivers)
+(define %default-xorg-server-arguments
+  ;; Default command-line arguments for X.
+  '("-nolisten" "tcp"))
+
+;; Configuration of an Xorg server.
+(define-record-type* <xorg-configuration>
+  xorg-configuration make-xorg-configuration
+  xorg-configuration?
+  (modules          xorg-configuration-modules    ;list of packages
+                    ; filter out modules not supported on current system
+                    (default (filter
+                              (lambda (p)
+                                (member (%current-system)
+                                        (package-supported-systems p)))
+                              %default-xorg-modules)))
+  (fonts            xorg-configuration-fonts      ;list of packges
+                    (default %default-xorg-fonts))
+  (drivers          xorg-configuration-drivers    ;list of strings
+                    (default '()))
+  (resolutions      xorg-configuration-resolutions ;list of tuples
+                    (default '()))
+  (keyboard-layout  xorg-configuration-keyboard-layout ;#f | <keyboard-layout>
+                    (default #f))
+  (extra-config     xorg-configuration-extra-config ;list of strings
+                    (default '()))
+  (server           xorg-configuration-server     ;package
+                    (default xorg-server))
+  (server-arguments xorg-configuration-server-arguments ;list of strings
+                    (default %default-xorg-server-arguments)))
+
+(define (xorg-configuration->file config)
+  "Compute an Xorg configuration file corresponding to CONFIG, an
+<xorg-configuration> record."
+  (let ((xorg-server (xorg-configuration-server config)))
+    (define all-modules
+      ;; 'xorg-server' provides 'fbdevhw.so' etc.
+      (append (xorg-configuration-modules config)
+              (list xorg-server)))
+
+    (define build
+      #~(begin
+          (use-modules (ice-9 match)
+                       (srfi srfi-1)
+                       (srfi srfi-26))
+
+          (call-with-output-file #$output
+            (lambda (port)
+              (define drivers
+                '#$(xorg-configuration-drivers config))
 
-            (define (device-section driver)
-              (string-append "
+              (define (device-section driver)
+                (string-append "
 Section \"Device\"
   Identifier \"device-" driver "\"
   Driver \"" driver "\"
 EndSection"))
 
-            (define (screen-section driver resolutions)
-              (string-append "
+              (define (screen-section driver resolutions)
+                (string-append "
 Section \"Screen\"
   Identifier \"screen-" driver "\"
   Device \"device-" driver "\"
@@ -178,56 +220,94 @@ Section \"Screen\"
   EndSubSection
 EndSection"))
 
-            (define (expand modules)
-              ;; Append to MODULES the relevant /lib/xorg/modules
-              ;; sub-directories.
-              (append-map (lambda (module)
-                            (filter-map (lambda (directory)
-                                          (let ((full (string-append module
-                                                                     directory)))
-                                            (and (file-exists? full)
-                                                 full)))
-                                        '("/lib/xorg/modules/drivers"
-                                          "/lib/xorg/modules/input"
-                                          "/lib/xorg/modules/multimedia"
-                                          "/lib/xorg/modules/extensions")))
-                          modules))
-
-            (display "Section \"Files\"\n" port)
-            (for-each (lambda (font)
-                        (format port "  FontPath \"~a\"~%" font))
-                      '#$fonts)
-            (for-each (lambda (module)
-                        (format port
-                                "  ModulePath \"~a\"~%"
-                                module))
-                      (append (expand '#$all-modules)
-
-                              ;; For fbdevhw.so and so on.
-                              (list #$(file-append xorg-server
-                                                   "/lib/xorg/modules"))))
-            (display "EndSection\n" port)
-            (display "
+              (define (input-class-section layout variant model options)
+                (string-append "
+Section \"InputClass\"
+  Identifier \"evdev keyboard catchall\"
+  MatchIsKeyboard \"on\"
+  Option \"XkbLayout\" " (object->string layout)
+  (if variant
+      (string-append "  Option \"XkbVariant\" \""
+                     variant "\"")
+      "")
+  (if model
+      (string-append "  Option \"XkbModel\" \""
+                     model "\"")
+      "")
+  (match options
+    (()
+     "")
+    (_
+     (string-append "  Option \"XkbOptions\" \""
+                    (string-join options ",") "\""))) "
+
+  MatchDevicePath \"/dev/input/event*\"
+  Driver \"evdev\"
+EndSection\n"))
+
+              (define (expand modules)
+                ;; Append to MODULES the relevant /lib/xorg/modules
+                ;; sub-directories.
+                (append-map (lambda (module)
+                              (filter-map (lambda (directory)
+                                            (let ((full (string-append module
+                                                                       directory)))
+                                              (and (file-exists? full)
+                                                   full)))
+                                          '("/lib/xorg/modules/drivers"
+                                            "/lib/xorg/modules/input"
+                                            "/lib/xorg/modules/multimedia"
+                                            "/lib/xorg/modules/extensions")))
+                            modules))
+
+              (display "Section \"Files\"\n" port)
+              (for-each (lambda (font)
+                          (format port "  FontPath \"~a\"~%" font))
+                        '#$(xorg-configuration-fonts config))
+              (for-each (lambda (module)
+                          (format port
+                                  "  ModulePath \"~a\"~%"
+                                  module))
+                        (append (expand '#$all-modules)
+
+                                ;; For fbdevhw.so and so on.
+                                (list #$(file-append xorg-server
+                                                     "/lib/xorg/modules"))))
+              (display "EndSection\n" port)
+              (display "
 Section \"ServerFlags\"
   Option \"AllowMouseOpenFail\" \"on\"
 EndSection\n" port)
 
-            (display (string-join (map device-section drivers) "\n")
-                     port)
-            (newline port)
-            (display (string-join
-                      (map (cut screen-section <> '#$resolutions)
-                           drivers)
-                      "\n")
-                     port)
-            (newline port)
-
-            (for-each (lambda (config)
-                        (display config port))
-                      '#$extra-config)))))
-
-  (computed-file "xserver.conf" build))
-
+              (display (string-join (map device-section drivers) "\n")
+                       port)
+              (newline port)
+              (display (string-join
+                        (map (cut screen-section <>
+                                  '#$(xorg-configuration-resolutions config))
+                             drivers)
+                        "\n")
+                       port)
+              (newline port)
+
+              (let ((layout  #$(and=> (xorg-configuration-keyboard-layout config)
+                                      keyboard-layout-name))
+                    (variant #$(and=> (xorg-configuration-keyboard-layout config)
+                                      keyboard-layout-variant))
+                    (model   #$(and=> (xorg-configuration-keyboard-layout config)
+                                      keyboard-layout-model))
+                    (options '#$(and=> (xorg-configuration-keyboard-layout config)
+                                       keyboard-layout-options)))
+                (when layout
+                  (display (input-class-section layout variant model options)
+                           port)
+                  (newline port)))
+
+              (for-each (lambda (config)
+                          (display config port))
+                        '#$(xorg-configuration-extra-config config))))))
+
+    (computed-file "xserver.conf" build)))
 
 (define (xorg-configuration-directory modules)
   "Return a directory that contains the @code{.conf} files for X.org that
@@ -255,60 +335,43 @@ in @var{modules}."
                                  files)
                        #t))))
 
-(define* (xorg-wrapper #:key
-                       (guile (canonical-package guile-2.0))
-                       (modules %default-xorg-modules)
-                       (configuration-file (xorg-configuration-file
-                                            #:modules modules))
-                       (xorg-server xorg-server))
-  "Return a derivation that builds a @var{guile} script to start the X server
-from @var{xorg-server}.  @var{configuration-file} is the server configuration
-file or a derivation that builds it; when omitted, the result of
-@code{xorg-configuration-file} is used.  The resulting script should be used
-in place of @code{/usr/bin/X}."
+(define* (xorg-wrapper #:optional (config (xorg-configuration)))
+  "Return a derivation that builds a script to start the X server with the
+given @var{config}.  The resulting script should be used in place of
+@code{/usr/bin/X}."
   (define exp
     ;; Write a small wrapper around the X server.
     #~(begin
         (setenv "XORG_DRI_DRIVER_PATH" (string-append #$mesa "/lib/dri"))
         (setenv "XKB_BINDIR" (string-append #$xkbcomp "/bin"))
 
-        (let ((X (string-append #$xorg-server "/bin/X")))
+        (let ((X (string-append #$(xorg-configuration-server config) "/bin/X")))
           (apply execl X X
                  "-xkbdir" (string-append #$xkeyboard-config "/share/X11/xkb")
-                 "-config" #$configuration-file
-                 "-configdir" #$(xorg-configuration-directory modules)
+                 "-config" #$(xorg-configuration->file config)
+                 "-configdir" #$(xorg-configuration-directory
+                                 (xorg-configuration-modules config))
                  (cdr (command-line))))))
 
   (program-file "X-wrapper" exp))
 
-(define* (xorg-start-command #:key
-                             (guile (canonical-package guile-2.0))
-                             (modules %default-xorg-modules)
-                             (fonts %default-xorg-fonts)
-                             (configuration-file
-                              (xorg-configuration-file #:modules modules
-                                                       #:fonts fonts))
-                             (xorg-server xorg-server))
-  "Return a @code{startx} script in which @var{modules}, a list of X module
-packages, and @var{fonts}, a list of X font directories, are available.  See
-@code{xorg-wrapper} for more details on the arguments.  The result should be
-used in place of @code{startx}."
+(define* (xorg-start-command #:optional (config (xorg-configuration)))
+  "Return a @code{startx} script in which the modules, fonts, etc. specified
+in @var{config}, are available.  The result should be used in place of
+@code{startx}."
   (define X
-    (xorg-wrapper #:guile guile
-                  #:configuration-file configuration-file
-                  #:modules modules
-                  #:xorg-server xorg-server))
+    (xorg-wrapper config))
+
   (define exp
     ;; Write a small wrapper around the X server.
     #~(apply execl #$X #$X ;; Second #$X is for argv[0].
-             "-logverbose" "-verbose" "-nolisten" "tcp" "-terminate"
-             (cdr (command-line))))
+             "-logverbose" "-verbose" "-terminate"
+             #$@(xorg-configuration-server-arguments config)
+              (cdr (command-line))))
 
   (program-file "startx" exp))
 
-(define* (xinitrc #:key
-                  (guile (canonical-package guile-2.0))
-                  fallback-session)
+(define* (xinitrc #:key fallback-session)
   "Return a system-wide xinitrc script that starts the specified X session,
 which should be passed to this script as the first argument.  If not, the
 @var{fallback-session} will be used or, if @var{fallback-session} is false, a
@@ -401,6 +464,25 @@ desktop session from the system or user profile will be used."
 
   (program-file "xinitrc" builder))
 
+(define-syntax handle-xorg-configuration
+  (syntax-rules ()
+    "Generate the `compose' and `extend' entries of a login manager
+`service-type' to handle specifying the `xorg-configuration' through
+a `service-extension', as used by `set-xorg-configuration'."
+    ((_ configuration-record service-type-definition)
+     (service-type
+       (inherit service-type-definition)
+       (compose (lambda (extensions)
+                  (match extensions
+                    (() #f)
+                    ((config . _) config))))
+       (extend (lambda (config xorg-configuration)
+                 (if xorg-configuration
+                     (configuration-record
+                      (inherit config)
+                      (xorg-configuration xorg-configuration))
+                     config)))))))
+
 \f
 ;;;
 ;;; SLiM log-in manager.
@@ -413,7 +495,7 @@ desktop session from the system or user profile will be used."
 (define %default-slim-theme-name
   ;; This must be the name of the sub-directory in %DEFAULT-SLIM-THEME that
   ;; contains the actual theme files.
-  "0.x")
+  "1.x")
 
 (define-record-type* <slim-configuration>
   slim-configuration make-slim-configuration
@@ -436,8 +518,12 @@ desktop session from the system or user profile will be used."
             (default shepherd))
   (auto-login-session slim-configuration-auto-login-session
                       (default #f))
-  (startx slim-configuration-startx
-          (default (xorg-start-command)))
+  (xorg-configuration slim-configuration-xorg
+                      (default (xorg-configuration)))
+  (display slim-configuration-display
+           (default ":0"))
+  (vt slim-configuration-vt
+      (default "vt7"))
   (sessreg slim-configuration-sessreg
            (default sessreg)))
 
@@ -445,25 +531,31 @@ desktop session from the system or user profile will be used."
   "Return a PAM service for @command{slim}."
   (list (unix-pam-service
          "slim"
+         #:login-uid? #t
          #:allow-empty-passwords?
          (slim-configuration-allow-empty-passwords? config))))
 
 (define (slim-shepherd-service config)
-  (define slim.cfg
-    (let ((xinitrc (xinitrc #:fallback-session
-                            (slim-configuration-auto-login-session config)))
-          (slim    (slim-configuration-slim config))
-          (xauth   (slim-configuration-xauth config))
-          (startx  (slim-configuration-startx config))
-          (shepherd   (slim-configuration-shepherd config))
-          (theme-name (slim-configuration-theme-name config))
-          (sessreg (slim-configuration-sessreg config)))
+  (let* ((xinitrc (xinitrc #:fallback-session
+                           (slim-configuration-auto-login-session config)))
+         (xauth   (slim-configuration-xauth config))
+         (startx  (xorg-start-command (slim-configuration-xorg config)))
+         (display (slim-configuration-display config))
+         (vt (slim-configuration-vt config))
+         (shepherd   (slim-configuration-shepherd config))
+         (theme-name (slim-configuration-theme-name config))
+         (sessreg (slim-configuration-sessreg config))
+         (lockfile (string-append "/var/run/slim-" vt ".lock")))
+    (define slim.cfg
       (mixed-text-file "slim.cfg"  "
 default_path /run/current-system/profile/bin
 default_xserver " startx "
-xserver_arguments :0 vt7
+display_name " display "
+xserver_arguments " vt "
 xauth_path " xauth "/bin/xauth
-authfile /var/run/slim.auth
+authfile /var/run/slim-" vt ".auth
+lockfile " lockfile "
+logfile /var/log/slim-" vt ".log
 
 # The login command.  '%session' is replaced by the chosen session name, one
 # of the names specified in the 'sessions' setting: 'wmaker', 'xfce', etc.
@@ -481,53 +573,67 @@ reboot_cmd " shepherd "/sbin/reboot\n"
     "")
 (if theme-name
     (string-append "current_theme " theme-name "\n")
-    ""))))
-
-  (define theme
-    (slim-configuration-theme config))
-
-  (list (shepherd-service
-         (documentation "Xorg display server")
-         (provision '(xorg-server))
-         (requirement '(user-processes host-name udev))
-         (start
-          #~(lambda ()
-              ;; A stale lock file can prevent SLiM from starting, so remove it to
-              ;; be on the safe side.
-              (false-if-exception (delete-file "/var/run/slim.lock"))
-
-              (fork+exec-command
-               (list (string-append #$slim "/bin/slim") "-nodaemon")
-               #:environment-variables
-               (list (string-append "SLIM_CFGFILE=" #$slim.cfg)
-                     #$@(if theme
-                            (list #~(string-append "SLIM_THEMESDIR=" #$theme))
-                            #~())))))
-         (stop #~(make-kill-destructor))
-         (respawn? #t))))
+    "")))
+
+    (define theme
+      (slim-configuration-theme config))
+
+    (list (shepherd-service
+           (documentation "Xorg display server")
+           (provision (append
+                       ;; For compatibility, also provide 'xorg-server'.
+                       (if (string=? vt "vt7")
+                           '(xorg-server)
+                           '())
+
+                       (list (symbol-append 'xorg-server-
+                                            (string->symbol vt)))))
+           (requirement '(user-processes host-name udev))
+           (start
+            #~(lambda ()
+                ;; A stale lock file can prevent SLiM from starting, so remove it to
+                ;; be on the safe side.
+                (false-if-exception (delete-file lockfile))
+
+                (fork+exec-command
+                 (list (string-append #$(slim-configuration-slim config)
+                                      "/bin/slim")
+                       "-nodaemon")
+                 #:environment-variables
+                 (list (string-append "SLIM_CFGFILE=" #$slim.cfg)
+                       #$@(if theme
+                              (list #~(string-append "SLIM_THEMESDIR=" #$theme))
+                              #~())))))
+           (stop #~(make-kill-destructor))
+           (respawn? #t)))))
 
 (define slim-service-type
-  (service-type (name 'slim)
-                (extensions
-                 (list (service-extension shepherd-root-service-type
-                                          slim-shepherd-service)
-                       (service-extension pam-root-service-type
-                                          slim-pam-service)
-
-                       ;; Unconditionally add xterm to the system profile, to
-                       ;; avoid bad surprises.
-                       (service-extension profile-service-type
-                                          (const (list xterm)))))
-                (default-value (slim-configuration))))
-
-(define* (slim-service #:key (slim slim)          ;deprecated
-                       (allow-empty-passwords? #t) auto-login?
-                       (default-user "")
-                       (theme %default-slim-theme)
-                       (theme-name %default-slim-theme-name)
-                       (xauth xauth) (shepherd shepherd)
-                       (auto-login-session #f)
-                       (startx (xorg-start-command)))
+  (handle-xorg-configuration slim-configuration
+    (service-type (name 'slim)
+                  (extensions
+                   (list (service-extension shepherd-root-service-type
+                                            slim-shepherd-service)
+                         (service-extension pam-root-service-type
+                                            slim-pam-service)
+
+                         ;; Unconditionally add xterm to the system profile, to
+                         ;; avoid bad surprises.
+                         (service-extension profile-service-type
+                                            (const (list xterm)))))
+
+                  (default-value (slim-configuration))
+                  (description
+                   "Run the SLiM graphical login manager for X11."))))
+
+(define-deprecated (slim-service #:key (slim slim)
+                                 (allow-empty-passwords? #t) auto-login?
+                                 (default-user "")
+                                 (theme %default-slim-theme)
+                                 (theme-name %default-slim-theme-name)
+                                 (xauth xauth) (shepherd shepherd)
+                                 (auto-login-session #f)
+                                 (startx (xorg-start-command)))
+  slim-service-type
   "Return a service that spawns the SLiM graphical login manager, which in
 turn starts the X display server with @var{startx}, a command as returned by
 @code{xorg-start-command}.
@@ -560,8 +666,7 @@ theme."
             (auto-login? auto-login?) (default-user default-user)
             (theme theme) (theme-name theme-name)
             (xauth xauth) (shepherd shepherd)
-            (auto-login-session auto-login-session)
-            (startx startx))))
+            (auto-login-session auto-login-session))))
 
 \f
 ;;;
@@ -590,7 +695,11 @@ theme."
                  (list (service-extension pam-root-service-type
                                           screen-locker-pam-services)
                        (service-extension setuid-program-service-type
-                                          screen-locker-setuid-programs)))))
+                                          screen-locker-setuid-programs)))
+                (description
+                 "Allow the given program to be used as a screen locker for
+the graphical server by making it setuid-root, so it can authenticate users,
+and by creating a PAM service for it.")))
 
 (define* (screen-locker-service package
                                 #:optional
@@ -610,66 +719,214 @@ makes the good ol' XlockMore usable."
                           (file-append package "/bin/" program)
                           allow-empty-passwords?)))
 
+\f
+;;;
+;;; Locale service.
+;;;
+
+(define-record-type* <localed-configuration>
+  localed-configuration make-localed-configuration
+  localed-configuration?
+  (localed         localed-configuration-localed
+                   (default localed))
+  (keyboard-layout localed-configuration-keyboard-layout
+                   (default #f)))
+
+(define (localed-dbus-service config)
+  "Return the 'localed' D-Bus service for @var{config}, a
+@code{<localed-configuration>} record."
+  (define keyboard-layout
+    (localed-configuration-keyboard-layout config))
+
+  ;; The primary purpose of 'localed' is to tell GDM what the "current" Xorg
+  ;; keyboard layout is.  If 'localed' is missing, or if it's unable to
+  ;; determine the current XKB layout, then GDM forcefully installs its
+  ;; default XKB config (US English).  Here we communicate the configured
+  ;; layout through environment variables.
+
+  (if keyboard-layout
+      (let* ((layout  (keyboard-layout-name keyboard-layout))
+             (variant (keyboard-layout-variant keyboard-layout))
+             (model   (keyboard-layout-model keyboard-layout))
+             (options (keyboard-layout-options keyboard-layout)))
+        (list (wrapped-dbus-service
+               (localed-configuration-localed config)
+               "libexec/localed/localed"
+               `(("GUIX_XKB_LAYOUT" ,layout)
+                 ,@(if variant
+                       `(("GUIX_XKB_VARIANT" ,variant))
+                       '())
+                 ,@(if model
+                       `(("GUIX_XKB_MODEL" ,model))
+                       '())
+                 ,@(if (null? options)
+                       '()
+                       `(("GUIX_XKB_OPTIONS"
+                          ,(string-join options ","))))))))
+      '()))
+
+(define localed-service-type
+  (let ((package (lambda (config)
+                   ;; Don't bother if the user didn't specify any keyboard
+                   ;; layout.
+                   (if (localed-configuration-keyboard-layout config)
+                       (list (localed-configuration-localed config))
+                       '()))))
+    (service-type (name 'localed)
+                  (extensions
+                   (list (service-extension dbus-root-service-type
+                                            localed-dbus-service)
+                         (service-extension udev-service-type package)
+                         (service-extension polkit-service-type package)
+
+                         ;; Add 'localectl' to the profile.
+                         (service-extension profile-service-type package)))
+
+                  ;; This service can be extended, typically by the X login
+                  ;; manager, to communicate the chosen Xorg keyboard layout.
+                  (compose (lambda (extensions)
+                             (find keyboard-layout? extensions)))
+                  (extend (lambda (config keyboard-layout)
+                            (localed-configuration
+                             (inherit config)
+                             (keyboard-layout keyboard-layout))))
+                  (description
+                   "Run the locale daemon, @command{localed}, which can be used
+to control the system locale and keyboard mapping from user programs such as
+the GNOME desktop environment.")
+                  (default-value (localed-configuration)))))
+
+\f
+;;;
+;;; GNOME Desktop Manager.
+;;;
+
 (define %gdm-accounts
   (list (user-group (name "gdm") (system? #t))
         (user-account
          (name "gdm")
          (group "gdm")
+         (supplementary-groups '("video"))
          (system? #t)
          (comment "GNOME Display Manager user")
          (home-directory "/var/lib/gdm")
          (shell (file-append shadow "/sbin/nologin")))))
 
+(define %gdm-activation
+  ;; Ensure /var/lib/gdm is owned by the "gdm" user.  This is normally the
+  ;; case but could be wrong if the "gdm" user was created, then removed, and
+  ;; then recreated under a different UID/GID: <https://bugs.gnu.org/37423>.
+  (with-imported-modules '((guix build utils))
+    #~(begin
+        (use-modules (guix build utils))
+
+        (let* ((gdm (getpwnam "gdm"))
+               (uid (passwd:uid gdm))
+               (gid (passwd:gid gdm))
+               (st  (stat "/var/lib/gdm" #f)))
+          ;; Recurse into /var/lib/gdm only if it has wrong ownership.
+          (when (and st
+                     (or (not (= uid (stat:uid st)))
+                         (not (= gid (stat:gid st)))))
+            (for-each (lambda (file)
+                        (chown file uid gid))
+                      (find-files "/var/lib/gdm"
+                                  #:directories? #t)))))))
+
+(define dbus-daemon-wrapper
+  (program-file
+   "gdm-dbus-wrapper"
+   #~(begin
+       (use-modules (srfi srfi-26))
+
+       (define system-profile
+         "/run/current-system/profile")
+
+       (define user-profile
+         (and=> (getpw (getuid))
+                (lambda (pw)
+                  (string-append (passwd:dir pw) "/.guix-profile"))))
+
+       ;; If we are able to find the user's profile, we can add it to
+       ;; the search paths set below.  We need to do this so that D-Bus
+       ;; can start services installed by the user.  This allows
+       ;; applications that require session D-Bus services (e.g,
+       ;; 'evolution') to work even if those services are only available
+       ;; in the user's profile.  See <https://bugs.gnu.org/35267>.
+       (define profiles
+         (if user-profile
+             (list user-profile system-profile)
+             (list system-profile)))
+
+       (setenv "XDG_CONFIG_DIRS"
+               (string-join (map (cut string-append <> "/etc/xdg") profiles)
+                            ":"))
+       (setenv "XDG_DATA_DIRS"
+               (string-join (map (cut string-append <> "/share") profiles)
+                            ":"))
+       (apply execl (string-append #$dbus "/bin/dbus-daemon")
+              (program-arguments)))))
+
 (define-record-type* <gdm-configuration>
   gdm-configuration make-gdm-configuration
   gdm-configuration?
   (gdm gdm-configuration-gdm (default gdm))
   (allow-empty-passwords? gdm-configuration-allow-empty-passwords? (default #t))
-  (allow-root? gdm-configuration-allow-root? (default #t))
   (auto-login? gdm-configuration-auto-login? (default #f))
+  (dbus-daemon gdm-configuration-dbus-daemon (default dbus-daemon-wrapper))
+  (debug? gdm-configuration-debug? (default #f))
   (default-user gdm-configuration-default-user (default #f))
-  (x-server gdm-configuration-x-server))
-
-(define (gdm-etc-service config)
-  (define gdm-configuration-file
-    (mixed-text-file "gdm-custom.conf"
-                     "[daemon]\n"
-                     "#User=gdm\n"
-                     "#Group=gdm\n"
-                     (if (gdm-configuration-auto-login? config)
-                         (string-append
-                          "AutomaticLoginEnable=true\n"
-                          "AutomaticLogin="
-                          (or (gdm-configuration-default-user config)
-                              (error "missing default user for auto-login"))
-                          "\n")
-                         (string-append
-                          "AutomaticLoginEnable=false\n"
-                          "#AutomaticLogin=\n"))
-                     "#TimedLoginEnable=false\n"
-                     "#TimedLogin=\n"
-                     "#TimedLoginDelay=0\n"
-                     "#InitialSetupEnable=true\n"
-                     ;; Enable me once X is working.
-                     "WaylandEnable=false\n"
-                     "\n"
-                     "[debug]\n"
-                     "Enable=true\n"
-                     "\n"
-                     "[security]\n"
-                     "#DisallowTCP=true\n"
-                     "#AllowRemoteAutoLogin=false\n"))
-  `(("gdm" ,(file-union
-             "gdm"
-             `(("custom.conf" ,gdm-configuration-file))))))
+  (gnome-shell-assets gdm-configuration-gnome-shell-assets
+                      (default (list adwaita-icon-theme font-cantarell)))
+  (xorg-configuration gdm-configuration-xorg
+                      (default (xorg-configuration)))
+  (x-session gdm-configuration-x-session
+             (default (xinitrc))))
+
+(define (gdm-configuration-file config)
+  (mixed-text-file "gdm-custom.conf"
+                   "[daemon]\n"
+                   "#User=gdm\n"
+                   "#Group=gdm\n"
+                   (if (gdm-configuration-auto-login? config)
+                       (string-append
+                        "AutomaticLoginEnable=true\n"
+                        "AutomaticLogin="
+                        (or (gdm-configuration-default-user config)
+                            (error "missing default user for auto-login"))
+                        "\n")
+                       (string-append
+                        "AutomaticLoginEnable=false\n"
+                        "#AutomaticLogin=\n"))
+                   "#TimedLoginEnable=false\n"
+                   "#TimedLogin=\n"
+                   "#TimedLoginDelay=0\n"
+                   ;; Disable initial system setup inside GDM.
+                   ;; Whatever settings are set there should already be
+                   ;; taken care of through `guix system'.
+                   ;; See also
+                   ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=39281>.
+                   "InitialSetupEnable=false\n"
+                   ;; Enable me once X is working.
+                   "WaylandEnable=false\n"
+                   "\n"
+                   "[debug]\n"
+                   "Enable=" (if (gdm-configuration-debug? config)
+                                 "true"
+                                 "false") "\n"
+                   "\n"
+                   "[security]\n"
+                   "#DisallowTCP=true\n"
+                   "#AllowRemoteAutoLogin=false\n"))
 
 (define (gdm-pam-service config)
   "Return a PAM service for @command{gdm}."
   (list
    (pam-service
-    (inherit (unix-pam-service "gdm-autologin"))
+    (inherit (unix-pam-service "gdm-autologin"
+                               #:login-uid? #t))
     (auth (list (pam-entry
-                 (control "[success=ok default=1]")
+                 (control "optional")
                  (module (file-append (gdm-configuration-gdm config)
                                       "/lib/security/pam_gdm.so")))
                 (pam-entry
@@ -680,56 +937,77 @@ makes the good ol' XlockMore usable."
     (auth (list (pam-entry
                  (control "required")
                  (module "pam_permit.so")))))
-   (unix-pam-service
-    "gdm-password"
-    #:allow-empty-passwords? (gdm-configuration-allow-empty-passwords? config)
-    #:allow-root? (gdm-configuration-allow-root? config))))
+   (unix-pam-service "gdm-password"
+                     #:login-uid? #t
+                     #:allow-empty-passwords?
+                     (gdm-configuration-allow-empty-passwords? config))))
 
 (define (gdm-shepherd-service config)
   (list (shepherd-service
          (documentation "Xorg display server (GDM)")
          (provision '(xorg-server))
          (requirement '(dbus-system user-processes host-name udev))
-         ;; While this service isn't working properly, turn off auto-start.
-         (auto-start? #f)
          (start #~(lambda ()
                     (fork+exec-command
                      (list #$(file-append (gdm-configuration-gdm config)
                                           "/bin/gdm"))
                      #:environment-variables
                      (list (string-append
+                            "GDM_CUSTOM_CONF="
+                            #$(gdm-configuration-file config))
+                           (string-append
+                            "GDM_DBUS_DAEMON="
+                            #$(gdm-configuration-dbus-daemon config))
+                           (string-append
                             "GDM_X_SERVER="
-                            #$(gdm-configuration-x-server config))
-                           ;; XXX: GDM requires access to a handful of
-                           ;; programs and components from Gnome (gnome-shell,
-                           ;; dbus, and gnome-session among others). The
-                           ;; following variables only work provided Gnome is
-                           ;; installed.
-                           "XDG_DATA_DIRS=/run/current-system/profile/share"
-                           "PATH=/run/current-system/profile/bin"))))
+                            #$(xorg-wrapper
+                               (gdm-configuration-xorg config)))
+                           (string-append
+                            "GDM_X_SESSION="
+                            #$(gdm-configuration-x-session config))
+                           (string-append
+                            "XDG_DATA_DIRS="
+                            ((lambda (ls) (string-join ls ":"))
+                             (map (lambda (path)
+                                    (string-append path "/share"))
+                                  ;; XXX: Remove gnome-shell below when GDM
+                                  ;; can depend on GNOME Shell directly.
+                                  (cons #$gnome-shell
+                                        '#$(gdm-configuration-gnome-shell-assets
+                                            config)))))))))
          (stop #~(make-kill-destructor))
          (respawn? #t))))
 
 (define gdm-service-type
-  (service-type (name 'gdm)
-                (extensions
-                 (list (service-extension shepherd-root-service-type
-                                          gdm-shepherd-service)
-                       (service-extension account-service-type
-                                          (const %gdm-accounts))
-                       (service-extension pam-root-service-type
-                                          gdm-pam-service)
-                       (service-extension etc-service-type
-                                          gdm-etc-service)
-                       (service-extension dbus-root-service-type
-                                          (compose list gdm-configuration-gdm))))))
-
-;; This service isn't working yet; it gets as far as starting to run the
-;; greeter from gnome-shell but doesn't get any further.  It is here because
-;; it doesn't hurt anyone and perhaps it inspires someone to fix it :)
-(define* (gdm-service #:key (gdm gdm)
-                       (allow-empty-passwords? #t)
-                       (x-server (xorg-wrapper)))
+  (handle-xorg-configuration gdm-configuration
+    (service-type (name 'gdm)
+                  (extensions
+                   (list (service-extension shepherd-root-service-type
+                                            gdm-shepherd-service)
+                         (service-extension activation-service-type
+                                            (const %gdm-activation))
+                         (service-extension account-service-type
+                                            (const %gdm-accounts))
+                         (service-extension pam-root-service-type
+                                            gdm-pam-service)
+                         (service-extension profile-service-type
+                                            gdm-configuration-gnome-shell-assets)
+                         (service-extension dbus-root-service-type
+                                            (compose list
+                                                     gdm-configuration-gdm))
+                         (service-extension localed-service-type
+                                            (compose
+                                             xorg-configuration-keyboard-layout
+                                             gdm-configuration-xorg))))
+                  (default-value (gdm-configuration))
+                  (description
+                   "Run the GNOME Desktop Manager (GDM), a program that allows
+you to log in in a graphical session, whether or not you use GNOME."))))
+
+(define-deprecated (gdm-service #:key (gdm gdm)
+                                (allow-empty-passwords? #t)
+                                (x-server (xorg-wrapper)))
+  gdm-service-type
   "Return a service that spawns the GDM graphical login manager, which in turn
 starts the X display server with @var{X}, a command as returned by
 @code{xorg-wrapper}.
@@ -752,7 +1030,16 @@ password."
   (service gdm-service-type
            (gdm-configuration
             (gdm gdm)
-            (allow-empty-passwords? allow-empty-passwords?)
-            (x-server x-server))))
+            (allow-empty-passwords? allow-empty-passwords?))))
+
+(define* (set-xorg-configuration config
+                                 #:optional
+                                 (login-manager-service-type
+                                  gdm-service-type))
+  "Tell the log-in manager (of type @var{login-manager-service-type}) to use
+@var{config}, an <xorg-configuration> record."
+  (simple-service 'set-xorg-configuration
+                  login-manager-service-type
+                  config))
 
 ;;; xorg.scm ends here