From 757e620a8dd26902215a332af71cfcdf08574803 Mon Sep 17 00:00:00 2001 From: Ray Strode 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 #include #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