GNU bug report logs - #77806
elogind behavior changed: power key turns computer off

Previous Next

Package: guix;

Reported by: Ludovic Courtès <ludo <at> gnu.org>

Date: Mon, 14 Apr 2025 16:48:02 UTC

Severity: important

Done: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>

Bug is archived. No further changes may be made.

To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 77806 in the body.
You can then email your comments to 77806 AT debbugs.gnu.org in the normal way.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to maxim.cournoyer <at> gmail.com, bug-guix <at> gnu.org:
bug#77806; Package guix. (Mon, 14 Apr 2025 16:48:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Ludovic Courtès <ludo <at> gnu.org>:
New bug report received and forwarded. Copy sent to maxim.cournoyer <at> gmail.com, bug-guix <at> gnu.org. (Mon, 14 Apr 2025 16:48:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: bug-guix <at> gnu.org
Subject: elogind behavior changed: power key turns computer off
Date: Mon, 14 Apr 2025 18:46:33 +0200
Hello,

Probably related to the elogind upgrade in
098b5cdf9c6841df0b3c086974eef7e13fd23b36, I noticed today that pressing
the power button on my laptop would turn it off instead of putting it
into software suspend as it used to do:

--8<---------------cut here---------------start------------->8---
2025-04-14 17:59:53 localhost elogind[290]: Power key pressed short.
2025-04-14 17:59:53 localhost elogind[290]: Powering off...
2025-04-14 17:59:53 localhost elogind[290]: System is powering down.
2025-04-14 17:59:54 localhost shepherd[1]: Stopping service root...
2025-04-14 17:59:54 localhost shepherd[1]: Exiting shepherd...
2025-04-14 17:59:54 localhost shepherd[1]: Stopping service swap-/swap...
2025-04-14 17:59:54 localhost shepherd[1]: Service swap-/swap stopped.
2025-04-14 17:59:54 localhost shepherd[1]: Service swap-/swap is now stopped.
--8<---------------cut here---------------end--------------->8---

Previously I had:

--8<---------------cut here---------------start------------->8---
2025-04-08 18:07:51 localhost elogind[282]: Power key pressed short.
2025-04-08 18:07:51 localhost elogind[282]: Suspending...
2025-04-08 18:07:51 localhost NetworkManager[311]: <info>  [1744128471.3311] manager: sleep: sleep requested (sleeping: no  enabled: yes)
2025-04-08 18:07:51 localhost NetworkManager[311]: <info>  [1744128471.3332] manager: NetworkManager state is now ASLEEP
2025-04-08 18:07:51 localhost elogind[282]: Entering sleep state 'suspend'...
2025-04-08 18:07:51 localhost linux: [82930.856145] PM: suspend entry (deep)
--8<---------------cut here---------------end--------------->8---

Yet the config reads this:

--8<---------------cut here---------------start------------->8---
$ grep -i powerkey $(sudo herd configuration elogind)
HandlePowerKey=suspend
PowerKeyIgnoreInhibited=no
--8<---------------cut here---------------end--------------->8---

Thoughts?

Ludo’.




Information forwarded to bug-guix <at> gnu.org:
bug#77806; Package guix. (Wed, 16 Apr 2025 03:04:02 GMT) Full text and rfc822 format available.

Message #8 received at 77806 <at> debbugs.gnu.org (full text, mbox):

From: Bas Alberts <bas <at> anti.computer>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, 77806 <at> debbugs.gnu.org
Subject: Re: bug#77806: elogind behavior changed: power key turns computer off
Date: Wed, 16 Apr 2025 03:03:28 +0000
On Mon, 14 Apr 2025 18:46:33 +0200, 
Ludovic Courtès <ludo <at> gnu.org> wrote:
>
> Yet the config reads this:
>
> $ grep -i powerkey $(sudo herd configuration elogind)
> HandlePowerKey=suspend
> PowerKeyIgnoreInhibited=no

I'm having the same issue.

My config is also unchanged but after this week's upgrades elogind no
longer respects HandleLidSwitch=hibernate and my laptop defaults to
s2idle which is prohibitively battery consuming on my hardware.

λ ~ › grep -i HandleLidSwitch= $(sudo herd configuration elogind)
HandleLidSwitch=hibernate

... on lid close:

[ 3603.354720] PM: suspend entry (s2idle)
[ 3603.359069] Filesystems sync: 0.004 seconds
[ 3603.368060] Freezing user space processes

...

Kind regards,
Bas





Severity set to 'important' from 'normal' Request was from Ludovic Courtès <ludo <at> gnu.org> to control <at> debbugs.gnu.org. (Fri, 18 Apr 2025 20:51:07 GMT) Full text and rfc822 format available.

Information forwarded to bug-guix <at> gnu.org:
bug#77806; Package guix. (Thu, 24 Apr 2025 05:30:02 GMT) Full text and rfc822 format available.

Message #13 received at 77806 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: Bas Alberts <bas <at> anti.computer>
Cc: Ludovic Courtès <ludo <at> gnu.org>, 77806 <at> debbugs.gnu.org
Subject: Re: bug#77806: elogind behavior changed: power key turns computer off
Date: Thu, 24 Apr 2025 14:29:24 +0900
Hi,

Thanks for the report.

It looks like nowadays the sleep settings are expected to be in a
distinct sleep.conf file [0]; I haven't verified yet but I suspect the
[Sleep] section of our logind.conf is no longer honored.

[0]  https://github.com/elogind/elogind/issues/293

-- 
Thanks,
Maxim




Information forwarded to liliana.prikler <at> gmail.com, maxim.cournoyer <at> gmail.com, noelopez <at> free.fr, vivien <at> planete-kraus.eu, bug-guix <at> gnu.org:
bug#77806; Package guix. (Thu, 24 Apr 2025 10:40:09 GMT) Full text and rfc822 format available.

Message #16 received at 77806 <at> debbugs.gnu.org (full text, mbox):

From: Josselin Poiret <dev <at> jpoiret.xyz>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Bas Alberts <bas <at> anti.computer>
Cc: Ludovic Courtès <ludo <at> gnu.org>,
 Josselin Poiret <dev <at> jpoiret.xyz>, 77806 <at> debbugs.gnu.org
Subject: [PATCH] gnu: elogind-service-type: Put user configuration in drop-in
 dirs.
Date: Thu, 24 Apr 2025 12:38:41 +0200
From: Josselin Poiret <dev <at> jpoiret.xyz>

* gnu/services/desktop.scm (elogind-configuration-files): Renamed from
elogind-configuration-file. Split sections [Login] and [Sleep] in two files.
(elogind-shepherd-service): Remove useless environment variable.

Change-Id: Ibb4db04152c397c1ed4a35118129a2860ac9c2b5
---
Hello everyone,

Here's a fix I just tested.  I chose to use drop-in directories instead of the
main conf file as it uses an hardcoded PKGCONFDIR.

Best,
Josselin

 gnu/services/desktop.scm | 110 ++++++++++++++++++++-------------------
 1 file changed, 57 insertions(+), 53 deletions(-)

diff --git a/gnu/services/desktop.scm b/gnu/services/desktop.scm
index fe034cfa8f4..628879c73dd 100644
--- a/gnu/services/desktop.scm
+++ b/gnu/services/desktop.scm
@@ -1101,7 +1101,7 @@ (define-record-type* <elogind-configuration> elogind-configuration
   (broadcast-suspend-interrupts?    elogind-broadcast-suspend-interrupts?
                                     (default #t)))
 
-(define (elogind-configuration-file config)
+(define (elogind-configuration-files config)
   (define (yesno x)
     (match x
       (#t "yes")
@@ -1152,55 +1152,59 @@ (define (elogind-configuration-file config)
            (string-append str "\n")))))
   (define-syntax-rule (ini-file config file clause ...)
     (plain-file file (string-append (ini-file-clause config clause) ...)))
-  (ini-file
-   config "logind.conf"
-   "[Login]"
-   ("KillUserProcesses" (yesno elogind-kill-user-processes?))
-   ("KillOnlyUsers" (user-name-list elogind-kill-only-users))
-   ("KillExcludeUsers" (user-name-list elogind-kill-exclude-users))
-   ("InhibitDelayMaxSec" (non-negative-integer elogind-inhibit-delay-max-seconds))
-   ("HandlePowerKey" (handle-action elogind-handle-power-key))
-   ("HandleSuspendKey" (handle-action elogind-handle-suspend-key))
-   ("HandleHibernateKey" (handle-action elogind-handle-hibernate-key))
-   ("HandleLidSwitch" (handle-action elogind-handle-lid-switch))
-   ("HandleLidSwitchDocked" (handle-action elogind-handle-lid-switch-docked))
-   ("HandleLidSwitchExternalPower" (handle-action elogind-handle-lid-switch-external-power))
-   ("PowerKeyIgnoreInhibited" (yesno elogind-power-key-ignore-inhibited?))
-   ("SuspendKeyIgnoreInhibited" (yesno elogind-suspend-key-ignore-inhibited?))
-   ("HibernateKeyIgnoreInhibited" (yesno elogind-hibernate-key-ignore-inhibited?))
-   ("LidSwitchIgnoreInhibited" (yesno elogind-lid-switch-ignore-inhibited?))
-   ("HoldoffTimeoutSec" (non-negative-integer elogind-holdoff-timeout-seconds))
-   ("IdleAction" (handle-action elogind-idle-action))
-   ("IdleActionSec" (non-negative-integer elogind-idle-action-seconds))
-   ("RuntimeDirectorySize"
-    (identity
-     (lambda (config)
-       (match (elogind-runtime-directory-size-percent config)
-         (#f (non-negative-integer (elogind-runtime-directory-size config)))
-         (percent (string-append (non-negative-integer percent) "%"))))))
-   ("RemoveIPC" (yesno elogind-remove-ipc?))
-   "[Sleep]"
-   ("SuspendState" (sleep-list elogind-suspend-state))
-   ("SuspendMode" (sleep-list elogind-suspend-mode))
-   ("HibernateState" (sleep-list elogind-hibernate-state))
-   ("HibernateMode" (sleep-list elogind-hibernate-mode))
-   ("HybridSleepState" (sleep-list elogind-hybrid-sleep-state))
-   ("HybridSleepMode" (sleep-list elogind-hybrid-sleep-mode))
-   ("HibernateDelaySec" (maybe-non-negative-integer elogind-hibernate-delay-seconds))
-   ("SuspendEstimationSec" (maybe-non-negative-integer elogind-suspend-estimation-seconds))
-   ("AllowPowerOffInterrupts" (yesno elogind-allow-power-off-interrupts?))
-   ("AllowSuspendInterrupts" (yesno elogind-allow-suspend-interrupts?))
-   ("BroadcastPowerOffInterrupts" (yesno elogind-broadcast-power-off-interrupts?))
-   ("BroadcastSuspendInterrupts" (yesno elogind-broadcast-suspend-interrupts?))))
+  `(("logind.conf.d/logind.conf" .
+     ,(ini-file
+       config "logind.conf"
+       "[Login]"
+       ("KillUserProcesses" (yesno elogind-kill-user-processes?))
+       ("KillOnlyUsers" (user-name-list elogind-kill-only-users))
+       ("KillExcludeUsers" (user-name-list elogind-kill-exclude-users))
+       ("InhibitDelayMaxSec" (non-negative-integer elogind-inhibit-delay-max-seconds))
+       ("HandlePowerKey" (handle-action elogind-handle-power-key))
+       ("HandleSuspendKey" (handle-action elogind-handle-suspend-key))
+       ("HandleHibernateKey" (handle-action elogind-handle-hibernate-key))
+       ("HandleLidSwitch" (handle-action elogind-handle-lid-switch))
+       ("HandleLidSwitchDocked" (handle-action elogind-handle-lid-switch-docked))
+       ("HandleLidSwitchExternalPower" (handle-action elogind-handle-lid-switch-external-power))
+       ("PowerKeyIgnoreInhibited" (yesno elogind-power-key-ignore-inhibited?))
+       ("SuspendKeyIgnoreInhibited" (yesno elogind-suspend-key-ignore-inhibited?))
+       ("HibernateKeyIgnoreInhibited" (yesno elogind-hibernate-key-ignore-inhibited?))
+       ("LidSwitchIgnoreInhibited" (yesno elogind-lid-switch-ignore-inhibited?))
+       ("HoldoffTimeoutSec" (non-negative-integer elogind-holdoff-timeout-seconds))
+       ("IdleAction" (handle-action elogind-idle-action))
+       ("IdleActionSec" (non-negative-integer elogind-idle-action-seconds))
+       ("RuntimeDirectorySize"
+        (identity
+         (lambda (config)
+           (match (elogind-runtime-directory-size-percent config)
+             (#f (non-negative-integer (elogind-runtime-directory-size config)))
+             (percent (string-append (non-negative-integer percent) "%"))))))
+       ("RemoveIPC" (yesno elogind-remove-ipc?))))
+    ("sleep.conf.d/sleep.conf" .
+     ,(ini-file
+       config "sleep.conf"
+       "[Sleep]"
+       ("SuspendState" (sleep-list elogind-suspend-state))
+       ("SuspendMode" (sleep-list elogind-suspend-mode))
+       ("HibernateState" (sleep-list elogind-hibernate-state))
+       ("HibernateMode" (sleep-list elogind-hibernate-mode))
+       ("HybridSleepState" (sleep-list elogind-hybrid-sleep-state))
+       ("HybridSleepMode" (sleep-list elogind-hybrid-sleep-mode))
+       ("HibernateDelaySec" (maybe-non-negative-integer elogind-hibernate-delay-seconds))
+       ("SuspendEstimationSec" (maybe-non-negative-integer elogind-suspend-estimation-seconds))
+       ("AllowPowerOffInterrupts" (yesno elogind-allow-power-off-interrupts?))
+       ("AllowSuspendInterrupts" (yesno elogind-allow-suspend-interrupts?))
+       ("BroadcastPowerOffInterrupts" (yesno elogind-broadcast-power-off-interrupts?))
+       ("BroadcastSuspendInterrupts" (yesno elogind-broadcast-suspend-interrupts?))))))
 
 (define (elogind-etc-directory config)
   "Return the /etc/elogind directory for CONFIG."
-  (with-imported-modules (source-module-closure '((guix build utils)))
+  (with-imported-modules (source-module-closure '((guix build utils) (ice-9 match) (srfi srfi-1)))
     (computed-file
      "etc-elogind"
 
      #~(begin
-         (use-modules (guix build utils))
+         (use-modules (guix build utils) (ice-9 match) (srfi srfi-1))
 
          (define sleep-directory (string-append #$output "/system-sleep/"))
          (define shutdown-directory (string-append #$output "/system-shutdown/"))
@@ -1218,7 +1222,14 @@ (define (elogind-etc-directory config)
                    '#$(elogind-system-sleep-hook-files config))
          (for-each (lambda (f)
                      (copy-script f shutdown-directory))
-                   '#$(elogind-system-shutdown-hook-files config))))))
+                   '#$(elogind-system-shutdown-hook-files config))
+         #$@(append-map
+             (match-lambda
+               ((name . file)
+                (list
+                 #~(mkdir-p (dirname (string-append #$output "/" #$name)))
+                 #~(copy-file #$file (string-append #$output "/" #$name)))))
+             (elogind-configuration-files config))))))
 
 (define (elogind-dbus-service config)
   "Return a @file{org.freedesktop.login1.service} file that tells D-Bus how to
@@ -1301,20 +1312,13 @@ (define (pam-extension-procedure config)
 
 (define (elogind-shepherd-service config)
   "Return a Shepherd service to start elogind according to @var{config}."
-  (define config-file
-    (elogind-configuration-file config))
-
   (list (shepherd-service
          (requirement '(user-processes dbus-system))
          (provision '(elogind))
          (start #~(make-forkexec-constructor
                    (list #$(file-append (elogind-package config)
-                                        "/libexec/elogind/elogind"))
-                   #:environment-variables
-                   (list (string-append "ELOGIND_CONF_FILE="
-                                        #$config-file))))
-         (stop #~(make-kill-destructor))
-         (actions (list (shepherd-configuration-action config-file))))))
+                                        "/libexec/elogind/elogind"))))
+         (stop #~(make-kill-destructor)))))
 
 (define elogind-service-type
   (service-type (name 'elogind)

base-commit: a6c96cdb7060ccfc2d0fe27a45b9bcc9590aedd6
prerequisite-patch-id: 1ee329e14fdb8d1e35141e9b5972c5e9b8a2b252
-- 
2.49.0





Information forwarded to bug-guix <at> gnu.org:
bug#77806; Package guix. (Thu, 24 Apr 2025 21:40:04 GMT) Full text and rfc822 format available.

Message #19 received at 77806 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: Josselin Poiret <dev <at> jpoiret.xyz>
Cc: Bas Alberts <bas <at> anti.computer>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, 77806 <at> debbugs.gnu.org
Subject: Re: [PATCH] gnu: elogind-service-type: Put user configuration in
 drop-in dirs.
Date: Thu, 24 Apr 2025 23:13:58 +0200
Hey,

Josselin Poiret <dev <at> jpoiret.xyz> writes:

> From: Josselin Poiret <dev <at> jpoiret.xyz>
>
> * gnu/services/desktop.scm (elogind-configuration-files): Renamed from
> elogind-configuration-file. Split sections [Login] and [Sleep] in two files.
> (elogind-shepherd-service): Remove useless environment variable.
>
> Change-Id: Ibb4db04152c397c1ed4a35118129a2860ac9c2b5
> +  `(("logind.conf.d/logind.conf" .

[...]

> +    ("sleep.conf.d/sleep.conf" .

Should it be elogind/{logind.conf,sleep.conf}?

>  (define (elogind-etc-directory config)
>    "Return the /etc/elogind directory for CONFIG."
> -  (with-imported-modules (source-module-closure '((guix build utils)))
> +  (with-imported-modules (source-module-closure '((guix build utils) (ice-9 match) (srfi srfi-1)))

This is incorrect: it would import these two modules from the host
Guile into the build environment.

> +         #$@(append-map
> +             (match-lambda
> +               ((name . file)
> +                (list
> +                 #~(mkdir-p (dirname (string-append #$output "/" #$name)))
> +                 #~(copy-file #$file (string-append #$output "/" #$name)))))
> +             (elogind-configuration-files config))))))

I’d rather avoid the list of gexps and instead write:

  #$(map (match-lambda
          ((target . file)
           #~(begin
               (mkdir-p …)
               (copy-file …))))
         …)

> -         (actions (list (shepherd-configuration-action config-file))))))

Too bad we’re losing this.  Perhaps we could have a custom
‘configuration’ action returning the list of config files?
Not a blocker anyway.

Thanks for fixing it, I’ve inadvertently turned off my laptop too many
times!

Ludo’.




Information forwarded to ludo <at> gnu.org, bas <at> anti.computer, maxim.cournoyer <at> gmail.com, dev <at> jpoiret.xyz, liliana.prikler <at> gmail.com, noelopez <at> free.fr, vivien <at> planete-kraus.eu, bug-guix <at> gnu.org:
bug#77806; Package guix. (Fri, 25 Apr 2025 02:12:03 GMT) Full text and rfc822 format available.

Message #22 received at 77806 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 77806 <at> debbugs.gnu.org
Cc: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Subject: [PATCH v2] services: elogind: Split sleep.conf and port to
 define-configuration.
Date: Fri, 25 Apr 2025 11:10:16 +0900
* gnu/services/desktop.scm (pascal-case): New procedure.
(<elogind-configuration>): Rewrite in terms of define-configuration.
(elogind-configuration-file): Delete.
(maybe-list-of-suspend-states?, maybe-list-of-suspend-modes?)
maybe-list-of-user-names?, maybe-boolean?maybe-package?)
(maybe-action?, maybe-percent?, maybe-list-of-strings?)
(maybe-list-of-hibernation-modes?, maybe-non-negative-integer?)
(non-negative-integer?, percent?, char-set:user-name, user-name?)
(list-of-user-names?, %elogind-actions, action?, %linux-suspend-states)
(string->symbol/maybe, suspend-state?, list-of-suspend-states?)
(%linux-suspend-modes, suspend-mode?, list-of-suspend-modes?)
(%linux-hibernation-modes, hibernation-mode?, list-of-hibernation-modes?)
(elogind-deprecated-empty-serializer, list-of-file-likes?)
(elogind-serialize-boolean, elogind-base-serializer, elogind-serialize-action)
(elogind-serialize-non-negative-integer, elogind-serialize-percent)
(elogind-list-serializer, elogind-serialize-list-of-strings)
(elogind-serialize-list-of-user-names, elogind-serialize-list-of-suspend-states)
(elogind-serialize-list-of-suspend-modes)
(elogind-serialize-list-of-hibernation-modes)
(%elogind-configuration-sleep-fields, logind.conf, sleep.conf): New procedures.
(elogind-etc-directory): Create the main configuration files there too.
(elogind-dbus-service): Adjust for package accessor name change.
(pam-extension-procedure, elogind-shepherd-service)
(elogind-service-type):  Likewise.
* doc/guix.texi (Desktop Services): Fully document configuration options.

Fixes: bug#77806
Change-Id: I8767891871d83e58d64995ec986a7d01689fa6d8
---
 doc/guix.texi            | 191 +++++++-----
 gnu/services/desktop.scm | 628 ++++++++++++++++++++++++++-------------
 2 files changed, 524 insertions(+), 295 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index be2fbbaf5bc..7b418a40892 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -26264,129 +26264,158 @@ Desktop Services
 @code{<elogind-configuration>} object.
 @end defvar
 
-@c TODO: field descriptions. This is best done by refactoring
-@c elogind-configuration to use define-configuration which embeds the
-@c descriptions in the code and then use configuration->documentation.
+@c Auto-generated via (configuration->documentation 'elogind-configuration).
+@c %start of fragment
+
 @deftp {Data Type} elogind-configuration
-Data type representing the configuration of @command{elogind}.
+Available @code{elogind-configuration} fields are:
 
 @table @asis
 @item @code{elogind} (default: @code{elogind}) (type: file-like)
-...
+The elogind package to use.
 
-@item @code{kill-user-processes?} (default: @code{#f}) (type: boolean)
-...
+@item @code{system-sleep-hook-files} (default: @code{()}) (type: list-of-file-likes)
+A list of executables (file-like objects) that will be installed into
+the @file{/etc/elogind/system-sleep} hook directory.  See `Hook
+directories' in the @samp{loginctl(1)} man page for more information.
 
-@item @code{kill-only-users} (default: @code{'()}) (type: list)
-...
+@item @code{system-shutdown-hook-files} (default: @code{()}) (type: list-of-file-likes)
+A list of executables (file-like objects) that will be installed into
+the @file{/etc/elogind/system-shutdown/} hook directory.
 
-@item @code{kill-exclude-users} (default: @code{'("root")}) (type: list-of-string)
-...
+@item @code{allow-power-off-interrupts?} (default: @code{#f}) (type: maybe-boolean)
+Whether the executables in elogind's hook directories (see above) can
+cause a power-off action to be cancelled (interrupted) by printing an
+appropriate error message to stdout.
 
-@item @code{inhibit-delay-max-seconds} (default: @code{5}) (type: integer)
-...
+@item @code{allow-suspend-interrupts?} (default: @code{#f}) (type: maybe-boolean)
+Likewise as the @code{allow-power-off-interrupts?} option, but for the
+suspend action.
 
-@item @code{handle-power-key} (default: @code{'poweroff}) (type: symbol)
-...
+@item @code{broadcast-power-off-interrupts?} (default: @code{#f}) (type: maybe-boolean)
+Whether an interrupt of a power-off action is broadcasted.
 
-@item @code{handle-suspend-key} (default: @code{'suspend}) (type: symbol)
-...
+@item @code{broadcast-suspend-interrupts?} (default: @code{#f}) (type: maybe-boolean)
+Whether an interrupt of a suspend action is broadcasted.
 
-@item @code{handle-hibernate-key} (default: @code{'hibernate}) (type: symbol)
-...
+@item @code{kill-user-processes?} (default: @code{#f}) (type: maybe-boolean)
+Whether the processes of a user should be killed when the user logs out.
 
-@item @code{handle-lid-switch} (default: @code{'suspend}) (type: symbol)
-...
+@item @code{kill-only-users} (type: maybe-list-of-user-names)
+Usernames whose processes should be killed, regardless the value of
+@code{kill-user-processes?}.
 
-@item @code{handle-lid-switch-docked} (default: @code{'ignore}) (type: symbol)
-...
+@item @code{kill-exclude-users} (default: @code{("root")}) (type: maybe-list-of-user-names)
+Usernames whose processes should @emph{not} be killed, regardless the
+value of @code{kill-user-processes?}.
 
-@item @code{handle-lid-switch-external-power} (default: @code{*unspecified*}) (type: symbol)
-...
+@item @code{inhibit-delay-max-seconds} (default: @code{5}) (type: maybe-non-negative-integer)
+The maximum time a system shutdown or sleep request is delayed due to an
+inhibitor lock of type delay being active before the inhibitor is
+ignored and the operation executes anyway.
 
-@item @code{power-key-ignore-inhibited?} (default: @code{#f}) (type: boolean)
-...
+@item @code{handle-power-key} (default: @code{poweroff}) (type: maybe-action)
+The action done when the power key is pressed.  The compiled default is
+@code{'poweroff}.
 
-@item @code{suspend-key-ignore-inhibited?} (default: @code{#f}) (type: boolean)
-...
+@item @code{handle-suspend-key} (default: @code{suspend}) (type: maybe-action)
+The action done when the suspend key is pressed.  The
 
-@item @code{hibernate-key-ignore-inhibited?} (default: @code{#f}) (type: boolean)
-...
+@item @code{handle-hibernate-key} (default: @code{hibernate}) (type: maybe-action)
+The action done when the hibernate key is pressed.
 
-@item @code{lid-switch-ignore-inhibited?} (default: @code{#t}) (type: boolean)
-...
+@item @code{handle-lid-switch} (default: @code{suspend}) (type: maybe-action)
+The action done when the lid is closed.
 
-@item @code{holdoff-timeout-seconds} (default: @code{30}) (type: integer)
-...
+@item @code{handle-lid-switch-docked} (default: @code{ignore}) (type: maybe-action)
+The action done when the lid is closed and the device docked.
 
-@item @code{idle-action} (default: @code{'ignore}) (type: symbol)
-...
+@item @code{handle-lid-switch-external-power} (default: @code{suspend}) (type: maybe-action)
+The action done when the lid is closed and the device is externally
+powered.
 
-@item @code{idle-action-seconds} (default: @code{(* 30 60)}) (type: integer)
-...
+@item @code{power-key-ignore-inhibited?} (default: @code{#f}) (type: maybe-boolean)
+Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+idle) when the power key is pressed.
 
-@item @code{runtime-directory-size-percent} (default: @code{10}) (type: integer)
-...
+@item @code{suspend-key-ignore-inhibited?} (default: @code{#f}) (type: maybe-boolean)
+Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+idle) when the suspend key is pressed.
 
-@item @code{runtime-directory-size} (default: @code{#f}) (type: integer)
-...
+@item @code{hibernate-key-ignore-inhibited?} (default: @code{#f}) (type: maybe-boolean)
+Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+idle) when the hibernate key is pressed.
 
-@item @code{remove-ipc?} (default: @code{#t}) (type: boolean)
-...
+@item @code{lid-switch-ignore-inhibited?} (default: @code{#f}) (type: maybe-boolean)
+Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+idle) when the lid is closed.
 
-@item @code{suspend-state} (default: @code{'("mem" "standby" "freeze")}) (type: list)
-...
+@item @code{holdoff-timeout-seconds} (default: @code{30}) (type: maybe-non-negative-integer)
+Specifies the number of seconds after system startup or system resume
+during which elogind will hold off on reacting to lid events.
 
-@item @code{suspend-mode} (default: @code{'()}) (type: list)
-...
+@item @code{idle-action} (default: @code{ignore}) (type: maybe-action)
+Action to take when the system is idle.
 
-@item @code{hibernate-state} (default: @code{'("disk")}) (type: list)
-...
+@item @code{idle-action-seconds} (type: maybe-non-negative-integer)
+The delay after which the action configured in @code{idle-action} is
+taken after the system is idle.
 
-@item @code{hibernate-mode} (default: @code{'("platform" "shutdown")}) (type: list)
-...
+@item @code{runtime-directory-size-percent} (type: maybe-percent)
+Set the size limit, in percent, on the @env{XDG_RUNTIME_DIR} runtime
+directory for each user who logs in.  This specifies the per-user size
+limit relative to the amount of physical @acronym{RAM,read access
+memory}.  This value takes precedence over that specified via
+@code{runtime-directory-size}.
 
-@item @code{hybrid-sleep-state} (default: @code{'("disk")}) (type: list)
-...
+@item @code{runtime-directory-size} (type: maybe-non-negative-integer)
+Set the size limit, in bytes, on the @env{XDG_RUNTIME_DIR} runtime
+directory for each user who logs in.
 
-@item @code{hybrid-sleep-mode} (default: @code{'("suspend" "platform" "shutdown")}) (type: list)
-...
+@item @code{remove-ipc?} (default: @code{#t}) (type: maybe-boolean)
+Whether @acronym{IPC,inter-process communication} objects belonging to
+the user shall be removed when the user fully logs out.
 
-@item @code{hibernate-delay-seconds} (default: @code{*unspecified*}) (type: integer)
-...
+@item @code{suspend-state} (default: @code{(mem standby freeze)}) (type: maybe-list-of-suspend-states)
+The suspend state values to be write to @file{/sys/power/state} by
+elogind when suspending the system.  They will be tried in turn, until
+one is written without error.
 
-@item @code{suspend-estimation-seconds} (default: @code{*unspecified*}) (type: integer)
-...
+@item @code{suspend-mode} (type: maybe-list-of-suspend-modes)
+The suspend mode values to write to @file{/sys/power/mem_sleep} by
+elogind when suspending the system.
 
-@item @code{system-sleep-hook-files} (default: @code{'()}) (type: list)
-A list of executables (file-like objects) that will be installed into
-the @file{/etc/elogind/system-sleep/} hook directory. For example:
+@item @code{suspend-estimation-seconds} (default: @code{3600}) (type: maybe-non-negative-integer)
+Cause the RTC alarm to wake the system after the specified time span to
+measure the system battery capacity level and estimate the battery
+discharging rate, which is used for estimating the time span until the
+system battery charge level goes down to 5%.  This option is only used
+by elogind when using the @code{'suspend-then-hibernate} action.
 
-@lisp
-(elogind-configuration
- (system-sleep-hook-files
-  (list (local-file "sleep-script"))))
-@end lisp
+@item @code{hibernate-mode} (default: @code{(platform shutdown)}) (type: maybe-list-of-hibernation-modes)
+The hibernation mode values to write to @file{/sys/power/disk} by
+elogind when hibernating the system.
 
-See `Hook directories' in the @code{loginctl(1)} man page for more information.
+@item @code{hibernate-delay-seconds} (type: maybe-non-negative-integer)
+The amount of time the system spends in suspend mode before the system
+is automatically put into hibernate mode.
 
-@item @code{system-shutdown-hook-files} (default: @code{'()}) (type: list)
-A list of executables (file-like objects) that will be installed into
-the @file{/etc/elogind/system-shutdown/} hook directory.
+@item @code{hibernate-state} (type: maybe-list-of-strings)
+Deprecated option.
 
-@item @code{allow-power-off-interrupts?} (default: @code{#f}) (type: boolean)
-@itemx @code{allow-suspend-interrupts?} (default: @code{#f}) (type: boolean)
-Whether the executables in elogind's hook directories (see above) can
-cause a power-off or suspend action to be cancelled (interrupted) by
-printing an appropriate error message to stdout.
+@item @code{hybrid-sleep-state} (type: maybe-list-of-strings)
+Deprecated option.
 
-@item @code{broadcast-power-off-interrupts?} (default: @code{#t}) (type: boolean)
-@itemx @code{broadcast-suspend-interrupts?} (default: @code{#t}) (type: boolean)
-Whether an interrupt of a power-off or suspend action is broadcasted.
+@item @code{hybrid-sleep-mode} (type: maybe-list-of-strings)
+Deprecated option.
 
 @end table
+
 @end deftp
 
+
+@c %end of fragment
+
 @defvar accountsservice-service-type
 Type for the service that runs AccountsService, a system service that can
 list available accounts, change their passwords, and so on.
diff --git a/gnu/services/desktop.scm b/gnu/services/desktop.scm
index fe034cfa8f4..e422fbc89df 100644
--- a/gnu/services/desktop.scm
+++ b/gnu/services/desktop.scm
@@ -37,6 +37,7 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services desktop)
+  #:use-module ((gnu home services utils) #:select (object->camel-case-string))
   #:use-module (gnu services)
   #:use-module (gnu services shepherd)
   #:use-module (gnu services base)
@@ -228,6 +229,8 @@ (define (package-direct-input-selector tree)
           (loop (cdr tree)
                 (car (assoc-ref (package-direct-inputs package)
                                 (car tree))))))))
+(define (pascal-case text)
+  (object->camel-case-string text 'upper))
 
 
 ;;;
@@ -1025,173 +1028,367 @@ (define gvfs-service-type
 ;;; Elogind login and seat management service.
 ;;;
 
-(define-record-type* <elogind-configuration> elogind-configuration
-  make-elogind-configuration
-  elogind-configuration?
-  (elogind                          elogind-package
-                                    (default elogind))
-  (kill-user-processes?             elogind-kill-user-processes?
-                                    (default #f))
-  (kill-only-users                  elogind-kill-only-users
-                                    (default '()))
-  (kill-exclude-users               elogind-kill-exclude-users
-                                    (default '("root")))
-  (inhibit-delay-max-seconds        elogind-inhibit-delay-max-seconds
-                                    (default 5))
-  (handle-power-key                 elogind-handle-power-key
-                                    (default 'poweroff))
-  (handle-suspend-key               elogind-handle-suspend-key
-                                    (default 'suspend))
-  (handle-hibernate-key             elogind-handle-hibernate-key
-                                    (default 'hibernate))
-  (handle-lid-switch                elogind-handle-lid-switch
-                                    (default 'suspend))
-  (handle-lid-switch-docked         elogind-handle-lid-switch-docked
-                                    (default 'ignore))
-  (handle-lid-switch-external-power elogind-handle-lid-switch-external-power
-                                    (default *unspecified*))
-  (power-key-ignore-inhibited?      elogind-power-key-ignore-inhibited?
-                                    (default #f))
-  (suspend-key-ignore-inhibited?    elogind-suspend-key-ignore-inhibited?
-                                    (default #f))
-  (hibernate-key-ignore-inhibited?  elogind-hibernate-key-ignore-inhibited?
-                                    (default #f))
-  (lid-switch-ignore-inhibited?     elogind-lid-switch-ignore-inhibited?
-                                    (default #t))
-  (holdoff-timeout-seconds          elogind-holdoff-timeout-seconds
-                                    (default 30))
-  (idle-action                      elogind-idle-action
-                                    (default 'ignore))
-  (idle-action-seconds              elogind-idle-action-seconds
-                                    (default (* 30 60)))
-  (runtime-directory-size-percent   elogind-runtime-directory-size-percent
-                                    (default 10))
-  (runtime-directory-size           elogind-runtime-directory-size
-                                    (default #f))
-  (remove-ipc?                      elogind-remove-ipc?
-                                    (default #t))
-
-  (suspend-state                    elogind-suspend-state
-                                    (default '("mem" "standby" "freeze")))
-  (suspend-mode                     elogind-suspend-mode
-                                    (default '()))
-  (hibernate-state                  elogind-hibernate-state
-                                    (default '("disk")))
-  (hibernate-mode                   elogind-hibernate-mode
-                                    (default '("platform" "shutdown")))
-  (hybrid-sleep-state               elogind-hybrid-sleep-state
-                                    (default '("disk")))
-  (hybrid-sleep-mode                elogind-hybrid-sleep-mode
-                                    (default
-                                      '("suspend" "platform" "shutdown")))
-  (hibernate-delay-seconds          elogind-hibernate-delay-seconds
-                                    (default *unspecified*))
-  (suspend-estimation-seconds       elogind-suspend-estimation-seconds
-                                    (default *unspecified*))
-  (system-sleep-hook-files          elogind-system-sleep-hook-files
-                                    (default '()))
-  (system-shutdown-hook-files       elogind-system-shutdown-hook-files
-                                    (default '()))
-  (allow-power-off-interrupts?      elogind-allow-power-off-interrupts?
-                                    (default #f))
-  (allow-suspend-interrupts?        elogind-allow-suspend-interrupts?
-                                    (default #f))
-  (broadcast-power-off-interrupts?  elogind-broadcast-power-off-interrupts?
-                                    (default #t))
-  (broadcast-suspend-interrupts?    elogind-broadcast-suspend-interrupts?
-                                    (default #t)))
-
-(define (elogind-configuration-file config)
-  (define (yesno x)
-    (match x
-      (#t "yes")
-      (#f "no")
-      (_ (error "expected #t or #f, instead got:" x))))
-  (define char-set:user-name
-    (string->char-set "abcdefghijklmnopqrstuvwxyz0123456789_-"))
-  (define (valid-list? l pred)
-    (and-map (lambda (x) (string-every pred x)) l))
-  (define (user-name-list users)
-    (unless (valid-list? users char-set:user-name)
-      (error "invalid user list" users))
-    (string-join users " "))
-  (define (enum val allowed)
-    (unless (memq val allowed)
-      (error "invalid value" val allowed))
-    (symbol->string val))
-  (define (non-negative-integer x)
-    (unless (exact-integer? x) (error "not an integer" x))
-    (when (negative? x) (error "negative number not allowed" x))
-    (number->string x))
-  (define (maybe-non-negative-integer x)
-    (or (and (unspecified? x) x)
-        (non-negative-integer x)))
-  (define handle-actions
-    '(ignore poweroff reboot halt kexec suspend hibernate hybrid-sleep suspend-then-hibernate lock))
-  (define (handle-action x)
-    (if (unspecified? x)
-        x                               ;let the unspecified value go through
-        (enum x handle-actions)))
-  (define (sleep-list tokens)
-    (unless (valid-list? tokens char-set:user-name)
-      (error "invalid sleep list" tokens))
-    (string-join tokens " "))
-  (define-syntax ini-file-clause
-    (syntax-rules ()
-      ;; Produce an empty line when encountering an unspecified value.  This
-      ;; is better than an empty string value, which can, in some cases, cause
-      ;; warnings such as "Failed to parse handle action setting".
-      ((_ config (prop (parser getter)))
-       (let ((value (parser (getter config))))
-         (if (unspecified? value)
-             ""
-             (string-append prop "=" value "\n"))))
-      ((_ config str)
-       (if (unspecified? str)
-           ""
-           (string-append str "\n")))))
-  (define-syntax-rule (ini-file config file clause ...)
-    (plain-file file (string-append (ini-file-clause config clause) ...)))
-  (ini-file
-   config "logind.conf"
-   "[Login]"
-   ("KillUserProcesses" (yesno elogind-kill-user-processes?))
-   ("KillOnlyUsers" (user-name-list elogind-kill-only-users))
-   ("KillExcludeUsers" (user-name-list elogind-kill-exclude-users))
-   ("InhibitDelayMaxSec" (non-negative-integer elogind-inhibit-delay-max-seconds))
-   ("HandlePowerKey" (handle-action elogind-handle-power-key))
-   ("HandleSuspendKey" (handle-action elogind-handle-suspend-key))
-   ("HandleHibernateKey" (handle-action elogind-handle-hibernate-key))
-   ("HandleLidSwitch" (handle-action elogind-handle-lid-switch))
-   ("HandleLidSwitchDocked" (handle-action elogind-handle-lid-switch-docked))
-   ("HandleLidSwitchExternalPower" (handle-action elogind-handle-lid-switch-external-power))
-   ("PowerKeyIgnoreInhibited" (yesno elogind-power-key-ignore-inhibited?))
-   ("SuspendKeyIgnoreInhibited" (yesno elogind-suspend-key-ignore-inhibited?))
-   ("HibernateKeyIgnoreInhibited" (yesno elogind-hibernate-key-ignore-inhibited?))
-   ("LidSwitchIgnoreInhibited" (yesno elogind-lid-switch-ignore-inhibited?))
-   ("HoldoffTimeoutSec" (non-negative-integer elogind-holdoff-timeout-seconds))
-   ("IdleAction" (handle-action elogind-idle-action))
-   ("IdleActionSec" (non-negative-integer elogind-idle-action-seconds))
-   ("RuntimeDirectorySize"
-    (identity
-     (lambda (config)
-       (match (elogind-runtime-directory-size-percent config)
-         (#f (non-negative-integer (elogind-runtime-directory-size config)))
-         (percent (string-append (non-negative-integer percent) "%"))))))
-   ("RemoveIPC" (yesno elogind-remove-ipc?))
-   "[Sleep]"
-   ("SuspendState" (sleep-list elogind-suspend-state))
-   ("SuspendMode" (sleep-list elogind-suspend-mode))
-   ("HibernateState" (sleep-list elogind-hibernate-state))
-   ("HibernateMode" (sleep-list elogind-hibernate-mode))
-   ("HybridSleepState" (sleep-list elogind-hybrid-sleep-state))
-   ("HybridSleepMode" (sleep-list elogind-hybrid-sleep-mode))
-   ("HibernateDelaySec" (maybe-non-negative-integer elogind-hibernate-delay-seconds))
-   ("SuspendEstimationSec" (maybe-non-negative-integer elogind-suspend-estimation-seconds))
-   ("AllowPowerOffInterrupts" (yesno elogind-allow-power-off-interrupts?))
-   ("AllowSuspendInterrupts" (yesno elogind-allow-suspend-interrupts?))
-   ("BroadcastPowerOffInterrupts" (yesno elogind-broadcast-power-off-interrupts?))
-   ("BroadcastSuspendInterrupts" (yesno elogind-broadcast-suspend-interrupts?))))
+;;; Elogind configuration types.
+(define-maybe boolean
+  (prefix elogind-))
+
+(define (non-negative-integer? x)
+  (and (exact-integer? x)
+       (not (negative? x))))
+
+(define-maybe non-negative-integer
+  (prefix elogind-))
+
+(define (percent? x)
+  (and (non-negative-integer? x)
+       (>= x 0)
+       (<= x 100)))
+
+(define-maybe percent
+  (prefix elogind-))
+
+(define char-set:user-name
+  (string->char-set "abcdefghijklmnopqrstuvwxyz0123456789_-"))
+
+(define (user-name? x)
+  (string-every char-set:user-name x))
+
+(define list-of-user-names?
+  (list-of user-name?))
+
+(define-maybe list-of-user-names
+  (prefix elogind-))
+
+(define %elogind-actions
+  '( ignore poweroff reboot halt kexec suspend hibernate hybrid-sleep
+     suspend-then-hibernate lock factory-reset))
+
+(define (action? x)
+  (member x %elogind-actions))
+
+(define-maybe action
+  (prefix elogind-))
+
+(define %linux-suspend-states
+  ;; The possible suspend states supported by the Linux kernel.
+  ;; See (info "(linux) Basic sysfs Interfaces for System Suspend and Hibernation").
+  '(disk standby freeze mem))
+
+(define (string->symbol/maybe x)
+  (if (string? x)
+      (string->symbol x)
+      x))
+
+(define (suspend-state? x)
+  (member (string->symbol/maybe x) %linux-suspend-states))
+
+(define list-of-suspend-states?
+  (list-of suspend-state?))
+
+(define-maybe list-of-suspend-states
+  (prefix elogind-))
+
+(define %linux-suspend-modes
+  ;; The possible suspend state variants supported by the Linux kernel.
+  ;; See (info "(linux) Basic sysfs Interfaces for System Suspend and Hibernation").
+  '(s2idle shallow deep))
+
+(define (suspend-mode? x)
+  (member (string->symbol/maybe x) %linux-suspend-modes))
+
+(define list-of-suspend-modes?
+  (list-of suspend-mode?))
+
+(define-maybe list-of-suspend-modes
+  (prefix elogind-))
+
+(define %linux-hibernation-modes
+  ;; The possible hibernation operating modes supported by the Linux kernel.
+  ;; See (info "(linux) Basic sysfs Interfaces for System Suspend and Hibernation").
+  '(platform shutdown reboot suspend test_resume))
+
+(define (hibernation-mode? x)
+  (member (string->symbol/maybe x) %linux-hibernation-modes))
+
+(define list-of-hibernation-modes?
+  (list-of hibernation-mode?))
+
+(define-maybe list-of-hibernation-modes
+  (prefix elogind-))
+
+(define (elogind-deprecated-empty-serializer name value)
+  (when (maybe-value-set? value)
+    (warn-about-deprecation name #f
+                            #:replacement #f))
+  "")
+
+(define list-of-file-likes?
+  (list-of file-like?))
+
+(define-maybe list-of-strings
+  (prefix elogind-))
+
+;;; Elogind serializers.
+(define (elogind-serialize-boolean name value)
+  (let* ((name-str (symbol->string name))
+         (name (if (string-suffix? "?" name-str)
+                   (string-drop-right name-str 1)
+                   name-str)))
+    (format #f "~a=~:[no~;yes~]~%" (pascal-case name) value)))
+
+(define (elogind-base-serializer name value)
+  (let* ((name-str (symbol->string name))
+         (name (if (string-suffix? "seconds" name-str)
+                   (string-drop-right name-str 4) ;seconds -> sec
+                   name-str)))
+    (format #f "~a=~a~%" (pascal-case name) value)))
+
+(define elogind-serialize-action elogind-base-serializer)
+(define elogind-serialize-non-negative-integer elogind-base-serializer)
+(define elogind-serialize-percent elogind-base-serializer)
+
+(define (elogind-list-serializer name value)
+  (format #f "~a=~{~a~^ ~}~%" (pascal-case name) value))
+
+(define elogind-serialize-list-of-strings elogind-list-serializer)
+(define elogind-serialize-list-of-user-names elogind-list-serializer)
+(define elogind-serialize-list-of-suspend-states elogind-list-serializer)
+(define elogind-serialize-list-of-suspend-modes elogind-list-serializer)
+(define elogind-serialize-list-of-hibernation-modes elogind-list-serializer)
+
+;;; XXX: For backward-compatible/historical reasons, the configuration object
+;;; is flat, containing the fields of both the logind.conf and sleep.conf
+;;; files.  The list below contains the fields that should be serialized to
+;;; sleep.conf.
+(define %elogind-configuration-sleep-fields
+  '( suspend-state suspend-mode suspend-estimation-seconds
+     hibernate-mode hibernate-delay-seconds hibernate-state
+     hybrid-sleep-state hybrid-sleep-mode))
+
+(define-configuration elogind-configuration
+  (elogind
+   (file-like elogind)
+   "The elogind package to use."
+   (serializer empty-serializer))
+
+  (system-sleep-hook-files
+   (list-of-file-likes '())
+   "A list of executables (file-like objects) that will be installed into the
+@file{/etc/elogind/system-sleep} hook directory.  See `Hook directories' in
+the @samp{loginctl(1)} man page for more information."
+   (serializer empty-serializer))
+
+  (system-shutdown-hook-files
+   (list-of-file-likes '())
+   "A list of executables (file-like objects) that will be installed into the
+@file{/etc/elogind/system-shutdown/} hook directory."
+   (serializer empty-serializer))
+
+  (allow-power-off-interrupts?
+   (maybe-boolean #f)
+   "Whether the executables in elogind's hook directories (see above) can
+  cause a power-off action to be cancelled (interrupted) by printing an
+  appropriate error message to stdout.")
+
+  (allow-suspend-interrupts?
+   (maybe-boolean #f)
+   "Likewise as the @code{allow-power-off-interrupts?} option, but for the
+  suspend action.")
+
+  (broadcast-power-off-interrupts?
+   (maybe-boolean #f)
+   "Whether an interrupt of a power-off action is broadcasted.")
+
+  (broadcast-suspend-interrupts?
+   (maybe-boolean #f)
+   "Whether an interrupt of a suspend action is broadcasted.")
+
+  ;; logind.conf options.
+  (kill-user-processes?
+   (maybe-boolean #f)
+   "Whether the processes of a user should be killed when the user logs
+  out.")
+
+  (kill-only-users
+   maybe-list-of-user-names
+   "Usernames whose processes should be killed, regardless the value of
+  @code{kill-user-processes?}.")
+
+  (kill-exclude-users
+   (maybe-list-of-user-names (list "root"))
+   "Usernames whose processes should @emph{not} be killed, regardless the
+  value of @code{kill-user-processes?}.")
+
+  (inhibit-delay-max-seconds
+   (maybe-non-negative-integer 5)
+   "The maximum time a system shutdown or sleep request is delayed due to an
+  inhibitor lock of type delay being active before the inhibitor is ignored and
+  the operation executes anyway.")
+
+  (handle-power-key
+   (maybe-action 'poweroff)
+   "The action done when the power key is pressed.  The compiled default is
+  @code{'poweroff}.")
+
+  (handle-suspend-key
+   (maybe-action 'suspend)
+   "The action done when the suspend key is pressed.  The ")
+
+  (handle-hibernate-key
+   (maybe-action 'hibernate)
+   "The action done when the hibernate key is pressed.")
+
+  (handle-lid-switch
+   (maybe-action 'suspend)
+   "The action done when the lid is closed.")
+
+  (handle-lid-switch-docked
+   (maybe-action 'ignore)
+   "The action done when the lid is closed and the device docked.")
+
+  (handle-lid-switch-external-power
+   (maybe-action 'suspend)
+   "The action done when the lid is closed and the device is externally
+  powered.")
+
+  (power-key-ignore-inhibited?
+   (maybe-boolean #f)
+   "Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+  idle) when the power key is pressed.")
+
+  (suspend-key-ignore-inhibited?
+   (maybe-boolean #f)
+   "Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+  idle) when the suspend key is pressed.")
+
+  (hibernate-key-ignore-inhibited?
+   (maybe-boolean #f)
+   "Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+  idle) when the hibernate key is pressed.")
+
+  (lid-switch-ignore-inhibited?
+   (maybe-boolean #f)
+   "Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+  idle) when the lid is closed.")
+
+  (holdoff-timeout-seconds
+   (maybe-non-negative-integer 30)
+   "Specifies the number of seconds after system startup or system resume
+during which elogind will hold off on reacting to lid events.")
+
+  (idle-action
+   (maybe-action 'ignore)
+   "Action to take when the system is idle.")
+
+  (idle-action-seconds
+   maybe-non-negative-integer
+   "The delay after which the action configured in @code{idle-action} is
+taken after the system is idle.")
+
+  ;; XXX: Perhaps deprecate in the future and handle all the accepted forms
+  ;; directly in 'runtime-directory-size' instead.
+  (runtime-directory-size-percent
+   maybe-percent
+   "Set the size limit, in percent, on the @env{XDG_RUNTIME_DIR} runtime
+directory for each user who logs in.  This specifies the per-user size limit
+relative to the amount of physical @acronym{RAM, read access memory}.  This
+value takes precedence over that specified via @code{runtime-directory-size}."
+   (serializer empty-serializer))       ;special cased at serialization time
+
+  (runtime-directory-size
+   maybe-non-negative-integer
+   "Set the size limit, in bytes, on the @env{XDG_RUNTIME_DIR} runtime
+directory for each user who logs in."
+   (serializer empty-serializer))       ;special cased at serialization time
+
+  (remove-ipc?
+   (maybe-boolean #t)
+   "Whether @acronym{IPC, inter-process communication} objects belonging to
+the user shall be removed when the user fully logs out.")
+
+  ;; sleep.conf options.
+  ;; CAUTION: all sleep.conf option names must be registered in the above
+  ;; %ELOGIND-CONFIGURATION-SLEEP-FIELDS variable: otherwise they will be
+  ;; serialized to logind.conf instead of sleep.conf!
+  (suspend-state
+   (maybe-list-of-suspend-states '(mem standby freeze))
+   "The suspend state values to be write to @file{/sys/power/state} by elogind
+  when suspending the system.  They will be tried in turn, until one is written
+  without error.")
+
+  (suspend-mode
+   (maybe-list-of-suspend-modes)
+   "The suspend mode values to write to @file{/sys/power/mem_sleep} by elogind
+  when suspending the system.")
+
+  (suspend-estimation-seconds
+   (maybe-non-negative-integer (* 60 60)) ;1 hour
+   "Cause the RTC alarm to wake the system after the specified time span to
+  measure the system battery capacity level and estimate the battery discharging
+  rate, which is used for estimating the time span until the system battery
+  charge level goes down to 5%.  This option is only used by elogind when using
+  the @code{'suspend-then-hibernate} action.")
+
+  (hibernate-mode
+   (maybe-list-of-hibernation-modes '(platform shutdown))
+   "The hibernation mode values to write to @file{/sys/power/disk} by elogind
+  when hibernating the system.")
+
+  (hibernate-delay-seconds
+   maybe-non-negative-integer
+   "The amount of time the system spends in suspend mode before the system is
+  automatically put into hibernate mode.")
+
+  ;; TODO: Remove in May 2026.
+  (hibernate-state
+   maybe-list-of-strings
+   "Deprecated option."
+   (serializer elogind-deprecated-empty-serializer))
+
+  ;; TODO: Remove in May 2026.
+  (hybrid-sleep-state
+   maybe-list-of-strings
+   "Deprecated option."
+   (serializer elogind-deprecated-empty-serializer))
+
+  ;; TODO: Remove in May 2026.
+  (hybrid-sleep-mode
+   maybe-list-of-strings
+   "Deprecated option."
+   (serializer elogind-deprecated-empty-serializer))
+
+  (prefix elogind-))
+
+(define (logind.conf config)
+  (let ((logind-fields (remove (lambda (field)
+                                 (memq (configuration-field-name field)
+                                       %elogind-configuration-sleep-fields))
+                               elogind-configuration-fields)))
+    (match-record config <elogind-configuration>
+                  (runtime-directory-size-percent runtime-directory-size)
+      ;; Handle the special-cased
+      ;; runtime-directory-size-percent/runtime-directory-size options pair.
+      (let ((runtime-directory-size
+             (if (maybe-value-set? runtime-directory-size-percent)
+                 (format #f "~a%~%" runtime-directory-size-percent) ;10 -> 10%
+                 runtime-directory-size)))
+        (mixed-text-file
+         "logind.conf"
+         "[Login]\n"
+         (if (maybe-value-set? runtime-directory-size)
+             (list "RuntimeDirectorySize=" runtime-directory-size)
+             "")
+         (serialize-configuration config logind-fields))))))
+
+(define (sleep.conf config)
+  (let ((sleep-fields (filter (lambda (field)
+                                (memq (configuration-field-name field)
+                                      %elogind-configuration-sleep-fields))
+                              elogind-configuration-fields)))
+    (mixed-text-file
+     "sleep.conf"
+     "[Sleep]\n"
+     (serialize-configuration config sleep-fields))))
 
 (define (elogind-etc-directory config)
   "Return the /etc/elogind directory for CONFIG."
@@ -1213,12 +1410,19 @@ (define (elogind-etc-directory config)
              (chmod dest #o500)))
 
          (mkdir-p #$output)            ;in case neither directory gets created
+
+         ;; Symlink the main configuration files.
+         (with-directory-excursion #$output
+           (symlink #$(logind.conf config) "logind.conf")
+           (symlink #$(sleep.conf config) "sleep.conf"))
+
          (for-each (lambda (f)
                      (copy-script f sleep-directory))
-                   '#$(elogind-system-sleep-hook-files config))
+                   '#$(elogind-configuration-system-sleep-hook-files config))
          (for-each (lambda (f)
                      (copy-script f shutdown-directory))
-                   '#$(elogind-system-shutdown-hook-files config))))))
+                   '#$(elogind-configuration-system-shutdown-hook-files
+                       config))))))
 
 (define (elogind-dbus-service config)
   "Return a @file{org.freedesktop.login1.service} file that tells D-Bus how to
@@ -1231,7 +1435,7 @@ (define (elogind-dbus-service config)
   ;; <https://issues.guix.gnu.org/55444>.
 
   (define elogind
-    (elogind-package config))
+    (elogind-configuration-elogind config))
 
   (define wrapper
     (program-file "elogind-dbus-shepherd-sync"
@@ -1288,7 +1492,7 @@ (define (pam-extension-procedure config)
   (define pam-elogind
     (pam-entry
      (control "required")
-     (module (file-append (elogind-package config)
+     (module (file-append (elogind-configuration-elogind config)
                           "/lib/security/pam_elogind.so"))))
 
   (list (pam-extension
@@ -1301,56 +1505,52 @@ (define (pam-extension-procedure config)
 
 (define (elogind-shepherd-service config)
   "Return a Shepherd service to start elogind according to @var{config}."
-  (define config-file
-    (elogind-configuration-file config))
-
   (list (shepherd-service
          (requirement '(user-processes dbus-system))
          (provision '(elogind))
          (start #~(make-forkexec-constructor
-                   (list #$(file-append (elogind-package config)
-                                        "/libexec/elogind/elogind"))
-                   #:environment-variables
-                   (list (string-append "ELOGIND_CONF_FILE="
-                                        #$config-file))))
+                   (list #$(file-append (elogind-configuration-elogind config)
+                                        "/libexec/elogind/elogind"))))
          (stop #~(make-kill-destructor))
-         (actions (list (shepherd-configuration-action config-file))))))
+         (actions (list (shepherd-configuration-action
+                         "/etc/elogind/logind.conf"))))))
 
 (define elogind-service-type
-  (service-type (name 'elogind)
-                (extensions
-                 (list (service-extension dbus-root-service-type
-                                          elogind-dbus-service)
-                       (service-extension udev-service-type
-                                          (compose list elogind-package))
-                       (service-extension polkit-service-type
-                                          (compose list elogind-package))
-
-                       ;; Start elogind from the Shepherd rather than waiting
-                       ;; for bus activation.  This ensures that it can handle
-                       ;; events like lid close, etc.
-                       (service-extension shepherd-root-service-type
-                                          elogind-shepherd-service)
-
-                       ;; Provide the 'loginctl' command.
-                       (service-extension profile-service-type
-                                          (compose list elogind-package))
-
-                       ;; Extend PAM with pam_elogind.so.
-                       (service-extension pam-root-service-type
-                                          pam-extension-procedure)
-
-                       ;; Install sleep/shutdown hook files.
-                       (service-extension etc-service-type
-                                          (lambda (config)
-                                            `(("elogind"
-                                               ,(elogind-etc-directory config)))))
-
-                       ;; We need /run/user, /run/systemd, etc.
-                       (service-extension file-system-service-type
-                                          (const %elogind-file-systems))))
-                (default-value (elogind-configuration))
-                (description "Run the @command{elogind} login and seat
+  (service-type
+   (name 'elogind)
+   (extensions
+    (list (service-extension dbus-root-service-type
+                             elogind-dbus-service)
+          (service-extension udev-service-type
+                             (compose list elogind-configuration-elogind))
+          (service-extension polkit-service-type
+                             (compose list elogind-configuration-elogind))
+
+          ;; Start elogind from the Shepherd rather than waiting
+          ;; for bus activation.  This ensures that it can handle
+          ;; events like lid close, etc.
+          (service-extension shepherd-root-service-type
+                             elogind-shepherd-service)
+
+          ;; Provide the 'loginctl' command.
+          (service-extension profile-service-type
+                             (compose list elogind-configuration-elogind))
+
+          ;; Extend PAM with pam_elogind.so.
+          (service-extension pam-root-service-type
+                             pam-extension-procedure)
+
+          ;; Install sleep/shutdown hook files.
+          (service-extension etc-service-type
+                             (lambda (config)
+                               `(("elogind"
+                                  ,(elogind-etc-directory config)))))
+
+          ;; We need /run/user, /run/systemd, etc.
+          (service-extension file-system-service-type
+                             (const %elogind-file-systems))))
+   (default-value (elogind-configuration))
+   (description "Run the @command{elogind} login and seat
 management service.  The @command{elogind} service integrates with PAM to
 allow other system components to know the set of logged-in users as well as
 their session types (graphical, console, remote, etc.).  It can also clean up

base-commit: 5b981a2b5c9c880634d883f3b7ae078b4fa75fdb
-- 
2.49.0





Information forwarded to bug-guix <at> gnu.org:
bug#77806; Package guix. (Fri, 25 Apr 2025 08:15:02 GMT) Full text and rfc822 format available.

Message #25 received at 77806 <at> debbugs.gnu.org (full text, mbox):

From: Josselin Poiret <dev <at> jpoiret.xyz>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, 77806 <at> debbugs.gnu.org
Cc: Vivien Kraus <vivien <at> planete-kraus.eu>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Noé Lopez <noelopez <at> free.fr>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Bas Alberts <bas <at> anti.computer>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: bug#77806: [PATCH v2] services: elogind: Split sleep.conf and
 port to define-configuration.
Date: Fri, 25 Apr 2025 10:14:05 +0200
[Message part 1 (text/plain, inline)]
Hi Maxim,

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

> +         ;; Symlink the main configuration files.
> +         (with-directory-excursion #$output
> +           (symlink #$(logind.conf config) "logind.conf")
> +           (symlink #$(sleep.conf config) "sleep.conf"))

I don't know about you, but this didn't work for me, as elogind looks
for the config file in its own PKGCONFDIR (which is in the store),
only looking for overrides in the /etc/elogind/*.d/ directories.

Best,
-- 
Josselin Poiret
[signature.asc (application/pgp-signature, inline)]

Information forwarded to ludo <at> gnu.org, bas <at> anti.computer, maxim.cournoyer <at> gmail.com, dev <at> jpoiret.xyz, liliana.prikler <at> gmail.com, noelopez <at> free.fr, vivien <at> planete-kraus.eu, bug-guix <at> gnu.org:
bug#77806; Package guix. (Fri, 25 Apr 2025 14:13:03 GMT) Full text and rfc822 format available.

Message #28 received at 77806 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 77806 <at> debbugs.gnu.org
Cc: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Subject: [PATCH v3] services: elogind: Split sleep.conf and port to
 define-configuration.
Date: Fri, 25 Apr 2025 23:11:33 +0900
* gnu/services/desktop.scm (pascal-case): New procedure.
(<elogind-configuration>): Rewrite in terms of define-configuration.
(elogind-configuration-file): Delete.
(maybe-list-of-suspend-states?, maybe-list-of-suspend-modes?)
maybe-list-of-user-names?, maybe-boolean?maybe-package?)
(maybe-action?, maybe-percent?, maybe-list-of-strings?)
(maybe-list-of-hibernation-modes?, maybe-non-negative-integer?)
(non-negative-integer?, percent?, char-set:user-name, user-name?)
(list-of-user-names?, %elogind-actions, action?, %linux-suspend-states)
(string->symbol/maybe, suspend-state?, list-of-suspend-states?)
(%linux-suspend-modes, suspend-mode?, list-of-suspend-modes?)
(%linux-hibernation-modes, hibernation-mode?, list-of-hibernation-modes?)
(elogind-deprecated-empty-serializer, list-of-file-likes?)
(elogind-serialize-boolean, elogind-base-serializer, elogind-serialize-action)
(elogind-serialize-non-negative-integer, elogind-serialize-percent)
(elogind-list-serializer, elogind-serialize-list-of-strings)
(elogind-serialize-list-of-user-names, elogind-serialize-list-of-suspend-states)
(elogind-serialize-list-of-suspend-modes)
(elogind-serialize-list-of-hibernation-modes)
(%elogind-configuration-sleep-fields, logind.conf, sleep.conf): New procedures.
(elogind-etc-directory): Create the main configuration files there too.
(elogind-dbus-service): Adjust for package accessor name change.
(pam-extension-procedure, elogind-shepherd-service)
(elogind-service-type):  Likewise.
* doc/guix.texi (Desktop Services): Fully document configuration options.

Fixes: bug#77806
Change-Id: I8767891871d83e58d64995ec986a7d01689fa6d8
---
 doc/guix.texi            | 191 +++++++-----
 gnu/services/desktop.scm | 630 ++++++++++++++++++++++++++-------------
 2 files changed, 526 insertions(+), 295 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index be2fbbaf5bc..7b418a40892 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -26264,129 +26264,158 @@ Desktop Services
 @code{<elogind-configuration>} object.
 @end defvar
 
-@c TODO: field descriptions. This is best done by refactoring
-@c elogind-configuration to use define-configuration which embeds the
-@c descriptions in the code and then use configuration->documentation.
+@c Auto-generated via (configuration->documentation 'elogind-configuration).
+@c %start of fragment
+
 @deftp {Data Type} elogind-configuration
-Data type representing the configuration of @command{elogind}.
+Available @code{elogind-configuration} fields are:
 
 @table @asis
 @item @code{elogind} (default: @code{elogind}) (type: file-like)
-...
+The elogind package to use.
 
-@item @code{kill-user-processes?} (default: @code{#f}) (type: boolean)
-...
+@item @code{system-sleep-hook-files} (default: @code{()}) (type: list-of-file-likes)
+A list of executables (file-like objects) that will be installed into
+the @file{/etc/elogind/system-sleep} hook directory.  See `Hook
+directories' in the @samp{loginctl(1)} man page for more information.
 
-@item @code{kill-only-users} (default: @code{'()}) (type: list)
-...
+@item @code{system-shutdown-hook-files} (default: @code{()}) (type: list-of-file-likes)
+A list of executables (file-like objects) that will be installed into
+the @file{/etc/elogind/system-shutdown/} hook directory.
 
-@item @code{kill-exclude-users} (default: @code{'("root")}) (type: list-of-string)
-...
+@item @code{allow-power-off-interrupts?} (default: @code{#f}) (type: maybe-boolean)
+Whether the executables in elogind's hook directories (see above) can
+cause a power-off action to be cancelled (interrupted) by printing an
+appropriate error message to stdout.
 
-@item @code{inhibit-delay-max-seconds} (default: @code{5}) (type: integer)
-...
+@item @code{allow-suspend-interrupts?} (default: @code{#f}) (type: maybe-boolean)
+Likewise as the @code{allow-power-off-interrupts?} option, but for the
+suspend action.
 
-@item @code{handle-power-key} (default: @code{'poweroff}) (type: symbol)
-...
+@item @code{broadcast-power-off-interrupts?} (default: @code{#f}) (type: maybe-boolean)
+Whether an interrupt of a power-off action is broadcasted.
 
-@item @code{handle-suspend-key} (default: @code{'suspend}) (type: symbol)
-...
+@item @code{broadcast-suspend-interrupts?} (default: @code{#f}) (type: maybe-boolean)
+Whether an interrupt of a suspend action is broadcasted.
 
-@item @code{handle-hibernate-key} (default: @code{'hibernate}) (type: symbol)
-...
+@item @code{kill-user-processes?} (default: @code{#f}) (type: maybe-boolean)
+Whether the processes of a user should be killed when the user logs out.
 
-@item @code{handle-lid-switch} (default: @code{'suspend}) (type: symbol)
-...
+@item @code{kill-only-users} (type: maybe-list-of-user-names)
+Usernames whose processes should be killed, regardless the value of
+@code{kill-user-processes?}.
 
-@item @code{handle-lid-switch-docked} (default: @code{'ignore}) (type: symbol)
-...
+@item @code{kill-exclude-users} (default: @code{("root")}) (type: maybe-list-of-user-names)
+Usernames whose processes should @emph{not} be killed, regardless the
+value of @code{kill-user-processes?}.
 
-@item @code{handle-lid-switch-external-power} (default: @code{*unspecified*}) (type: symbol)
-...
+@item @code{inhibit-delay-max-seconds} (default: @code{5}) (type: maybe-non-negative-integer)
+The maximum time a system shutdown or sleep request is delayed due to an
+inhibitor lock of type delay being active before the inhibitor is
+ignored and the operation executes anyway.
 
-@item @code{power-key-ignore-inhibited?} (default: @code{#f}) (type: boolean)
-...
+@item @code{handle-power-key} (default: @code{poweroff}) (type: maybe-action)
+The action done when the power key is pressed.  The compiled default is
+@code{'poweroff}.
 
-@item @code{suspend-key-ignore-inhibited?} (default: @code{#f}) (type: boolean)
-...
+@item @code{handle-suspend-key} (default: @code{suspend}) (type: maybe-action)
+The action done when the suspend key is pressed.  The
 
-@item @code{hibernate-key-ignore-inhibited?} (default: @code{#f}) (type: boolean)
-...
+@item @code{handle-hibernate-key} (default: @code{hibernate}) (type: maybe-action)
+The action done when the hibernate key is pressed.
 
-@item @code{lid-switch-ignore-inhibited?} (default: @code{#t}) (type: boolean)
-...
+@item @code{handle-lid-switch} (default: @code{suspend}) (type: maybe-action)
+The action done when the lid is closed.
 
-@item @code{holdoff-timeout-seconds} (default: @code{30}) (type: integer)
-...
+@item @code{handle-lid-switch-docked} (default: @code{ignore}) (type: maybe-action)
+The action done when the lid is closed and the device docked.
 
-@item @code{idle-action} (default: @code{'ignore}) (type: symbol)
-...
+@item @code{handle-lid-switch-external-power} (default: @code{suspend}) (type: maybe-action)
+The action done when the lid is closed and the device is externally
+powered.
 
-@item @code{idle-action-seconds} (default: @code{(* 30 60)}) (type: integer)
-...
+@item @code{power-key-ignore-inhibited?} (default: @code{#f}) (type: maybe-boolean)
+Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+idle) when the power key is pressed.
 
-@item @code{runtime-directory-size-percent} (default: @code{10}) (type: integer)
-...
+@item @code{suspend-key-ignore-inhibited?} (default: @code{#f}) (type: maybe-boolean)
+Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+idle) when the suspend key is pressed.
 
-@item @code{runtime-directory-size} (default: @code{#f}) (type: integer)
-...
+@item @code{hibernate-key-ignore-inhibited?} (default: @code{#f}) (type: maybe-boolean)
+Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+idle) when the hibernate key is pressed.
 
-@item @code{remove-ipc?} (default: @code{#t}) (type: boolean)
-...
+@item @code{lid-switch-ignore-inhibited?} (default: @code{#f}) (type: maybe-boolean)
+Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+idle) when the lid is closed.
 
-@item @code{suspend-state} (default: @code{'("mem" "standby" "freeze")}) (type: list)
-...
+@item @code{holdoff-timeout-seconds} (default: @code{30}) (type: maybe-non-negative-integer)
+Specifies the number of seconds after system startup or system resume
+during which elogind will hold off on reacting to lid events.
 
-@item @code{suspend-mode} (default: @code{'()}) (type: list)
-...
+@item @code{idle-action} (default: @code{ignore}) (type: maybe-action)
+Action to take when the system is idle.
 
-@item @code{hibernate-state} (default: @code{'("disk")}) (type: list)
-...
+@item @code{idle-action-seconds} (type: maybe-non-negative-integer)
+The delay after which the action configured in @code{idle-action} is
+taken after the system is idle.
 
-@item @code{hibernate-mode} (default: @code{'("platform" "shutdown")}) (type: list)
-...
+@item @code{runtime-directory-size-percent} (type: maybe-percent)
+Set the size limit, in percent, on the @env{XDG_RUNTIME_DIR} runtime
+directory for each user who logs in.  This specifies the per-user size
+limit relative to the amount of physical @acronym{RAM,read access
+memory}.  This value takes precedence over that specified via
+@code{runtime-directory-size}.
 
-@item @code{hybrid-sleep-state} (default: @code{'("disk")}) (type: list)
-...
+@item @code{runtime-directory-size} (type: maybe-non-negative-integer)
+Set the size limit, in bytes, on the @env{XDG_RUNTIME_DIR} runtime
+directory for each user who logs in.
 
-@item @code{hybrid-sleep-mode} (default: @code{'("suspend" "platform" "shutdown")}) (type: list)
-...
+@item @code{remove-ipc?} (default: @code{#t}) (type: maybe-boolean)
+Whether @acronym{IPC,inter-process communication} objects belonging to
+the user shall be removed when the user fully logs out.
 
-@item @code{hibernate-delay-seconds} (default: @code{*unspecified*}) (type: integer)
-...
+@item @code{suspend-state} (default: @code{(mem standby freeze)}) (type: maybe-list-of-suspend-states)
+The suspend state values to be write to @file{/sys/power/state} by
+elogind when suspending the system.  They will be tried in turn, until
+one is written without error.
 
-@item @code{suspend-estimation-seconds} (default: @code{*unspecified*}) (type: integer)
-...
+@item @code{suspend-mode} (type: maybe-list-of-suspend-modes)
+The suspend mode values to write to @file{/sys/power/mem_sleep} by
+elogind when suspending the system.
 
-@item @code{system-sleep-hook-files} (default: @code{'()}) (type: list)
-A list of executables (file-like objects) that will be installed into
-the @file{/etc/elogind/system-sleep/} hook directory. For example:
+@item @code{suspend-estimation-seconds} (default: @code{3600}) (type: maybe-non-negative-integer)
+Cause the RTC alarm to wake the system after the specified time span to
+measure the system battery capacity level and estimate the battery
+discharging rate, which is used for estimating the time span until the
+system battery charge level goes down to 5%.  This option is only used
+by elogind when using the @code{'suspend-then-hibernate} action.
 
-@lisp
-(elogind-configuration
- (system-sleep-hook-files
-  (list (local-file "sleep-script"))))
-@end lisp
+@item @code{hibernate-mode} (default: @code{(platform shutdown)}) (type: maybe-list-of-hibernation-modes)
+The hibernation mode values to write to @file{/sys/power/disk} by
+elogind when hibernating the system.
 
-See `Hook directories' in the @code{loginctl(1)} man page for more information.
+@item @code{hibernate-delay-seconds} (type: maybe-non-negative-integer)
+The amount of time the system spends in suspend mode before the system
+is automatically put into hibernate mode.
 
-@item @code{system-shutdown-hook-files} (default: @code{'()}) (type: list)
-A list of executables (file-like objects) that will be installed into
-the @file{/etc/elogind/system-shutdown/} hook directory.
+@item @code{hibernate-state} (type: maybe-list-of-strings)
+Deprecated option.
 
-@item @code{allow-power-off-interrupts?} (default: @code{#f}) (type: boolean)
-@itemx @code{allow-suspend-interrupts?} (default: @code{#f}) (type: boolean)
-Whether the executables in elogind's hook directories (see above) can
-cause a power-off or suspend action to be cancelled (interrupted) by
-printing an appropriate error message to stdout.
+@item @code{hybrid-sleep-state} (type: maybe-list-of-strings)
+Deprecated option.
 
-@item @code{broadcast-power-off-interrupts?} (default: @code{#t}) (type: boolean)
-@itemx @code{broadcast-suspend-interrupts?} (default: @code{#t}) (type: boolean)
-Whether an interrupt of a power-off or suspend action is broadcasted.
+@item @code{hybrid-sleep-mode} (type: maybe-list-of-strings)
+Deprecated option.
 
 @end table
+
 @end deftp
 
+
+@c %end of fragment
+
 @defvar accountsservice-service-type
 Type for the service that runs AccountsService, a system service that can
 list available accounts, change their passwords, and so on.
diff --git a/gnu/services/desktop.scm b/gnu/services/desktop.scm
index fe034cfa8f4..04babf0e82d 100644
--- a/gnu/services/desktop.scm
+++ b/gnu/services/desktop.scm
@@ -37,6 +37,7 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services desktop)
+  #:use-module ((gnu home services utils) #:select (object->camel-case-string))
   #:use-module (gnu services)
   #:use-module (gnu services shepherd)
   #:use-module (gnu services base)
@@ -228,6 +229,8 @@ (define (package-direct-input-selector tree)
           (loop (cdr tree)
                 (car (assoc-ref (package-direct-inputs package)
                                 (car tree))))))))
+(define (pascal-case text)
+  (object->camel-case-string text 'upper))
 
 
 ;;;
@@ -1025,173 +1028,367 @@ (define gvfs-service-type
 ;;; Elogind login and seat management service.
 ;;;
 
-(define-record-type* <elogind-configuration> elogind-configuration
-  make-elogind-configuration
-  elogind-configuration?
-  (elogind                          elogind-package
-                                    (default elogind))
-  (kill-user-processes?             elogind-kill-user-processes?
-                                    (default #f))
-  (kill-only-users                  elogind-kill-only-users
-                                    (default '()))
-  (kill-exclude-users               elogind-kill-exclude-users
-                                    (default '("root")))
-  (inhibit-delay-max-seconds        elogind-inhibit-delay-max-seconds
-                                    (default 5))
-  (handle-power-key                 elogind-handle-power-key
-                                    (default 'poweroff))
-  (handle-suspend-key               elogind-handle-suspend-key
-                                    (default 'suspend))
-  (handle-hibernate-key             elogind-handle-hibernate-key
-                                    (default 'hibernate))
-  (handle-lid-switch                elogind-handle-lid-switch
-                                    (default 'suspend))
-  (handle-lid-switch-docked         elogind-handle-lid-switch-docked
-                                    (default 'ignore))
-  (handle-lid-switch-external-power elogind-handle-lid-switch-external-power
-                                    (default *unspecified*))
-  (power-key-ignore-inhibited?      elogind-power-key-ignore-inhibited?
-                                    (default #f))
-  (suspend-key-ignore-inhibited?    elogind-suspend-key-ignore-inhibited?
-                                    (default #f))
-  (hibernate-key-ignore-inhibited?  elogind-hibernate-key-ignore-inhibited?
-                                    (default #f))
-  (lid-switch-ignore-inhibited?     elogind-lid-switch-ignore-inhibited?
-                                    (default #t))
-  (holdoff-timeout-seconds          elogind-holdoff-timeout-seconds
-                                    (default 30))
-  (idle-action                      elogind-idle-action
-                                    (default 'ignore))
-  (idle-action-seconds              elogind-idle-action-seconds
-                                    (default (* 30 60)))
-  (runtime-directory-size-percent   elogind-runtime-directory-size-percent
-                                    (default 10))
-  (runtime-directory-size           elogind-runtime-directory-size
-                                    (default #f))
-  (remove-ipc?                      elogind-remove-ipc?
-                                    (default #t))
-
-  (suspend-state                    elogind-suspend-state
-                                    (default '("mem" "standby" "freeze")))
-  (suspend-mode                     elogind-suspend-mode
-                                    (default '()))
-  (hibernate-state                  elogind-hibernate-state
-                                    (default '("disk")))
-  (hibernate-mode                   elogind-hibernate-mode
-                                    (default '("platform" "shutdown")))
-  (hybrid-sleep-state               elogind-hybrid-sleep-state
-                                    (default '("disk")))
-  (hybrid-sleep-mode                elogind-hybrid-sleep-mode
-                                    (default
-                                      '("suspend" "platform" "shutdown")))
-  (hibernate-delay-seconds          elogind-hibernate-delay-seconds
-                                    (default *unspecified*))
-  (suspend-estimation-seconds       elogind-suspend-estimation-seconds
-                                    (default *unspecified*))
-  (system-sleep-hook-files          elogind-system-sleep-hook-files
-                                    (default '()))
-  (system-shutdown-hook-files       elogind-system-shutdown-hook-files
-                                    (default '()))
-  (allow-power-off-interrupts?      elogind-allow-power-off-interrupts?
-                                    (default #f))
-  (allow-suspend-interrupts?        elogind-allow-suspend-interrupts?
-                                    (default #f))
-  (broadcast-power-off-interrupts?  elogind-broadcast-power-off-interrupts?
-                                    (default #t))
-  (broadcast-suspend-interrupts?    elogind-broadcast-suspend-interrupts?
-                                    (default #t)))
-
-(define (elogind-configuration-file config)
-  (define (yesno x)
-    (match x
-      (#t "yes")
-      (#f "no")
-      (_ (error "expected #t or #f, instead got:" x))))
-  (define char-set:user-name
-    (string->char-set "abcdefghijklmnopqrstuvwxyz0123456789_-"))
-  (define (valid-list? l pred)
-    (and-map (lambda (x) (string-every pred x)) l))
-  (define (user-name-list users)
-    (unless (valid-list? users char-set:user-name)
-      (error "invalid user list" users))
-    (string-join users " "))
-  (define (enum val allowed)
-    (unless (memq val allowed)
-      (error "invalid value" val allowed))
-    (symbol->string val))
-  (define (non-negative-integer x)
-    (unless (exact-integer? x) (error "not an integer" x))
-    (when (negative? x) (error "negative number not allowed" x))
-    (number->string x))
-  (define (maybe-non-negative-integer x)
-    (or (and (unspecified? x) x)
-        (non-negative-integer x)))
-  (define handle-actions
-    '(ignore poweroff reboot halt kexec suspend hibernate hybrid-sleep suspend-then-hibernate lock))
-  (define (handle-action x)
-    (if (unspecified? x)
-        x                               ;let the unspecified value go through
-        (enum x handle-actions)))
-  (define (sleep-list tokens)
-    (unless (valid-list? tokens char-set:user-name)
-      (error "invalid sleep list" tokens))
-    (string-join tokens " "))
-  (define-syntax ini-file-clause
-    (syntax-rules ()
-      ;; Produce an empty line when encountering an unspecified value.  This
-      ;; is better than an empty string value, which can, in some cases, cause
-      ;; warnings such as "Failed to parse handle action setting".
-      ((_ config (prop (parser getter)))
-       (let ((value (parser (getter config))))
-         (if (unspecified? value)
-             ""
-             (string-append prop "=" value "\n"))))
-      ((_ config str)
-       (if (unspecified? str)
-           ""
-           (string-append str "\n")))))
-  (define-syntax-rule (ini-file config file clause ...)
-    (plain-file file (string-append (ini-file-clause config clause) ...)))
-  (ini-file
-   config "logind.conf"
-   "[Login]"
-   ("KillUserProcesses" (yesno elogind-kill-user-processes?))
-   ("KillOnlyUsers" (user-name-list elogind-kill-only-users))
-   ("KillExcludeUsers" (user-name-list elogind-kill-exclude-users))
-   ("InhibitDelayMaxSec" (non-negative-integer elogind-inhibit-delay-max-seconds))
-   ("HandlePowerKey" (handle-action elogind-handle-power-key))
-   ("HandleSuspendKey" (handle-action elogind-handle-suspend-key))
-   ("HandleHibernateKey" (handle-action elogind-handle-hibernate-key))
-   ("HandleLidSwitch" (handle-action elogind-handle-lid-switch))
-   ("HandleLidSwitchDocked" (handle-action elogind-handle-lid-switch-docked))
-   ("HandleLidSwitchExternalPower" (handle-action elogind-handle-lid-switch-external-power))
-   ("PowerKeyIgnoreInhibited" (yesno elogind-power-key-ignore-inhibited?))
-   ("SuspendKeyIgnoreInhibited" (yesno elogind-suspend-key-ignore-inhibited?))
-   ("HibernateKeyIgnoreInhibited" (yesno elogind-hibernate-key-ignore-inhibited?))
-   ("LidSwitchIgnoreInhibited" (yesno elogind-lid-switch-ignore-inhibited?))
-   ("HoldoffTimeoutSec" (non-negative-integer elogind-holdoff-timeout-seconds))
-   ("IdleAction" (handle-action elogind-idle-action))
-   ("IdleActionSec" (non-negative-integer elogind-idle-action-seconds))
-   ("RuntimeDirectorySize"
-    (identity
-     (lambda (config)
-       (match (elogind-runtime-directory-size-percent config)
-         (#f (non-negative-integer (elogind-runtime-directory-size config)))
-         (percent (string-append (non-negative-integer percent) "%"))))))
-   ("RemoveIPC" (yesno elogind-remove-ipc?))
-   "[Sleep]"
-   ("SuspendState" (sleep-list elogind-suspend-state))
-   ("SuspendMode" (sleep-list elogind-suspend-mode))
-   ("HibernateState" (sleep-list elogind-hibernate-state))
-   ("HibernateMode" (sleep-list elogind-hibernate-mode))
-   ("HybridSleepState" (sleep-list elogind-hybrid-sleep-state))
-   ("HybridSleepMode" (sleep-list elogind-hybrid-sleep-mode))
-   ("HibernateDelaySec" (maybe-non-negative-integer elogind-hibernate-delay-seconds))
-   ("SuspendEstimationSec" (maybe-non-negative-integer elogind-suspend-estimation-seconds))
-   ("AllowPowerOffInterrupts" (yesno elogind-allow-power-off-interrupts?))
-   ("AllowSuspendInterrupts" (yesno elogind-allow-suspend-interrupts?))
-   ("BroadcastPowerOffInterrupts" (yesno elogind-broadcast-power-off-interrupts?))
-   ("BroadcastSuspendInterrupts" (yesno elogind-broadcast-suspend-interrupts?))))
+;;; Elogind configuration types.
+(define-maybe boolean
+  (prefix elogind-))
+
+(define (non-negative-integer? x)
+  (and (exact-integer? x)
+       (not (negative? x))))
+
+(define-maybe non-negative-integer
+  (prefix elogind-))
+
+(define (percent? x)
+  (and (non-negative-integer? x)
+       (>= x 0)
+       (<= x 100)))
+
+(define-maybe percent
+  (prefix elogind-))
+
+(define char-set:user-name
+  (string->char-set "abcdefghijklmnopqrstuvwxyz0123456789_-"))
+
+(define (user-name? x)
+  (string-every char-set:user-name x))
+
+(define list-of-user-names?
+  (list-of user-name?))
+
+(define-maybe list-of-user-names
+  (prefix elogind-))
+
+(define %elogind-actions
+  '( ignore poweroff reboot halt kexec suspend hibernate hybrid-sleep
+     suspend-then-hibernate lock factory-reset))
+
+(define (action? x)
+  (member x %elogind-actions))
+
+(define-maybe action
+  (prefix elogind-))
+
+(define %linux-suspend-states
+  ;; The possible suspend states supported by the Linux kernel.
+  ;; See (info "(linux) Basic sysfs Interfaces for System Suspend and Hibernation").
+  '(disk standby freeze mem))
+
+(define (string->symbol/maybe x)
+  (if (string? x)
+      (string->symbol x)
+      x))
+
+(define (suspend-state? x)
+  (member (string->symbol/maybe x) %linux-suspend-states))
+
+(define list-of-suspend-states?
+  (list-of suspend-state?))
+
+(define-maybe list-of-suspend-states
+  (prefix elogind-))
+
+(define %linux-suspend-modes
+  ;; The possible suspend state variants supported by the Linux kernel.
+  ;; See (info "(linux) Basic sysfs Interfaces for System Suspend and Hibernation").
+  '(s2idle shallow deep))
+
+(define (suspend-mode? x)
+  (member (string->symbol/maybe x) %linux-suspend-modes))
+
+(define list-of-suspend-modes?
+  (list-of suspend-mode?))
+
+(define-maybe list-of-suspend-modes
+  (prefix elogind-))
+
+(define %linux-hibernation-modes
+  ;; The possible hibernation operating modes supported by the Linux kernel.
+  ;; See (info "(linux) Basic sysfs Interfaces for System Suspend and Hibernation").
+  '(platform shutdown reboot suspend test_resume))
+
+(define (hibernation-mode? x)
+  (member (string->symbol/maybe x) %linux-hibernation-modes))
+
+(define list-of-hibernation-modes?
+  (list-of hibernation-mode?))
+
+(define-maybe list-of-hibernation-modes
+  (prefix elogind-))
+
+(define (elogind-deprecated-empty-serializer name value)
+  (when (maybe-value-set? value)
+    (warn-about-deprecation name #f
+                            #:replacement #f))
+  "")
+
+(define list-of-file-likes?
+  (list-of file-like?))
+
+(define-maybe list-of-strings
+  (prefix elogind-))
+
+;;; Elogind serializers.
+(define (elogind-serialize-boolean name value)
+  (let* ((name-str (symbol->string name))
+         (name (if (string-suffix? "?" name-str)
+                   (string-drop-right name-str 1)
+                   name-str)))
+    (format #f "~a=~:[no~;yes~]~%" (pascal-case name) value)))
+
+(define (elogind-base-serializer name value)
+  (let* ((name-str (symbol->string name))
+         (name (if (string-suffix? "seconds" name-str)
+                   (string-drop-right name-str 4) ;seconds -> sec
+                   name-str)))
+    (format #f "~a=~a~%" (pascal-case name) value)))
+
+(define elogind-serialize-action elogind-base-serializer)
+(define elogind-serialize-non-negative-integer elogind-base-serializer)
+(define elogind-serialize-percent elogind-base-serializer)
+
+(define (elogind-list-serializer name value)
+  (format #f "~a=~{~a~^ ~}~%" (pascal-case name) value))
+
+(define elogind-serialize-list-of-strings elogind-list-serializer)
+(define elogind-serialize-list-of-user-names elogind-list-serializer)
+(define elogind-serialize-list-of-suspend-states elogind-list-serializer)
+(define elogind-serialize-list-of-suspend-modes elogind-list-serializer)
+(define elogind-serialize-list-of-hibernation-modes elogind-list-serializer)
+
+;;; XXX: For backward-compatible/historical reasons, the configuration object
+;;; is flat, containing the fields of both the logind.conf and sleep.conf
+;;; files.  The list below contains the fields that should be serialized to
+;;; sleep.conf.
+(define %elogind-configuration-sleep-fields
+  '( suspend-state suspend-mode suspend-estimation-seconds
+     hibernate-mode hibernate-delay-seconds hibernate-state
+     hybrid-sleep-state hybrid-sleep-mode))
+
+(define-configuration elogind-configuration
+  (elogind
+   (file-like elogind)
+   "The elogind package to use."
+   (serializer empty-serializer))
+
+  (system-sleep-hook-files
+   (list-of-file-likes '())
+   "A list of executables (file-like objects) that will be installed into the
+@file{/etc/elogind/system-sleep} hook directory.  See `Hook directories' in
+the @samp{loginctl(1)} man page for more information."
+   (serializer empty-serializer))
+
+  (system-shutdown-hook-files
+   (list-of-file-likes '())
+   "A list of executables (file-like objects) that will be installed into the
+@file{/etc/elogind/system-shutdown/} hook directory."
+   (serializer empty-serializer))
+
+  (allow-power-off-interrupts?
+   (maybe-boolean #f)
+   "Whether the executables in elogind's hook directories (see above) can
+  cause a power-off action to be cancelled (interrupted) by printing an
+  appropriate error message to stdout.")
+
+  (allow-suspend-interrupts?
+   (maybe-boolean #f)
+   "Likewise as the @code{allow-power-off-interrupts?} option, but for the
+  suspend action.")
+
+  (broadcast-power-off-interrupts?
+   (maybe-boolean #f)
+   "Whether an interrupt of a power-off action is broadcasted.")
+
+  (broadcast-suspend-interrupts?
+   (maybe-boolean #f)
+   "Whether an interrupt of a suspend action is broadcasted.")
+
+  ;; logind.conf options.
+  (kill-user-processes?
+   (maybe-boolean #f)
+   "Whether the processes of a user should be killed when the user logs
+  out.")
+
+  (kill-only-users
+   maybe-list-of-user-names
+   "Usernames whose processes should be killed, regardless the value of
+  @code{kill-user-processes?}.")
+
+  (kill-exclude-users
+   (maybe-list-of-user-names (list "root"))
+   "Usernames whose processes should @emph{not} be killed, regardless the
+  value of @code{kill-user-processes?}.")
+
+  (inhibit-delay-max-seconds
+   (maybe-non-negative-integer 5)
+   "The maximum time a system shutdown or sleep request is delayed due to an
+  inhibitor lock of type delay being active before the inhibitor is ignored and
+  the operation executes anyway.")
+
+  (handle-power-key
+   (maybe-action 'poweroff)
+   "The action done when the power key is pressed.  The compiled default is
+  @code{'poweroff}.")
+
+  (handle-suspend-key
+   (maybe-action 'suspend)
+   "The action done when the suspend key is pressed.  The ")
+
+  (handle-hibernate-key
+   (maybe-action 'hibernate)
+   "The action done when the hibernate key is pressed.")
+
+  (handle-lid-switch
+   (maybe-action 'suspend)
+   "The action done when the lid is closed.")
+
+  (handle-lid-switch-docked
+   (maybe-action 'ignore)
+   "The action done when the lid is closed and the device docked.")
+
+  (handle-lid-switch-external-power
+   (maybe-action 'suspend)
+   "The action done when the lid is closed and the device is externally
+  powered.")
+
+  (power-key-ignore-inhibited?
+   (maybe-boolean #f)
+   "Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+  idle) when the power key is pressed.")
+
+  (suspend-key-ignore-inhibited?
+   (maybe-boolean #f)
+   "Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+  idle) when the suspend key is pressed.")
+
+  (hibernate-key-ignore-inhibited?
+   (maybe-boolean #f)
+   "Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+  idle) when the hibernate key is pressed.")
+
+  (lid-switch-ignore-inhibited?
+   (maybe-boolean #f)
+   "Whether to ignore high-level inhibitor locks (shutdown, reboot, sleep or
+  idle) when the lid is closed.")
+
+  (holdoff-timeout-seconds
+   (maybe-non-negative-integer 30)
+   "Specifies the number of seconds after system startup or system resume
+during which elogind will hold off on reacting to lid events.")
+
+  (idle-action
+   (maybe-action 'ignore)
+   "Action to take when the system is idle.")
+
+  (idle-action-seconds
+   maybe-non-negative-integer
+   "The delay after which the action configured in @code{idle-action} is
+taken after the system is idle.")
+
+  ;; XXX: Perhaps deprecate in the future and handle all the accepted forms
+  ;; directly in 'runtime-directory-size' instead.
+  (runtime-directory-size-percent
+   maybe-percent
+   "Set the size limit, in percent, on the @env{XDG_RUNTIME_DIR} runtime
+directory for each user who logs in.  This specifies the per-user size limit
+relative to the amount of physical @acronym{RAM, read access memory}.  This
+value takes precedence over that specified via @code{runtime-directory-size}."
+   (serializer empty-serializer))       ;special cased at serialization time
+
+  (runtime-directory-size
+   maybe-non-negative-integer
+   "Set the size limit, in bytes, on the @env{XDG_RUNTIME_DIR} runtime
+directory for each user who logs in."
+   (serializer empty-serializer))       ;special cased at serialization time
+
+  (remove-ipc?
+   (maybe-boolean #t)
+   "Whether @acronym{IPC, inter-process communication} objects belonging to
+the user shall be removed when the user fully logs out.")
+
+  ;; sleep.conf options.
+  ;; CAUTION: all sleep.conf option names must be registered in the above
+  ;; %ELOGIND-CONFIGURATION-SLEEP-FIELDS variable: otherwise they will be
+  ;; serialized to logind.conf instead of sleep.conf!
+  (suspend-state
+   (maybe-list-of-suspend-states '(mem standby freeze))
+   "The suspend state values to be write to @file{/sys/power/state} by elogind
+  when suspending the system.  They will be tried in turn, until one is written
+  without error.")
+
+  (suspend-mode
+   (maybe-list-of-suspend-modes)
+   "The suspend mode values to write to @file{/sys/power/mem_sleep} by elogind
+  when suspending the system.")
+
+  (suspend-estimation-seconds
+   (maybe-non-negative-integer (* 60 60)) ;1 hour
+   "Cause the RTC alarm to wake the system after the specified time span to
+  measure the system battery capacity level and estimate the battery discharging
+  rate, which is used for estimating the time span until the system battery
+  charge level goes down to 5%.  This option is only used by elogind when using
+  the @code{'suspend-then-hibernate} action.")
+
+  (hibernate-mode
+   (maybe-list-of-hibernation-modes '(platform shutdown))
+   "The hibernation mode values to write to @file{/sys/power/disk} by elogind
+  when hibernating the system.")
+
+  (hibernate-delay-seconds
+   maybe-non-negative-integer
+   "The amount of time the system spends in suspend mode before the system is
+  automatically put into hibernate mode.")
+
+  ;; TODO: Remove in May 2026.
+  (hibernate-state
+   maybe-list-of-strings
+   "Deprecated option."
+   (serializer elogind-deprecated-empty-serializer))
+
+  ;; TODO: Remove in May 2026.
+  (hybrid-sleep-state
+   maybe-list-of-strings
+   "Deprecated option."
+   (serializer elogind-deprecated-empty-serializer))
+
+  ;; TODO: Remove in May 2026.
+  (hybrid-sleep-mode
+   maybe-list-of-strings
+   "Deprecated option."
+   (serializer elogind-deprecated-empty-serializer))
+
+  (prefix elogind-))
+
+(define (logind.conf config)
+  (let ((logind-fields (remove (lambda (field)
+                                 (memq (configuration-field-name field)
+                                       %elogind-configuration-sleep-fields))
+                               elogind-configuration-fields)))
+    (match-record config <elogind-configuration>
+                  (runtime-directory-size-percent runtime-directory-size)
+      ;; Handle the special-cased
+      ;; runtime-directory-size-percent/runtime-directory-size options pair.
+      (let ((runtime-directory-size
+             (if (maybe-value-set? runtime-directory-size-percent)
+                 (format #f "~a%~%" runtime-directory-size-percent) ;10 -> 10%
+                 runtime-directory-size)))
+        (mixed-text-file
+         "logind.conf"
+         "[Login]\n"
+         (if (maybe-value-set? runtime-directory-size)
+             (list "RuntimeDirectorySize=" runtime-directory-size)
+             "")
+         (serialize-configuration config logind-fields))))))
+
+(define (sleep.conf config)
+  (let ((sleep-fields (filter (lambda (field)
+                                (memq (configuration-field-name field)
+                                      %elogind-configuration-sleep-fields))
+                              elogind-configuration-fields)))
+    (mixed-text-file
+     "sleep.conf"
+     "[Sleep]\n"
+     (serialize-configuration config sleep-fields))))
 
 (define (elogind-etc-directory config)
   "Return the /etc/elogind directory for CONFIG."
@@ -1213,12 +1410,21 @@ (define (elogind-etc-directory config)
              (chmod dest #o500)))
 
          (mkdir-p #$output)            ;in case neither directory gets created
+
+         ;; Symlink the main configuration files.
+         (with-directory-excursion #$output
+           (mkdir-p "logind.conf.d")
+           (symlink #$(logind.conf config) "logind.conf.d/logind.conf")
+           (mkdir-p "sleep.conf.d")
+           (symlink #$(sleep.conf config) "sleep.conf.d/sleep.conf"))
+
          (for-each (lambda (f)
                      (copy-script f sleep-directory))
-                   '#$(elogind-system-sleep-hook-files config))
+                   '#$(elogind-configuration-system-sleep-hook-files config))
          (for-each (lambda (f)
                      (copy-script f shutdown-directory))
-                   '#$(elogind-system-shutdown-hook-files config))))))
+                   '#$(elogind-configuration-system-shutdown-hook-files
+                       config))))))
 
 (define (elogind-dbus-service config)
   "Return a @file{org.freedesktop.login1.service} file that tells D-Bus how to
@@ -1231,7 +1437,7 @@ (define (elogind-dbus-service config)
   ;; <https://issues.guix.gnu.org/55444>.
 
   (define elogind
-    (elogind-package config))
+    (elogind-configuration-elogind config))
 
   (define wrapper
     (program-file "elogind-dbus-shepherd-sync"
@@ -1288,7 +1494,7 @@ (define (pam-extension-procedure config)
   (define pam-elogind
     (pam-entry
      (control "required")
-     (module (file-append (elogind-package config)
+     (module (file-append (elogind-configuration-elogind config)
                           "/lib/security/pam_elogind.so"))))
 
   (list (pam-extension
@@ -1301,56 +1507,52 @@ (define (pam-extension-procedure config)
 
 (define (elogind-shepherd-service config)
   "Return a Shepherd service to start elogind according to @var{config}."
-  (define config-file
-    (elogind-configuration-file config))
-
   (list (shepherd-service
          (requirement '(user-processes dbus-system))
          (provision '(elogind))
          (start #~(make-forkexec-constructor
-                   (list #$(file-append (elogind-package config)
-                                        "/libexec/elogind/elogind"))
-                   #:environment-variables
-                   (list (string-append "ELOGIND_CONF_FILE="
-                                        #$config-file))))
+                   (list #$(file-append (elogind-configuration-elogind config)
+                                        "/libexec/elogind/elogind"))))
          (stop #~(make-kill-destructor))
-         (actions (list (shepherd-configuration-action config-file))))))
+         (actions (list (shepherd-configuration-action
+                         "/etc/elogind/logind.conf"))))))
 
 (define elogind-service-type
-  (service-type (name 'elogind)
-                (extensions
-                 (list (service-extension dbus-root-service-type
-                                          elogind-dbus-service)
-                       (service-extension udev-service-type
-                                          (compose list elogind-package))
-                       (service-extension polkit-service-type
-                                          (compose list elogind-package))
-
-                       ;; Start elogind from the Shepherd rather than waiting
-                       ;; for bus activation.  This ensures that it can handle
-                       ;; events like lid close, etc.
-                       (service-extension shepherd-root-service-type
-                                          elogind-shepherd-service)
-
-                       ;; Provide the 'loginctl' command.
-                       (service-extension profile-service-type
-                                          (compose list elogind-package))
-
-                       ;; Extend PAM with pam_elogind.so.
-                       (service-extension pam-root-service-type
-                                          pam-extension-procedure)
-
-                       ;; Install sleep/shutdown hook files.
-                       (service-extension etc-service-type
-                                          (lambda (config)
-                                            `(("elogind"
-                                               ,(elogind-etc-directory config)))))
-
-                       ;; We need /run/user, /run/systemd, etc.
-                       (service-extension file-system-service-type
-                                          (const %elogind-file-systems))))
-                (default-value (elogind-configuration))
-                (description "Run the @command{elogind} login and seat
+  (service-type
+   (name 'elogind)
+   (extensions
+    (list (service-extension dbus-root-service-type
+                             elogind-dbus-service)
+          (service-extension udev-service-type
+                             (compose list elogind-configuration-elogind))
+          (service-extension polkit-service-type
+                             (compose list elogind-configuration-elogind))
+
+          ;; Start elogind from the Shepherd rather than waiting
+          ;; for bus activation.  This ensures that it can handle
+          ;; events like lid close, etc.
+          (service-extension shepherd-root-service-type
+                             elogind-shepherd-service)
+
+          ;; Provide the 'loginctl' command.
+          (service-extension profile-service-type
+                             (compose list elogind-configuration-elogind))
+
+          ;; Extend PAM with pam_elogind.so.
+          (service-extension pam-root-service-type
+                             pam-extension-procedure)
+
+          ;; Install sleep/shutdown hook files.
+          (service-extension etc-service-type
+                             (lambda (config)
+                               `(("elogind"
+                                  ,(elogind-etc-directory config)))))
+
+          ;; We need /run/user, /run/systemd, etc.
+          (service-extension file-system-service-type
+                             (const %elogind-file-systems))))
+   (default-value (elogind-configuration))
+   (description "Run the @command{elogind} login and seat
 management service.  The @command{elogind} service integrates with PAM to
 allow other system components to know the set of logged-in users as well as
 their session types (graphical, console, remote, etc.).  It can also clean up

base-commit: 4fe4cf9fdd959126d3c53c3df4504d851e7b736a
-- 
2.49.0





Information forwarded to bug-guix <at> gnu.org:
bug#77806; Package guix. (Fri, 25 Apr 2025 14:56:02 GMT) Full text and rfc822 format available.

Message #31 received at 77806 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Vivien Kraus <vivien <at> planete-kraus.eu>,
 Noé Lopez <noelopez <at> free.fr>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Bas Alberts <bas <at> anti.computer>, 77806 <at> debbugs.gnu.org
Subject: Re: bug#77806: [PATCH v3] services: elogind: Split sleep.conf and
 port to define-configuration.
Date: Fri, 25 Apr 2025 16:54:46 +0200
Hi,

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

> Fixes: bug#77806

I would provide the complete URL, as is usually done, to avoid
ambiguity.

> +         (actions (list (shepherd-configuration-action
> +                         "/etc/elogind/logind.conf"))))))

Would it be possible to give the config file name in the store instead?
That way, we would know which one is actually in effect (when one
reconfigures, /etc/logind/logind.conf is changed through activation but
elogind keeps honoring the previous version of it, which is why
distinguishing between both can be helpful IMO.)

I let Josselin comment on the rest.

Thanks for the fix!

Ludo’.




Reply sent to Maxim Cournoyer <maxim.cournoyer <at> gmail.com>:
You have taken responsibility. (Sat, 26 Apr 2025 14:04:02 GMT) Full text and rfc822 format available.

Notification sent to Ludovic Courtès <ludo <at> gnu.org>:
bug acknowledged by developer. (Sat, 26 Apr 2025 14:04:03 GMT) Full text and rfc822 format available.

Message #36 received at 77806-done <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Vivien Kraus <vivien <at> planete-kraus.eu>,
 Noé Lopez <noelopez <at> free.fr>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Bas Alberts <bas <at> anti.computer>, 77806-done <at> debbugs.gnu.org
Subject: Re: bug#77806: elogind behavior changed: power key turns computer off
Date: Sat, 26 Apr 2025 23:03:09 +0900
Hi,

Ludovic Courtès <ludo <at> gnu.org> writes:

> Hi,
>
> Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
>
>> Fixes: bug#77806
>
> I would provide the complete URL, as is usually done, to avoid
> ambiguity.

Okay, done.

>> +         (actions (list (shepherd-configuration-action
>> +                         "/etc/elogind/logind.conf"))))))
>
> Would it be possible to give the config file name in the store instead?
> That way, we would know which one is actually in effect (when one
> reconfigures, /etc/logind/logind.conf is changed through activation but
> elogind keeps honoring the previous version of it, which is why
> distinguishing between both can be helpful IMO.)

I did what you had suggested earlier with a custom
'shepherd-configuration-action' job that accepts multiple files:

--8<---------------cut here---------------start------------->8---
1 file changed, 13 insertions(+), 2 deletions(-)
gnu/services/desktop.scm | 15 +++++++++++++--

modified   gnu/services/desktop.scm
@@ -1505,6 +1505,16 @@ (define (pam-extension-procedure config)
              (session (cons pam-elogind (pam-service-session pam))))))
          (shepherd-requirements '(elogind)))))
 
+(define* (shepherd-configuration-action* files)
+  "Return a 'configuration' action to display FILES, which should be the names
+of the service's configuration files."
+  (shepherd-action
+   (name 'configuration)
+   (documentation "Display the names of this service's configuration files.")
+   (procedure #~(lambda (_)
+                  (format #t "~{~a~%~}" '#$files)
+                  '#$files))))
+
 (define (elogind-shepherd-service config)
   "Return a Shepherd service to start elogind according to @var{config}."
   (list (shepherd-service
@@ -1514,8 +1524,9 @@ (define (elogind-shepherd-service config)
                    (list #$(file-append (elogind-configuration-elogind config)
                                         "/libexec/elogind/elogind"))))
          (stop #~(make-kill-destructor))
-         (actions (list (shepherd-configuration-action
-                         "/etc/elogind/logind.conf"))))))
+         (actions (list (shepherd-configuration-action*
+                         (list (logind.conf config)
+                               (sleep.conf config))))))))
 
 (define elogind-service-type
   (service-type
--8<---------------cut here---------------end--------------->8---

And it seems to work correctly on my machine:

--8<---------------cut here---------------start------------->8---
$ sudo herd configuration elogind
/gnu/store/z2hdrmyxjd0x7msf66638ppl9hx99fh3-logind.conf
/gnu/store/hqbmn9idlydkkm554k1zrhi306ffsldl-sleep.conf
maxim <at> terra ~$ cat /gnu/store/hqbmn9idlydkkm554k1zrhi306ffsldl-sleep.conf
[Sleep]
SuspendState=mem standby freeze
SuspendEstimationSec=3600
HibernateMode=platform shutdown
maxim <at> terra ~$ find /etc/elogind/
/etc/elogind/
/etc/elogind/logind.conf.d
/etc/elogind/logind.conf.d/logind.conf
/etc/elogind/sleep.conf.d
/etc/elogind/sleep.conf.d/sleep.conf
maxim <at> terra ~$ readlink /etc/elogind/logind.conf.d/logind.conf
/gnu/store/z2hdrmyxjd0x7msf66638ppl9hx99fh3-logind.conf
maxim <at> terra ~$ readlink /etc/elogind/sleep.conf.d/sleep.conf
/gnu/store/hqbmn9idlydkkm554k1zrhi306ffsldl-sleep.conf
--8<---------------cut here---------------end--------------->8---

Josselin pointed to some problem, which I've now fix (had to use
"drop-ins" files instead of the canonical config file names, which are
looked from the package's sysconfdir in the store instead of under
/etc/elogind).  If we find other we can fix them later, but it seems to
me it can't be much worst than it is now, so I've now pushed this to
master, with commit f10d00e4e25.

Thanks to everyone who helped figure that one out!

-- 
Thanks,
Maxim




Information forwarded to bug-guix <at> gnu.org:
bug#77806; Package guix. (Sat, 03 May 2025 16:34:02 GMT) Full text and rfc822 format available.

Message #39 received at 77806-done <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Vivien Kraus <vivien <at> planete-kraus.eu>,
 Noé Lopez <noelopez <at> free.fr>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Bas Alberts <bas <at> anti.computer>, 77806-done <at> debbugs.gnu.org
Subject: Re: bug#77806: elogind behavior changed: power key turns computer off
Date: Sat, 03 May 2025 18:30:12 +0200
Hi,

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

> Josselin pointed to some problem, which I've now fix (had to use
> "drop-ins" files instead of the canonical config file names, which are
> looked from the package's sysconfdir in the store instead of under
> /etc/elogind).  If we find other we can fix them later, but it seems to
> me it can't be much worst than it is now, so I've now pushed this to
> master, with commit f10d00e4e25.
>
> Thanks to everyone who helped figure that one out!

Thanks to both of you for the quick fix!

Ludo’.




bug archived. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Sun, 01 Jun 2025 11:24:15 GMT) Full text and rfc822 format available.

This bug report was last modified 17 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.