diff mbox series

[bug#65343] home: services: Add 'x11-display' service.

Message ID b9fa2dae291ec797b1869cce7d74d59cf5299a03.1692207625.git.ludo@gnu.org
State New
Headers show
Series [bug#65343] home: services: Add 'x11-display' service. | expand

Commit Message

Ludovic Courtès Aug. 16, 2023, 5:43 p.m. UTC
* gnu/home/services/desktop.scm (x11-shepherd-service): New procedure.
(home-x11-service-type): New variable.
(redshift-shepherd-service): Add 'requirement' field.
(home-redshift-service-type): Extend 'home-x11-service-type'.
* doc/guix.texi (Desktop Home Services): Document it.
---
 doc/guix.texi                 | 22 ++++++++++
 gnu/home/services/desktop.scm | 82 ++++++++++++++++++++++++++++++++---
 2 files changed, 99 insertions(+), 5 deletions(-)

Hello Guix!

This is an attempt to fix a longstanding issue with Home services
that depend on X11: how can we make sure that (1) they are not started
when X is not running, and (2) they get the correct ‘DISPLAY’
variable.

It’s a bit of a hack (the idea came up during a discussion on IRC
a few days ago), but it does the job.  I guess it could be
extended to Wayland as well, but I’m not familiar with it.

Thoughts?

Ludo’.


base-commit: 880ada0bdb9e694573ec42200d48658b27744b9b

Comments

Oleg Pykhalov Aug. 16, 2023, 7:03 p.m. UTC | #1
Hi Ludovic,

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

> * gnu/home/services/desktop.scm (x11-shepherd-service): New procedure.
> (home-x11-service-type): New variable.
> (redshift-shepherd-service): Add 'requirement' field.
> (home-redshift-service-type): Extend 'home-x11-service-type'.
> * doc/guix.texi (Desktop Home Services): Document it.
> ---
>  doc/guix.texi                 | 22 ++++++++++
>  gnu/home/services/desktop.scm | 82 ++++++++++++++++++++++++++++++++---
>  2 files changed, 99 insertions(+), 5 deletions(-)
>
> This is an attempt to fix a longstanding issue with Home services
> that depend on X11: how can we make sure that (1) they are not started
> when X is not running, and (2) they get the correct ‘DISPLAY’
> variable.
>
> […]
>
> Thoughts?

If I understood the patch correctly, the ‘x11-shepherd-service’
procedure finds a first file like ‘/tmp/.X11-unix/X0’ or
‘/tmp/.X11-unix/X2’ which is readable by a user which runs the Shepherd.

Is it possible to allow a user to exactly specify a list of files in
‘/tmp/.X11-unix’ directory, which will be checked?  It will be useful
for VNC users to make sure X11 services are running on a specified
DISPLAY. Otherwise X11 services will be running on a DISPLAY handled by
VNC after boot and never on DISPLAY which becomes available after
authentication (XWayland, GDM , SLIM, LightDM, etc).

Regards,
Oleg.
Brian Cully Aug. 16, 2023, 8:55 p.m. UTC | #2
The largest issue I see with this patch is that it doesn't correlate the
X11 socket with the session being used in cases where there's more than
one X11 display.

If, for instance, I start an X session on the console, allocating the
first display (:0), everything will start up correctly. If I then log in
from a remote host with SSH using X forwarding, I'll get another display
allocated (:1), but this isn't accounted for. If I do these operations
in reverse, first starting my X-forwarded SSH session, then logging in
via console, it will almost certainly not do what just about anyone
wants.

This does presume the Shepherd can be started multiple times for a given
user, and run concurrently, though this does not appear to actually be
the case, since there's a single global location for the socket, which
isn't differentiated by session. But that's a separate issue.

This also doesn't handle the case of the X11 server going away, either
by crash or user request. If we're starting stuff on behalf of users
when it comes up, it seems to me we should also be stopping stuff on
users' behalf when it comes down. The lack of handling this could easily
lead to resource-churning loops where X11 goes away, but Shepherd
services continuously restart themselves trying to connect to a display
that no longer exists.

If this is only meant to be used when using a display manager, a la gdm,
then it might be ok. I'm not sure, since I don't use them. When logging
out of an X session started from gdm, is the user's shepherd process
stopped? Is it stopped gracefully? What about sddm? lightdm?
Andrew Tropin Sept. 5, 2023, noon UTC | #3
On 2023-08-16 16:55, Brian Cully wrote:

> The largest issue I see with this patch is that it doesn't correlate the
> X11 socket with the session being used in cases where there's more than
> one X11 display.
>
> If, for instance, I start an X session on the console, allocating the
> first display (:0), everything will start up correctly. If I then log in
> from a remote host with SSH using X forwarding, I'll get another display
> allocated (:1), but this isn't accounted for. If I do these operations
> in reverse, first starting my X-forwarded SSH session, then logging in
> via console, it will almost certainly not do what just about anyone
> wants.
>
> This does presume the Shepherd can be started multiple times for a given
> user, and run concurrently, though this does not appear to actually be
> the case, since there's a single global location for the socket, which
> isn't differentiated by session. But that's a separate issue.
>
> This also doesn't handle the case of the X11 server going away, either
> by crash or user request. If we're starting stuff on behalf of users
> when it comes up, it seems to me we should also be stopping stuff on
> users' behalf when it comes down. The lack of handling this could easily
> lead to resource-churning loops where X11 goes away, but Shepherd
> services continuously restart themselves trying to connect to a display
> that no longer exists.
>
> If this is only meant to be used when using a display manager, a la gdm,
> then it might be ok. I'm not sure, since I don't use them. When logging
> out of an X session started from gdm, is the user's shepherd process
> stopped? Is it stopped gracefully? What about sddm? lightdm?

I have been seeking for the solution for this for some time and also
tried similiar thing as Ludo's patch does and I'm agree with concerns
mentioned above by Brian.

At the end in rde we decided to start shepherd by Sway (wayland
compositor), so all the services have proper environment variables.  It
has other downsides, but overall works well for usual desktop use cases.

I don't have a complete generic answer to this problem yet.
Ludovic Courtès Sept. 14, 2023, 8:38 p.m. UTC | #4
Hello Brian & Oleg,

Brian Cully <bjc@spork.org> skribis:

> If, for instance, I start an X session on the console, allocating the
> first display (:0), everything will start up correctly. If I then log in
> from a remote host with SSH using X forwarding, I'll get another display
> allocated (:1), but this isn't accounted for. If I do these operations
> in reverse, first starting my X-forwarded SSH session, then logging in
> via console, it will almost certainly not do what just about anyone
> wants.

Yeah, and similarly with the scenario Oleg describes.

> This does presume the Shepherd can be started multiple times for a given
> user,

No no, but it assumes simple scenarios: when you first login locally, X
is not running yet, but you eventually start it and that’s the display
you want your services to use.  Anything beyond that won’t work, as you
point out.

A simple improvement would be to stop the service when the relevant
/tmp/.X11-unix socket disappears.

As for which display to use when several are available (the SSH example
above), I don’t know.  Apparently elogind doesn’t know which display
corresponds to a “seat”.  Maybe we shouldn’t try to guess and instead
let users specify it, for instance with ‘herd start x11-display :42’?

Now, without this service the situation is even worse: shepherd and its
sub-processes inherit whatever ‘DISPLAY’ value was in its environment,
if any, and that’s it.  This service is a hack, but might still do more
good than harm?

Ludo’.
Oleg Pykhalov Sept. 14, 2023, 10:39 p.m. UTC | #5
Ludovic Courtès <ludo@gnu.org> writes:

[…]

> Now, without this service the situation is even worse: shepherd and its
> sub-processes inherit whatever ‘DISPLAY’ value was in its environment,
> if any, and that’s it.  This service is a hack, but might still do more
> good than harm?

Inheriting environment variable is under the user's controll. Finding a
readable file by the user is less (requires to start x11 sessions in a
specific order). By more controll I mean the user could stop Shepherd on
a DISPLAY :0 and start it on DISPLAY :1 without stopping x11 sessions.

In any case, current patch with or without specification of files order
(or ‘herd start x11-display :42’) will change current behaviour. So, I
think a small entry in ‘news.scm’ could save somebody a day.


Regards,
Oleg.
diff mbox series

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index 22590b4f9c..a99ef8e5e8 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -44067,6 +44067,28 @@  Desktop Home Services
 may find useful on ``desktop'' systems running a graphical user
 environment such as Xorg.
 
+@cindex X Window, for Guix Home services
+@cindex X11, in Guix Home
+@defvar home-x11-service-type
+This is the service type representing the X Window graphical display
+server (also referred to as ``X11'').
+
+X Window is necessarily started by a system service; on Guix System,
+starting it is the responsibility of @code{gdm-service-type} and similar
+services (@pxref{X Window}).  At the level of Guix Home, as an
+unprivileged user, we cannot start X Window; all we can do is check
+whether it is running.  This is what this service does.
+
+As a user, you probably don't need to worry or explicitly instantiate
+@code{home-x11-service-type}.  Services that require an X Window
+graphical display, such as @code{home-redshift-service-type} below,
+instantiate it and depend on its corresponding @code{x11-display}
+Shepherd service (@pxref{Shepherd Home Service}).  When X Window is
+running, the @code{x11-display} Shepherd service starts and sets the
+@code{DISPLAY} environment variable of the @command{shepherd} process;
+otherwise, it fails to start.
+@end defvar
+
 @defvar home-redshift-service-type
 This is the service type for @uref{https://github.com/jonls/redshift,
 Redshift}, a program that adjusts the display color temperature
diff --git a/gnu/home/services/desktop.scm b/gnu/home/services/desktop.scm
index 626918fd9e..b293031fd1 100644
--- a/gnu/home/services/desktop.scm
+++ b/gnu/home/services/desktop.scm
@@ -1,5 +1,5 @@ 
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2022-2023 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2022 ( <paren@disroot.org>
 ;;; Copyright © 2023 conses <contact@conses.eu>
 ;;; Copyright © 2023 Janneke Nieuwenhuizen <janneke@gnu.org>
@@ -30,7 +30,9 @@  (define-module (gnu home services desktop)
   #:use-module (guix gexp)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 match)
-  #:export (home-redshift-configuration
+  #:export (home-x11-service-type
+
+            home-redshift-configuration
             home-redshift-configuration?
             home-redshift-service-type
 
@@ -43,6 +45,69 @@  (define-module (gnu home services desktop)
             home-xmodmap-configuration
             home-xmodmap-service-type))
 
+
+;;;
+;;; Waiting for X11.
+;;;
+
+(define (x11-shepherd-service delay)
+  (list (shepherd-service
+         (provision '(x11-display))
+         (modules '((ice-9 ftw)
+                    (ice-9 match)
+                    (srfi srfi-1)))
+         (start #~(lambda ()
+                    (define x11-directory
+                      "/tmp/.X11-unix")
+
+                    ;; Wait for an accessible socket to show up in
+                    ;; X11-DIRECTORY, up to DELAY seconds.
+                    (let loop ((attempts #$delay))
+                      (define socket
+                        (find (match-lambda
+                                ((or "." "..") #f)
+                                (name
+                                 (let ((name (in-vicinity x11-directory
+                                                          name)))
+                                   (access? name O_RDWR))))
+                              (or (scandir x11-directory) '())))
+
+                      (if (and socket (string-prefix? "X" socket))
+                          (let ((display (string-append
+                                          ":" (string-drop socket 1))))
+                            (format #t "X11 display server found at ~s.~%"
+                                    display)
+                            (setenv "DISPLAY" display)
+                            display)
+                          (if (zero? attempts)
+                              (begin
+                                (display
+                                 "X11 display server did not show up; \
+giving up.\n"
+                                 (current-error-port))
+                                #f)
+                              (begin
+                                (sleep 1)
+                                (loop (- attempts 1))))))))
+         (stop #~(lambda (_)
+                   (unsetenv "DISPLAY")
+                   #f))
+         (respawn? #f))))
+
+(define home-x11-service-type
+  (service-type
+   (name 'home-x11-display)
+   (extensions (list (service-extension home-shepherd-service-type
+                                        x11-shepherd-service)))
+   (default-value 5)
+   (description
+    "Create a @code{x11-display} Shepherd service that waits for the X
+Window (or ``X11'') graphical display server to be up and running, up to a
+configurable delay, and sets the @code{DISPLAY} environment variable of
+@command{shepherd} itself accordingly.  If no accessible X11 server shows up
+during that time, the @code{x11-display} service is marked as failing to
+start.")))
+
 
 ;;;
 ;;; Redshift.
@@ -169,8 +234,11 @@  (define (redshift-shepherd-service config)
   (list (shepherd-service
          (documentation "Redshift program.")
          (provision '(redshift))
-         ;; FIXME: This fails to start if Home is first activated from a
-         ;; non-X11 session.
+
+         ;; Depend on 'x11-display', which sets 'DISPLAY' if an X11 server is
+         ;; available, and fails to start otherwise.
+         (requirement '(x11-display))
+
          (start #~(make-forkexec-constructor
                    (list #$(file-append redshift "/bin/redshift")
                          "-c" #$config-file)))
@@ -181,7 +249,11 @@  (define home-redshift-service-type
   (service-type
    (name 'home-redshift)
    (extensions (list (service-extension home-shepherd-service-type
-                                        redshift-shepherd-service)))
+                                        redshift-shepherd-service)
+                     ;; Ensure 'home-x11-service-type' is instantiated so we
+                     ;; can depend on the Shepherd 'x11-display' service.
+                     (service-extension home-x11-service-type
+                                        (const #t))))
    (default-value (home-redshift-configuration))
    (description
     "Run Redshift, a program that adjusts the color temperature of display