From 757e620a8dd26902215a332af71cfcdf08574803 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 4 Oct 2018 10:40:41 -0400
Subject: [PATCH] local-display-factory: defer initialization for
 CanGraphical=no seats

During startup a seat may not be ready for a display server yet.

This commit changes GDM to wait until the seat reports that it is
CanGraphical capable, before trying to put a login screen on it.

Closes https://gitlab.gnome.org/bugzilla-migration/gdm/issues/103
---
 daemon/gdm-local-display-factory.c | 132 +++++++++++++++++++++++++++--
 1 file changed, 126 insertions(+), 6 deletions(-)

diff --git a/daemon/gdm-local-display-factory.c b/daemon/gdm-local-display-factory.c
index 891c25375..5430085ea 100644
--- a/daemon/gdm-local-display-factory.c
+++ b/daemon/gdm-local-display-factory.c
@@ -29,92 +29,98 @@
 #include <gio/gio.h>
 
 #include <systemd/sd-login.h>
 
 #include "gdm-common.h"
 #include "gdm-manager.h"
 #include "gdm-display-factory.h"
 #include "gdm-local-display-factory.h"
 #include "gdm-local-display-factory-glue.h"
 
 #include "gdm-settings-keys.h"
 #include "gdm-settings-direct.h"
 #include "gdm-display-store.h"
 #include "gdm-local-display.h"
 #include "gdm-legacy-display.h"
 
 #define GDM_LOCAL_DISPLAY_FACTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_LOCAL_DISPLAY_FACTORY, GdmLocalDisplayFactoryPrivate))
 
 #define GDM_DBUS_PATH                       "/org/gnome/DisplayManager"
 #define GDM_LOCAL_DISPLAY_FACTORY_DBUS_PATH GDM_DBUS_PATH "/LocalDisplayFactory"
 #define GDM_MANAGER_DBUS_NAME               "org.gnome.DisplayManager.LocalDisplayFactory"
 
 #define MAX_DISPLAY_FAILURES 5
 #define WAIT_TO_FINISH_TIMEOUT 10 /* seconds */
 
 struct GdmLocalDisplayFactoryPrivate
 {
         GdmDBusLocalDisplayFactory *skeleton;
         GDBusConnection *connection;
         GHashTable      *used_display_numbers;
+        GHashTable      *seat_proxies;
 
         /* FIXME: this needs to be per seat? */
         guint            num_failures;
 
         guint            seat_new_id;
         guint            seat_removed_id;
 
 #if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER)
         char            *tty_of_active_vt;
         guint            active_vt_watch_id;
         guint            wait_to_finish_timeout_id;
 #endif
 };
 
 enum {
         PROP_0,
 };
 
 static void     gdm_local_display_factory_class_init    (GdmLocalDisplayFactoryClass *klass);
 static void     gdm_local_display_factory_init          (GdmLocalDisplayFactory      *factory);
 static void     gdm_local_display_factory_finalize      (GObject                     *object);
 
 static GdmDisplay *create_display                       (GdmLocalDisplayFactory      *factory,
                                                          const char                  *seat_id,
                                                          const char                  *session_type,
                                                          gboolean                    initial_display);
 
 static void     on_display_status_changed               (GdmDisplay                  *display,
                                                          GParamSpec                  *arg1,
                                                          GdmLocalDisplayFactory      *factory);
 
 static gboolean gdm_local_display_factory_sync_seats    (GdmLocalDisplayFactory *factory);
+
+static gboolean create_seat_proxy (GdmLocalDisplayFactory *self,
+                                   const char             *seat_id,
+                                   const char             *seat_path);
+
 static gpointer local_display_factory_object = NULL;
 static gboolean lookup_by_session_id (const char *id,
                                       GdmDisplay *display,
                                       gpointer    user_data);
 
 G_DEFINE_TYPE (GdmLocalDisplayFactory, gdm_local_display_factory, GDM_TYPE_DISPLAY_FACTORY)
 
 GQuark
 gdm_local_display_factory_error_quark (void)
 {
         static GQuark ret = 0;
         if (ret == 0) {
                 ret = g_quark_from_static_string ("gdm_local_display_factory_error");
         }
 
         return ret;
 }
 
 static void
 listify_hash (gpointer    key,
               GdmDisplay *display,
               GList     **list)
 {
         *list = g_list_prepend (*list, key);
 }
 
 static int
 sort_nums (gpointer a,
            gpointer b)
 {
@@ -409,60 +415,61 @@ lookup_by_seat_id (const char *id,
         return res;
 }
 
 static gboolean
 lookup_prepared_display_by_seat_id (const char *id,
                                     GdmDisplay *display,
                                     gpointer    user_data)
 {
         int status;
 
         status = gdm_display_get_status (display);
 
         if (status != GDM_DISPLAY_PREPARED)
                 return FALSE;
 
         return lookup_by_seat_id (id, display, user_data);
 }
 
 static GdmDisplay *
 create_display (GdmLocalDisplayFactory *factory,
                 const char             *seat_id,
                 const char             *session_type,
                 gboolean                initial)
 {
         GdmDisplayStore *store;
         GdmDisplay      *display = NULL;
         g_autofree char *login_session_id = NULL;
 
         g_debug ("GdmLocalDisplayFactory: %s login display for seat %s requested",
                  session_type? : "X11", seat_id);
+
         store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory));
 
         if (sd_seat_can_multi_session (seat_id))
                 display = gdm_display_store_find (store, lookup_prepared_display_by_seat_id, (gpointer) seat_id);
         else
                 display = gdm_display_store_find (store, lookup_by_seat_id, (gpointer) seat_id);
 
         /* Ensure we don't create the same display more than once */
         if (display != NULL) {
                 g_debug ("GdmLocalDisplayFactory: display already created");
                 return NULL;
         }
 
         /* If we already have a login window, switch to it */
         if (gdm_get_login_window_session_id (seat_id, &login_session_id)) {
                 GdmDisplay *display;
 
                 display = gdm_display_store_find (store,
                                                   lookup_by_session_id,
                                                   (gpointer) login_session_id);
                 if (display != NULL &&
                     (gdm_display_get_status (display) == GDM_DISPLAY_MANAGED ||
                      gdm_display_get_status (display) == GDM_DISPLAY_WAITING_TO_FINISH)) {
                         g_object_set (G_OBJECT (display), "status", GDM_DISPLAY_MANAGED, NULL);
                         g_debug ("GdmLocalDisplayFactory: session %s found, activating.",
                                  login_session_id);
                         gdm_activate_session_by_id (factory->priv->connection, seat_id, login_session_id);
                         return NULL;
                 }
         }
@@ -493,131 +500,236 @@ create_display (GdmLocalDisplayFactory *factory,
 
         /* let store own the ref */
         g_object_unref (display);
 
         if (! gdm_display_manage (display)) {
                 gdm_display_unmanage (display);
         }
 
         return display;
 }
 
 static void
 delete_display (GdmLocalDisplayFactory *factory,
                 const char             *seat_id) {
 
         GdmDisplayStore *store;
 
         g_debug ("GdmLocalDisplayFactory: Removing used_display_numbers on seat %s", seat_id);
 
         store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory));
         gdm_display_store_foreach_remove (store, lookup_by_seat_id, (gpointer) seat_id);
 }
 
 static gboolean
 gdm_local_display_factory_sync_seats (GdmLocalDisplayFactory *factory)
 {
         GError *error = NULL;
         GVariant *result;
         GVariant *array;
         GVariantIter iter;
-        const char *seat;
+        const char *seat, *path;
 
         g_debug ("GdmLocalDisplayFactory: enumerating seats from logind");
         result = g_dbus_connection_call_sync (factory->priv->connection,
                                               "org.freedesktop.login1",
                                               "/org/freedesktop/login1",
                                               "org.freedesktop.login1.Manager",
                                               "ListSeats",
                                               NULL,
                                               G_VARIANT_TYPE ("(a(so))"),
                                               G_DBUS_CALL_FLAGS_NONE,
                                               -1,
                                               NULL, &error);
 
         if (!result) {
                 g_warning ("GdmLocalDisplayFactory: Failed to issue method call: %s", error->message);
                 g_clear_error (&error);
                 return FALSE;
         }
 
         array = g_variant_get_child_value (result, 0);
         g_variant_iter_init (&iter, array);
 
-        while (g_variant_iter_loop (&iter, "(&so)", &seat, NULL)) {
+        while (g_variant_iter_loop (&iter, "(&s&o)", &seat, &path)) {
                 gboolean is_initial;
                 const char *session_type = NULL;
 
                 if (g_strcmp0 (seat, "seat0") == 0) {
                         is_initial = TRUE;
                         if (gdm_local_display_factory_use_wayland ())
                                 session_type = "wayland";
                 } else {
                         is_initial = FALSE;
                 }
 
+                if (!create_seat_proxy (factory, seat, path))
+                        continue;
+
+                if (!sd_seat_can_graphical (seat)) {
+                        g_debug ("GdmLocalDisplayFactory: seat %s not ready for graphical displays", seat);
+                        continue;
+                }
+
                 create_display (factory, seat, session_type, is_initial);
         }
 
         g_variant_unref (result);
         g_variant_unref (array);
         return TRUE;
 }
 
+static void
+on_seat_proxy_properties_changed (GDBusProxy  *proxy,
+                                  GVariant    *changed_properties,
+                                  char       **invalidated_properties,
+                                  gpointer     user_data)
+{
+        GdmLocalDisplayFactory *factory = GDM_LOCAL_DISPLAY_FACTORY (user_data);
+        g_autoptr (GVariant) value = NULL;
+        const char *seat;
+
+        value = g_variant_lookup_value (changed_properties, "CanGraphical", G_VARIANT_TYPE_BOOLEAN);
+
+        if (!value)
+                return;
+
+        g_debug ("GdmLocalDisplayFactory: CanGraphical changed");
+
+        seat = g_object_get_data (G_OBJECT (proxy), "seat-id");
+
+        if (!seat)
+                return;
+
+        if (g_variant_get_boolean (value)) {
+                gboolean is_initial;
+                const char *session_type = NULL;
+
+                g_debug ("GdmLocalDisplayFactory: seat '%s' now graphical", seat);
+
+                if (g_strcmp0 (seat, "seat0") == 0) {
+                        is_initial = TRUE;
+                        if (gdm_local_display_factory_use_wayland ())
+                                session_type = "wayland";
+                } else {
+                        is_initial = FALSE;
+                }
+
+                create_display (factory, seat, session_type, is_initial);
+        } else {
+                g_debug ("GdmLocalDisplayFactory: seat '%s' no longer graphical", seat);
+                delete_display (factory, seat);
+        }
+}
+
+static gboolean
+create_seat_proxy (GdmLocalDisplayFactory *self,
+                   const char             *seat,
+                   const char             *path)
+{
+        g_autoptr (GDBusProxy) proxy = NULL;
+        g_autoptr (GError) error = NULL;
+
+        g_debug ("GdmLocalDisplayFactory: creating seat proxy for seat '%s' with path '%s'",
+                 seat, path);
+
+        proxy = g_dbus_proxy_new_sync (self->priv->connection,
+                                       G_DBUS_PROXY_FLAGS_NONE,
+                                       NULL,
+                                       "org.freedesktop.login1",
+                                       path,
+                                       "org.freedesktop.login1.Seat",
+                                       NULL,
+                                       &error);
+
+        if (proxy == NULL) {
+                g_debug ("GdmLocalDisplayFactory: failed to get proxy to seat '%s' from logind: %s",
+                         seat, error->message);
+                return FALSE;
+        }
+
+        g_hash_table_insert (self->priv->seat_proxies, g_strdup (seat), g_object_ref (proxy));
+        g_object_set_data_full (G_OBJECT (proxy), "seat-id", g_strdup (seat), (GDestroyNotify) g_free);
+        g_signal_connect_object (G_OBJECT (proxy),
+                                 "g-properties-changed",
+                                 G_CALLBACK (on_seat_proxy_properties_changed),
+                                 self,
+                                 0);
+
+        return TRUE;
+}
+
 static void
 on_seat_new (GDBusConnection *connection,
              const gchar     *sender_name,
              const gchar     *object_path,
              const gchar     *interface_name,
              const gchar     *signal_name,
              GVariant        *parameters,
              gpointer         user_data)
 {
-        const char *seat;
+        GdmLocalDisplayFactory *factory = GDM_LOCAL_DISPLAY_FACTORY (user_data);
+        const char *seat, *path;
 
-        g_variant_get (parameters, "(&s&o)", &seat, NULL);
-        create_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat, NULL, FALSE);
+        g_variant_get (parameters, "(&s&o)", &seat, &path);
+
+        g_debug ("GdmLocalDisplayFactory: new seat '%s' available", seat);
+
+        if (!create_seat_proxy (factory, seat, path))
+                return;
+
+        if (!sd_seat_can_graphical (seat)) {
+                g_debug ("GdmLocalDisplayFactory: but not yet ready for graphical displays");
+                return;
+        }
+
+        create_display (factory, seat, NULL, FALSE);
 }
 
 static void
 on_seat_removed (GDBusConnection *connection,
                  const gchar     *sender_name,
                  const gchar     *object_path,
                  const gchar     *interface_name,
                  const gchar     *signal_name,
                  GVariant        *parameters,
                  gpointer         user_data)
 {
+        GdmLocalDisplayFactory *factory = GDM_LOCAL_DISPLAY_FACTORY (user_data);
         const char *seat;
 
         g_variant_get (parameters, "(&s&o)", &seat, NULL);
-        delete_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat);
+
+        g_debug ("GdmLocalDisplayFactory: seat '%s' no longer available", seat);
+
+        g_hash_table_remove (factory->priv->seat_proxies, (gpointer) seat);
+        delete_display (factory, seat);
 }
 
 static gboolean
 lookup_by_session_id (const char *id,
                       GdmDisplay *display,
                       gpointer    user_data)
 {
         const char *looking_for = user_data;
         const char *current;
 
         current = gdm_display_get_session_id (display);
         return g_strcmp0 (current, looking_for) == 0;
 }
 
 #if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER)
 static gboolean
 wait_to_finish_timeout (GdmLocalDisplayFactory *factory)
 {
         finish_waiting_displays_on_seat (factory, "seat0");
         factory->priv->wait_to_finish_timeout_id = 0;
         return G_SOURCE_REMOVE;
 }
 
 static void
 maybe_stop_greeter_in_background (GdmLocalDisplayFactory *factory,
                                   GdmDisplay             *display)
 {
         g_autofree char *display_session_type = NULL;
         gboolean doing_initial_setup = FALSE;
 
@@ -737,60 +849,65 @@ on_vt_changed (GIOChannel    *source,
 
                         g_debug ("GdmLocalDisplayFactory: tty of login window is %s", tty_of_login_window_vt);
                         if (g_strcmp0 (tty_of_login_window_vt, tty_of_previous_vt) == 0) {
                                 GdmDisplayStore *store;
                                 GdmDisplay *display;
 
                                 g_debug ("GdmLocalDisplayFactory: VT switched from login window");
 
                                 store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory));
                                 display = gdm_display_store_find (store,
                                                                   lookup_by_session_id,
                                                                   (gpointer) login_session_id);
 
                                 if (display != NULL)
                                         maybe_stop_greeter_in_background (factory, display);
                         } else {
                                 g_debug ("GdmLocalDisplayFactory: VT not switched from login window");
                         }
                 }
         }
 
         /* if user jumped back to initial vt and it's empty put a login screen
          * on it (unless a login screen is already running elsewhere, then
          * jump to that login screen)
          */
         if (strcmp (factory->priv->tty_of_active_vt, tty_of_initial_vt) != 0) {
                 g_debug ("GdmLocalDisplayFactory: active VT is not initial VT, so ignoring");
                 return G_SOURCE_CONTINUE;
         }
 
+        if (!sd_seat_can_graphical ("seat0")) {
+                g_debug ("GdmLocalDisplayFactory: seat0 not yet ready for graphical displays");
+                return G_SOURCE_CONTINUE;
+        }
+
         if (gdm_local_display_factory_use_wayland ())
                 session_type = "wayland";
 
         g_debug ("GdmLocalDisplayFactory: creating new display on seat0 because of VT change");
 
         create_display (factory, "seat0", session_type, TRUE);
 
         return G_SOURCE_CONTINUE;
 }
 #endif
 
 static void
 gdm_local_display_factory_start_monitor (GdmLocalDisplayFactory *factory)
 {
         g_autoptr (GIOChannel) io_channel = NULL;
 
         factory->priv->seat_new_id = g_dbus_connection_signal_subscribe (factory->priv->connection,
                                                                          "org.freedesktop.login1",
                                                                          "org.freedesktop.login1.Manager",
                                                                          "SeatNew",
                                                                          "/org/freedesktop/login1",
                                                                          NULL,
                                                                          G_DBUS_SIGNAL_FLAGS_NONE,
                                                                          on_seat_new,
                                                                          g_object_ref (factory),
                                                                          g_object_unref);
         factory->priv->seat_removed_id = g_dbus_connection_signal_subscribe (factory->priv->connection,
                                                                              "org.freedesktop.login1",
                                                                              "org.freedesktop.login1.Manager",
                                                                              "SeatRemoved",
@@ -1014,69 +1131,72 @@ gdm_local_display_factory_constructor (GType                  type,
         if (! res) {
                 g_warning ("Unable to register local display factory with system bus");
         }
 
         return G_OBJECT (factory);
 }
 
 static void
 gdm_local_display_factory_class_init (GdmLocalDisplayFactoryClass *klass)
 {
         GObjectClass           *object_class = G_OBJECT_CLASS (klass);
         GdmDisplayFactoryClass *factory_class = GDM_DISPLAY_FACTORY_CLASS (klass);
 
         object_class->get_property = gdm_local_display_factory_get_property;
         object_class->set_property = gdm_local_display_factory_set_property;
         object_class->finalize = gdm_local_display_factory_finalize;
         object_class->constructor = gdm_local_display_factory_constructor;
 
         factory_class->start = gdm_local_display_factory_start;
         factory_class->stop = gdm_local_display_factory_stop;
 
         g_type_class_add_private (klass, sizeof (GdmLocalDisplayFactoryPrivate));
 }
 
 static void
 gdm_local_display_factory_init (GdmLocalDisplayFactory *factory)
 {
         factory->priv = GDM_LOCAL_DISPLAY_FACTORY_GET_PRIVATE (factory);
 
         factory->priv->used_display_numbers = g_hash_table_new (NULL, NULL);
+        factory->priv->seat_proxies = g_hash_table_new_full (NULL, NULL, g_free, g_object_unref);
 }
 
 static void
 gdm_local_display_factory_finalize (GObject *object)
 {
         GdmLocalDisplayFactory *factory;
 
         g_return_if_fail (object != NULL);
         g_return_if_fail (GDM_IS_LOCAL_DISPLAY_FACTORY (object));
 
         factory = GDM_LOCAL_DISPLAY_FACTORY (object);
 
         g_return_if_fail (factory->priv != NULL);
 
+        g_hash_table_destroy (factory->priv->seat_proxies);
+
         g_clear_object (&factory->priv->connection);
         g_clear_object (&factory->priv->skeleton);
 
         g_hash_table_destroy (factory->priv->used_display_numbers);
 
         gdm_local_display_factory_stop_monitor (factory);
 
         G_OBJECT_CLASS (gdm_local_display_factory_parent_class)->finalize (object);
 }
 
 GdmLocalDisplayFactory *
 gdm_local_display_factory_new (GdmDisplayStore *store)
 {
         if (local_display_factory_object != NULL) {
                 g_object_ref (local_display_factory_object);
         } else {
                 local_display_factory_object = g_object_new (GDM_TYPE_LOCAL_DISPLAY_FACTORY,
                                                              "display-store", store,
                                                              NULL);
                 g_object_add_weak_pointer (local_display_factory_object,
                                            (gpointer *) &local_display_factory_object);
         }
 
         return GDM_LOCAL_DISPLAY_FACTORY (local_display_factory_object);
 }
-- 
2.17.1

