From ec305406b92e2bfaf8cc299ddc06113c614e1a63 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 9 Feb 2021 15:43:05 +0100 Subject: local-display-factory: Wait for seats to become graphical It may happen that seats are not graphical initially because the DRM device is not ready yet. In that case, ignore the seat and wait for the CanGraphical property notification in order to add it at that point. However, there seem to be some rare cases where CanGraphical will never turn TRUE. To catch these, launch "udevadm settle", and start assuming that "seat0" is graphical after it returns. Fixes: #662 --- daemon/gdm-local-display-factory.c | 120 +++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/daemon/gdm-local-display-factory.c b/daemon/gdm-local-display-factory.c index 8c898215..344a074d 100644 --- a/daemon/gdm-local-display-factory.c +++ b/daemon/gdm-local-display-factory.c @@ -62,6 +62,10 @@ struct _GdmLocalDisplayFactory guint seat_new_id; guint seat_removed_id; + guint seat_prop_changed_id; + + gboolean udev_settle_started; + gboolean udev_settled; #if defined(ENABLE_USER_DISPLAY_SERVER) unsigned int active_vt; @@ -450,17 +454,74 @@ lookup_prepared_display_by_seat_id (const char *id, return lookup_by_seat_id (id, display, user_data); } +static void +udevadm_settle_cb (GPid pid, + gint status, + gpointer user_data) +{ + GdmLocalDisplayFactory *factory = user_data; + + if (status != 0) + g_warning ("GdmLocalDisplayFactory: udevadm settle finished with status %d", status); + g_debug ("GdmLocalDisplayFactory: udevadm settle finished.", + status); + factory->udev_settled = TRUE; + + /* Simply try to re-add seat0. If it is there already (i.e. CanGraphical + * turned TRUE, then we'll find it and it will not be created again. + */ + create_display (factory, "seat0", 0); +} + static GdmDisplay * create_display (GdmLocalDisplayFactory *factory, const char *seat_id, int failures) { gboolean is_initial; + int can_graphical; const char *session_type = NULL; GdmDisplayStore *store; GdmDisplay *display = NULL; g_autofree char *login_session_id = NULL; + can_graphical = sd_seat_can_graphical (seat_id); + if (can_graphical < 0) + return NULL; + + /* If seat0 is not graphical, then start udevadm settle if needed. */ + if (!can_graphical && !factory->udev_settle_started && g_strcmp0 (seat_id, "seat0") == 0) { + g_autoptr(GError) error = NULL; + gchar * argv[] = { "udevadm", "settle", NULL }; + GPid pid; + + factory->udev_settle_started = TRUE; + + g_debug ("GdmLocalDisplayFactory: Starting udevadm settle to wait for all devices."); + + if (!g_spawn_async (NULL, argv, NULL, + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH | G_SPAWN_LEAVE_DESCRIPTORS_OPEN, + NULL, NULL, + &pid, + &error)) { + g_warning ("GdmLocalDisplayFactory: Failed to spawn udevadm settle: %s", error->message); + return NULL; + } + + g_child_watch_add (pid, udevadm_settle_cb, factory); + + return NULL; + } + + /* If udev is settled and this is seat0 then assume we can use it */ + if (!can_graphical && factory->udev_settled && g_strcmp0 (seat_id, "seat0") == 0) { + g_warning ("GdmLocalDisplayFactory: Assuming we can use seat0 even though it is not graphical!"); + can_graphical = TRUE; + } + + if (!can_graphical) + return NULL; + if (g_strcmp0 (seat_id, "seat0") == 0) { is_initial = TRUE; if (failures == 0 && gdm_local_display_factory_use_wayland ()) @@ -599,6 +660,7 @@ on_seat_new (GDBusConnection *connection, const char *seat; g_variant_get (parameters, "(&s&o)", &seat, NULL); + create_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat, 0); } @@ -617,6 +679,49 @@ on_seat_removed (GDBusConnection *connection, delete_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat); } +static void +on_seat_prop_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + const gchar *seat = NULL; + g_autoptr(GVariant) changed_props = NULL; + g_autoptr(GVariant) changed_prop = NULL; + g_autofree const gchar * const *invalidated_props = NULL; + gboolean changed = FALSE; + int res; + + /* Extract seat id, i.e. the last element of the object path. */ + seat = strrchr (object_path, '/'); + if (seat == NULL) + return; + seat += 1; + + g_variant_get (parameters, "(s@a{sv}^a&s)", NULL, &changed_props, &invalidated_props); + + changed_prop = g_variant_lookup_value (changed_props, "CanGraphical", NULL); + if (changed_prop) + changed = TRUE; + if (!changed && g_strv_contains (invalidated_props, "CanGraphical")) + changed = TRUE; + + if (!changed) + return; + + res = sd_seat_can_graphical (seat); + if (res < 0) + return; + + if (res) + create_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat, 0); + else + delete_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat); +} + static gboolean lookup_by_session_id (const char *id, GdmDisplay *display, @@ -851,6 +956,16 @@ gdm_local_display_factory_start_monitor (GdmLocalDisplayFactory *factory) on_seat_removed, g_object_ref (factory), g_object_unref); + factory->seat_prop_changed_id = g_dbus_connection_signal_subscribe (factory->connection, + "org.freedesktop.login1", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + NULL, + "org.freedesktop.login1.Seat", + G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, + on_seat_prop_changed, + g_object_ref (factory), + g_object_unref); #if defined(ENABLE_USER_DISPLAY_SERVER) io_channel = g_io_channel_new_file ("/sys/class/tty/tty0/active", "r", NULL); @@ -879,6 +994,11 @@ gdm_local_display_factory_stop_monitor (GdmLocalDisplayFactory *factory) factory->seat_removed_id); factory->seat_removed_id = 0; } + if (factory->seat_prop_changed_id) { + g_dbus_connection_signal_unsubscribe (factory->connection, + factory->seat_prop_changed_id); + factory->seat_prop_changed_id = 0; + } #if defined(ENABLE_USER_DISPLAY_SERVER) if (factory->active_vt_watch_id) { g_source_remove (factory->active_vt_watch_id); -- cgit v1.2.1