From 575edeab57c891a7053279c676e82bebe53222d3 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Tue, 15 Aug 2023 10:53:41 -0400 Subject: [PATCH 01/16] gnome-initial-setup: Bump GLib required version to 2.76 This gives us GStrvBuilder, g_ptr_array_sort_values, etc --- gnome-initial-setup/meson.build | 2 +- meson.build | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gnome-initial-setup/meson.build b/gnome-initial-setup/meson.build index beb96ecd..32b3fe46 100644 --- a/gnome-initial-setup/meson.build +++ b/gnome-initial-setup/meson.build @@ -51,7 +51,7 @@ dependencies = [ dependency ('gsettings-desktop-schemas', version: '>= 3.37.1'), dependency ('fontconfig'), dependency ('gtk4', version: '>= 4.6'), - dependency ('glib-2.0', version: '>= 2.63.1'), + dependency ('glib-2.0', version: '>= 2.76.0'), dependency ('gio-unix-2.0', version: '>= 2.53.0'), dependency ('gdm', version: '>= 3.8.3'), gweather_dep, diff --git a/meson.build b/meson.build index 5d30fcd1..00e24d7b 100644 --- a/meson.build +++ b/meson.build @@ -30,8 +30,8 @@ conf.set_quoted('LIBEXECDIR', libexec_dir) conf.set('SECRET_API_SUBJECT_TO_CHANGE', true) conf.set_quoted('G_LOG_DOMAIN', 'InitialSetup') conf.set('G_LOG_USE_STRUCTURED', true) -conf.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_64') -conf.set('GLIB_VERSION_MAX_ALLOWED', 'GLIB_VERSION_2_64') +conf.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_76') +conf.set('GLIB_VERSION_MAX_ALLOWED', 'GLIB_VERSION_2_76') enable_systemd = get_option('systemd') if enable_systemd -- 2.43.0 From 60a2db34bd50d561e716a63dd526ee7ff4810a29 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sat, 9 Sep 2023 17:07:46 -0400 Subject: [PATCH 02/16] keyboard: Don't require localed for existing user mode If we're in existing user mode, the user may not have permission to set the system keymap. This commit makes sure that lack of permission doesn't prevent the keyboard page from completing. --- gnome-initial-setup/pages/keyboard/gis-keyboard-page.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c index fa41230f..da384495 100644 --- a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c +++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c @@ -415,8 +415,13 @@ update_page_complete (GisKeyboardPage *self) GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); gboolean complete; - complete = (priv->localed != NULL && - cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser)) != NULL); + if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) { + complete = (priv->localed != NULL && + cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser)) != NULL); + } else { + complete = cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser)) != NULL; + } + gis_page_set_complete (GIS_PAGE (self), complete); } -- 2.43.0 From 1478cce3663bfa276066c073be3bbb65bc4b1785 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Fri, 8 Sep 2023 11:02:39 -0400 Subject: [PATCH 03/16] language: Don't proceed until localed has set locale In sysmte modes, the keyboard page requires reading the locale from localed, so we need to make sure the setting has been applied before proceeding to the keyboard page from the language page. This commit changes the Next button to desensitize if a set locale operation is pending. --- .../pages/language/gis-language-page.c | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/gnome-initial-setup/pages/language/gis-language-page.c b/gnome-initial-setup/pages/language/gis-language-page.c index 87b9f2d8..26a01257 100644 --- a/gnome-initial-setup/pages/language/gis-language-page.c +++ b/gnome-initial-setup/pages/language/gis-language-page.c @@ -56,6 +56,23 @@ typedef struct _GisLanguagePagePrivate GisLanguagePagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (GisLanguagePage, gis_language_page, GIS_TYPE_PAGE); +static void +on_locale_set (GDBusProxy *proxy, + GAsyncResult *result, + GisLanguagePage *self) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) call_result = NULL; + + call_result = g_dbus_proxy_call_finish (proxy, result, &error); + + if (error != NULL) { + g_warning ("Could not set system locale: %s", error->message); + } + + gis_page_set_complete (GIS_PAGE (self), TRUE); +} + static void set_localed_locale (GisLanguagePage *self) { @@ -72,7 +89,9 @@ set_localed_locale (GisLanguagePage *self) "SetLocale", g_variant_new ("(asb)", b, TRUE), G_DBUS_CALL_FLAGS_NONE, - -1, NULL, NULL, NULL); + -1, priv->cancellable, + (GAsyncReadyCallback) on_locale_set, + self); g_variant_builder_unref (b); } @@ -127,6 +146,9 @@ language_changed (CcLanguageChooser *chooser, gtk_widget_set_default_direction (gtk_get_locale_direction ()); if (gis_driver_get_mode (driver) == GIS_DRIVER_MODE_NEW_USER) { + + gis_page_set_complete (GIS_PAGE (page), FALSE); + if (g_permission_get_allowed (priv->permission)) { set_localed_locale (page); } @@ -177,6 +199,7 @@ localed_proxy_ready (GObject *source, } priv->localed = proxy; + gis_page_set_complete (GIS_PAGE (self), TRUE); } static void @@ -251,8 +274,10 @@ gis_language_page_constructed (GObject *object) object); g_object_unref (bus); } - - gis_page_set_complete (GIS_PAGE (page), TRUE); + else + { + gis_page_set_complete (GIS_PAGE (page), TRUE); + } gtk_widget_set_visible (GTK_WIDGET (page), TRUE); } -- 2.43.0 From e8d5b6a4c1ee9bfb3f8ed7dbe5f25053fae7a1ae Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 24 Aug 2023 21:19:40 -0400 Subject: [PATCH 04/16] keyboard: Get default input sources from gnome-desktop Right now, we figure out the default input sources ourselves, based on the current locale and layout information coming from localed. This logic needs to be duplicated in several components, so its now provided by gnome-desktop. This commit changes it over to use gnome-desktop APIs. The same time if leverages a gnome-desktop API to fix a bug where cyrillic layouts were getting added without a latin counterpart. --- .../pages/keyboard/gis-keyboard-page.c | 475 +++++++++--------- 1 file changed, 239 insertions(+), 236 deletions(-) diff --git a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c index da384495..f2bfe164 100644 --- a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c +++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c @@ -44,6 +44,8 @@ #define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources" #define KEY_CURRENT_INPUT_SOURCE "current" #define KEY_INPUT_SOURCES "sources" +#define KEY_MRU_SOURCES "mru-sources" +#define KEY_INPUT_OPTIONS "xkb-options" struct _GisKeyboardPagePrivate { GtkWidget *input_chooser; @@ -52,8 +54,14 @@ struct _GisKeyboardPagePrivate { GCancellable *cancellable; GPermission *permission; GSettings *input_settings; - - GSList *system_sources; + char **default_input_source_ids; + char **default_input_source_types; + char **default_options; + char **system_layouts; + char **system_variants; + char **system_options; + + gboolean should_skip; }; typedef struct _GisKeyboardPagePrivate GisKeyboardPagePrivate; @@ -72,98 +80,191 @@ gis_keyboard_page_finalize (GObject *object) g_clear_object (&priv->permission); g_clear_object (&priv->localed); g_clear_object (&priv->input_settings); - - g_slist_free_full (priv->system_sources, g_free); + g_clear_pointer (&priv->default_input_source_ids, g_strfreev); + g_clear_pointer (&priv->default_input_source_types, g_strfreev); + g_clear_pointer (&priv->default_options, g_strfreev); + g_clear_pointer (&priv->system_layouts, g_strfreev); + g_clear_pointer (&priv->system_variants, g_strfreev); + g_clear_pointer (&priv->system_options, g_strfreev); G_OBJECT_CLASS (gis_keyboard_page_parent_class)->finalize (object); } static void -set_input_settings (GisKeyboardPage *self) +add_defaults_to_variant_builder (GisKeyboardPage *self, + const char *already_added_type, + const char *already_added_id, + GVariantBuilder *input_source_builder, + char **default_layout) + { GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); - const gchar *type; - const gchar *id; - GVariantBuilder builder; - GSList *l; - gboolean is_xkb_source = FALSE; + size_t i; - type = cc_input_chooser_get_input_type (CC_INPUT_CHOOSER (priv->input_chooser)); - id = cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser)); + for (i = 0; priv->default_input_source_ids && priv->default_input_source_ids[i] != NULL; i++) { + if (g_strcmp0 (already_added_id, priv->default_input_source_ids[i]) == 0 && g_strcmp0 (already_added_type, priv->default_input_source_types[i]) == 0) { + continue; + } - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)")); + g_variant_builder_add (input_source_builder, "(ss)", priv->default_input_source_types[i], priv->default_input_source_ids[i]); - if (g_str_equal (type, "xkb")) { - g_variant_builder_add (&builder, "(ss)", type, id); - is_xkb_source = TRUE; + if (*default_layout != NULL) { + if (!gnome_input_source_is_non_latin (priv->default_input_source_types[i], priv->default_input_source_ids[i])) { + *default_layout = g_strdup (priv->default_input_source_ids[i]); + } + } } +} - for (l = priv->system_sources; l; l = l->next) { - const gchar *sid = l->data; - if (g_str_equal (id, sid) && g_str_equal (type, "xkb")) - continue; +static void +add_input_source_to_arrays (GisKeyboardPage *self, + const char *type, + const char *id, + GPtrArray *layouts_array, + GPtrArray *variants_array) +{ + g_auto(GStrv) layout_and_variant = NULL; + const char *layout, *variant; + + if (!g_str_equal (type, "xkb")) { + return; + } + + layout_and_variant = g_strsplit (id, "+", -1); + + layout = layout_and_variant[0]; + variant = layout_and_variant[1]?: ""; + + if (g_ptr_array_find_with_equal_func (layouts_array, layout, g_str_equal, NULL) && + g_ptr_array_find_with_equal_func (variants_array, variant, g_str_equal, NULL)) { + return; + } + + g_ptr_array_add (layouts_array, g_strdup (layout)); + g_ptr_array_add (variants_array, g_strdup (variant)); +} + +static void +add_defaults_to_arrays (GisKeyboardPage *self, + GPtrArray *layouts_array, + GPtrArray *variants_array) +{ + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + size_t i; + + for (i = 0; priv->default_input_source_ids && priv->default_input_source_ids[i] != NULL; i++) { + add_input_source_to_arrays (self, priv->default_input_source_types[i], priv->default_input_source_ids[i], layouts_array, variants_array); + } +} + +static void +set_input_settings (GisKeyboardPage *self, + const char *type, + const char *id) +{ + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + g_autofree char *layout = NULL; + g_autofree char *variant = NULL; + g_autoptr(GVariant) default_input_sources = NULL; + g_autoptr(GVariant) input_sources = NULL; + g_autoptr(GPtrArray) layouts_array = NULL; + g_autoptr(GPtrArray) variants_array = NULL; + GVariantBuilder input_source_builder; + GVariantBuilder input_options_builder; + g_autoptr(GVariant) input_options = NULL; + gboolean is_system_mode; + size_t i; + g_autofree char *default_input_source_id = NULL; + + default_input_sources = g_settings_get_default_value (priv->input_settings, KEY_INPUT_SOURCES); + input_sources = g_settings_get_value (priv->input_settings, KEY_INPUT_SOURCES); + + if (!g_variant_equal (default_input_sources, input_sources)) + return; + + g_clear_pointer (&input_sources, g_variant_unref); + + g_variant_builder_init (&input_options_builder, G_VARIANT_TYPE ("as")); + + is_system_mode = gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER; + + layouts_array = g_ptr_array_new (); + variants_array = g_ptr_array_new (); + + /* Notice the added latin layout (if relevant) gets put first for gsettings + * (input_source_builder and last for localed (layouts_array/variants_array) + * This ensures we get a cyrillic layout on ttys, but a latin layout by default + * in the UI. + */ + g_variant_builder_init (&input_source_builder, G_VARIANT_TYPE ("a(ss)")); + if (type != NULL && id != NULL) { + add_input_source_to_arrays (self, type, id, layouts_array, variants_array); + + if (gnome_input_source_is_non_latin (type, id)) { + default_input_source_id = g_strdup ("us"); + add_input_source_to_arrays (self, "xkb", default_input_source_id, layouts_array, variants_array); + g_variant_builder_add (&input_source_builder, "(ss)", "xkb", default_input_source_id); + } else { + default_input_source_id = g_strdup (id); + } - g_variant_builder_add (&builder, "(ss)", "xkb", sid); + g_variant_builder_add (&input_source_builder, "(ss)", type, id); } - if (!is_xkb_source) - g_variant_builder_add (&builder, "(ss)", type, id); + if (default_input_source_id == NULL || !is_system_mode) { + add_defaults_to_variant_builder (self, type, id, &input_source_builder, &default_input_source_id); + } + input_sources = g_variant_builder_end (&input_source_builder); + + for (i = 0; priv->default_options[i] != NULL; i++) { + g_variant_builder_add (&input_options_builder, "s", priv->default_options[i]); + } + input_options = g_variant_builder_end (&input_options_builder); + + add_defaults_to_arrays (self, layouts_array, variants_array); + g_ptr_array_add (layouts_array, NULL); + g_ptr_array_add (variants_array, NULL); + + priv->system_layouts = (char **) g_ptr_array_steal (layouts_array, NULL); + priv->system_variants = (char **) g_ptr_array_steal (variants_array, NULL); - g_settings_set_value (priv->input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder)); - g_settings_set_uint (priv->input_settings, KEY_CURRENT_INPUT_SOURCE, 0); + g_variant_get (input_options, "^as", &priv->system_options); + + g_settings_set_value (priv->input_settings, KEY_INPUT_SOURCES, g_steal_pointer (&input_sources)); + g_settings_set_value (priv->input_settings, KEY_INPUT_OPTIONS, g_steal_pointer (&input_options)); + + if (default_input_source_id != NULL) { + GVariantBuilder mru_input_source_builder; + + g_variant_builder_init (&mru_input_source_builder, G_VARIANT_TYPE ("a(ss)")); + g_variant_builder_add (&mru_input_source_builder, "(ss)", type, default_input_source_id); + g_settings_set_value (priv->input_settings, KEY_MRU_SOURCES, g_variant_builder_end (&mru_input_source_builder)); + } - g_settings_apply (priv->input_settings); + g_settings_apply (priv->input_settings); } static void set_localed_input (GisKeyboardPage *self) { GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); - const gchar *layout, *variant; - GString *layouts; - GString *variants; - GSList *l; + g_autofree char *layouts = NULL; + g_autofree char *variants = NULL; + g_autofree char *options = NULL; if (!priv->localed) return; - cc_input_chooser_get_layout (CC_INPUT_CHOOSER (priv->input_chooser), &layout, &variant); - if (layout == NULL) - layout = ""; - if (variant == NULL) - variant = ""; - - layouts = g_string_new (layout); - variants = g_string_new (variant); - -#define LAYOUT(a) (a[0]) -#define VARIANT(a) (a[1] ? a[1] : "") - for (l = priv->system_sources; l; l = l->next) { - const gchar *sid = l->data; - gchar **lv = g_strsplit (sid, "+", -1); - - if (!g_str_equal (LAYOUT (lv), layout) || - !g_str_equal (VARIANT (lv), variant)) { - if (layouts->str[0]) { - g_string_append_c (layouts, ','); - g_string_append_c (variants, ','); - } - g_string_append (layouts, LAYOUT (lv)); - g_string_append (variants, VARIANT (lv)); - } - g_strfreev (lv); - } -#undef LAYOUT -#undef VARIANT + layouts = g_strjoinv (",", priv->system_layouts); + variants = g_strjoinv (",", priv->system_variants); + options = g_strjoinv (",", priv->system_options); g_dbus_proxy_call (priv->localed, "SetX11Keyboard", - g_variant_new ("(ssssbb)", layouts->str, "", variants->str, "", TRUE, TRUE), + g_variant_new ("(ssssbb)", layouts, "", variants, options, TRUE, TRUE), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); - g_string_free (layouts, TRUE); - g_string_free (variants, TRUE); } static void @@ -192,8 +293,13 @@ static void update_input (GisKeyboardPage *self) { GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + const gchar *type; + const gchar *id; - set_input_settings (self); + type = cc_input_chooser_get_input_type (CC_INPUT_CHOOSER (priv->input_chooser)); + id = cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser)); + + set_input_settings (self, type, id); if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) { if (g_permission_get_allowed (priv->permission)) { @@ -215,119 +321,10 @@ gis_keyboard_page_apply (GisPage *page, return FALSE; } -static GSList * -get_localed_input (GDBusProxy *proxy) -{ - GVariant *v; - const gchar *s; - gchar *id; - guint i, n; - gchar **layouts = NULL; - gchar **variants = NULL; - GSList *sources = NULL; - - v = g_dbus_proxy_get_cached_property (proxy, "X11Layout"); - if (v) { - s = g_variant_get_string (v, NULL); - layouts = g_strsplit (s, ",", -1); - g_variant_unref (v); - } - - v = g_dbus_proxy_get_cached_property (proxy, "X11Variant"); - if (v) { - s = g_variant_get_string (v, NULL); - if (s && *s) - variants = g_strsplit (s, ",", -1); - g_variant_unref (v); - } - - if (variants && variants[0]) - n = MIN (g_strv_length (layouts), g_strv_length (variants)); - else if (layouts && layouts[0]) - n = g_strv_length (layouts); - else - n = 0; - - for (i = 0; i < n && layouts[i][0]; i++) { - if (variants && variants[i] && variants[i][0]) - id = g_strdup_printf ("%s+%s", layouts[i], variants[i]); - else - id = g_strdup (layouts[i]); - sources = g_slist_prepend (sources, id); - } - - g_strfreev (variants); - g_strfreev (layouts); - - return sources; -} - static void -add_default_keyboard_layout (GDBusProxy *proxy, - GVariantBuilder *builder) +add_default_input_sources (GisKeyboardPage *self) { - GSList *sources = get_localed_input (proxy); - sources = g_slist_reverse (sources); - - for (; sources; sources = sources->next) - g_variant_builder_add (builder, "(ss)", "xkb", - (const gchar *) sources->data); - - g_slist_free_full (sources, g_free); -} - -static void -add_default_input_sources (GisKeyboardPage *self, - GDBusProxy *proxy) -{ - const gchar *type; - const gchar *id; - gchar *language; - GVariantBuilder builder; - GSettings *input_settings; - - input_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR); - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)")); - - add_default_keyboard_layout (proxy, &builder); - - /* add other input sources */ - language = cc_common_language_get_current_language (); - if (gnome_get_input_source_from_locale (language, &type, &id)) { - if (!g_str_equal (type, "xkb")) - g_variant_builder_add (&builder, "(ss)", type, id); - } - g_free (language); - - g_settings_delay (input_settings); - g_settings_set_value (input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder)); - g_settings_set_uint (input_settings, KEY_CURRENT_INPUT_SOURCE, 0); - g_settings_apply (input_settings); - - g_object_unref (input_settings); -} - -static void -skip_proxy_ready (GObject *source, - GAsyncResult *res, - gpointer data) -{ - GisKeyboardPage *self = data; - GDBusProxy *proxy; - GError *error = NULL; - - proxy = g_dbus_proxy_new_finish (res, &error); - - if (!proxy) { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Failed to contact localed: %s", error->message); - g_error_free (error); - return; - } - - add_default_input_sources (self, proxy); - - g_object_unref (proxy); + set_input_settings (self, NULL, NULL); } static void @@ -336,77 +333,49 @@ gis_keyboard_page_skip (GisPage *page) GisKeyboardPage *self = GIS_KEYBOARD_PAGE (page); GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, - NULL, - "org.freedesktop.locale1", - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - priv->cancellable, - (GAsyncReadyCallback) skip_proxy_ready, - self); + priv->should_skip = TRUE; + + if (priv->default_input_source_ids != NULL) + add_default_input_sources (self); } static void preselect_input_source (GisKeyboardPage *self) { - const gchar *type; - const gchar *id; - gchar *language; - gboolean desktop_got_something; - gboolean desktop_got_input_method; + const char *language = NULL; GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); - GSList *sources = get_localed_input (priv->localed); - - /* These will be added silently after the user selection when - * writing out the settings. */ - g_slist_free_full (priv->system_sources, g_free); - priv->system_sources = g_slist_reverse (sources); - - /* We have two potential sources of information as to which - * source to pre-select here: the keyboard layout that is - * configured system-wide (read from priv->system_sources), - * and a gnome-desktop function that lets us look up a default - * input source for a given language. - * - * An important limitation here is that there is no system-wide - * configuration for input methods, so if the best choice for the - * language is an input method, we will only find it from the - * gnome-desktop lookup. But if both sources give us keyboard layouts, - * we want to prefer the one that's configured system-wide over the one - * from gnome-desktop. - * - * So we first do the gnome-desktop lookup, and keep track of what we - * got. - * - * - If we got an input method, we preselect that, and we're done. - * - If we got a keyboard layout, and there's no system-wide keyboard - * layout set, we preselect the layout we got from gnome-desktop. - * - If we didn't get an input method from gnome-desktop and there - * is a system-wide keyboard layout set, we preselect that. - * - If we got nothing from gnome-desktop and there's no system-wide - * keyboard layout set, we don't preselect anything. - * - * See: - * - https://bugzilla.gnome.org/show_bug.cgi?id=776189 - * - https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/issues/104 - */ - language = cc_common_language_get_current_language (); - desktop_got_something = gnome_get_input_source_from_locale (language, &type, &id); - desktop_got_input_method = (desktop_got_something && g_strcmp0 (type, "xkb") != 0); + language = cc_common_language_get_current_language (); - if (desktop_got_something && (desktop_got_input_method || !priv->system_sources)) { - cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser), - id, type); - } else if (priv->system_sources) { - cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser), - (const gchar *) priv->system_sources->data, - "xkb"); + /* We deduce the initial input source from language if we're in a system mode + * (where preexisting system configuration may be stale) or if the language + * requires an input method (because there is no way for system configuration + * to denote the need for an input method) + * + * If it's a non-system mode we can trust the system configuration is probably + * a better bet than a heuristic based on locale. + */ + if (language != NULL) { + gboolean got_input_source; + const char *id, *type; + + got_input_source = gnome_get_input_source_from_locale (language, &type, &id); + + if (got_input_source) { + gboolean is_system_mode = gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER; + if (is_system_mode || g_str_equal (type, "ibus")) { + cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser), + id, + type); + return; + } + } } - g_free (language); + cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser), + priv->default_input_source_ids[0], + priv->default_input_source_types[0]); } static void @@ -445,9 +414,7 @@ localed_proxy_ready (GObject *source, } priv->localed = proxy; - - preselect_input_source (self); - update_page_complete (self); + update_page_complete (self); } static void @@ -464,6 +431,40 @@ input_changed (CcInputChooser *chooser, update_page_complete (self); } +static void +on_got_default_sources (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GisKeyboardPage *self = data; + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + g_autoptr (GError) error = NULL; + gboolean success = FALSE; + g_auto (GStrv) ids = NULL; + g_auto (GStrv) types = NULL; + g_auto (GStrv) options = NULL; + + success = gnome_get_default_input_sources_finish (res, &ids, &types, &options, &error); + + if (!success) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to fetch default input sources: %s", error->message); + return; + } + + priv->default_input_source_ids = g_steal_pointer (&ids); + priv->default_input_source_types = g_steal_pointer (&types); + priv->default_options = g_steal_pointer (&options); + + if (priv->should_skip) { + add_default_input_sources (self); + return; + } + + preselect_input_source (self); + update_page_complete (self); +} + static void gis_keyboard_page_constructed (GObject *object) { @@ -492,6 +493,8 @@ gis_keyboard_page_constructed (GObject *object) (GAsyncReadyCallback) localed_proxy_ready, self); + gnome_get_default_input_sources (priv->cancellable, on_got_default_sources, self); + /* If we're in new user mode then we're manipulating system settings */ if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) priv->permission = polkit_permission_new_sync ("org.freedesktop.locale1.set-keyboard", NULL, NULL, NULL); -- 2.43.0 From 7b83c89d8b2fe2f9de5f7f364a7af5f2f4fcc605 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sun, 13 Aug 2023 09:09:56 -0400 Subject: [PATCH 05/16] driver: Specify mode via flags instead of boolean At the moment we just have system mode and new user mode, but we're actually going to want other modes (such as live user mode) as well. Currently the code distinguishes between its two available modes using a boolean `is_new_user`. That isn't extensible beyond two modes, so this commit changes it use bit flags instead. --- gnome-initial-setup/gis-driver.c | 17 ++- gnome-initial-setup/gis-driver.h | 8 +- gnome-initial-setup/gnome-initial-setup.c | 126 +++++++++++++--------- 3 files changed, 92 insertions(+), 59 deletions(-) diff --git a/gnome-initial-setup/gis-driver.c b/gnome-initial-setup/gis-driver.c index 0b3f542f..4325b631 100644 --- a/gnome-initial-setup/gis-driver.c +++ b/gnome-initial-setup/gis-driver.c @@ -30,8 +30,6 @@ #include "cc-common-language.h" #include "gis-assistant.h" -#define GIS_TYPE_DRIVER_MODE (gis_driver_mode_get_type ()) - /* Statically include this for now. Maybe later * we'll generate this from glib-mkenums. */ GType @@ -39,12 +37,13 @@ gis_driver_mode_get_type (void) { static GType enum_type_id = 0; if (G_UNLIKELY (!enum_type_id)) { - static const GEnumValue values[] = { + static const GFlagsValue values[] = { { GIS_DRIVER_MODE_NEW_USER, "GIS_DRIVER_MODE_NEW_USER", "new_user" }, { GIS_DRIVER_MODE_EXISTING_USER, "GIS_DRIVER_MODE_EXISTING_USER", "existing_user" }, + { GIS_DRIVER_MODE_ALL, "GIS_DRIVER_MODE_ALL", "all" }, { 0, NULL, NULL } }; - enum_type_id = g_enum_register_static("GisDriverMode", values); + enum_type_id = g_flags_register_static("GisDriverMode", values); } return enum_type_id; } @@ -645,7 +644,7 @@ gis_driver_set_property (GObject *object, switch ((GisDriverProperty) prop_id) { case PROP_MODE: - driver->mode = g_value_get_enum (value); + driver->mode = g_value_get_flags (value); break; case PROP_USERNAME: g_free (driver->username); @@ -853,10 +852,10 @@ gis_driver_class_init (GisDriverClass *klass) G_TYPE_NONE, 0); obj_props[PROP_MODE] = - g_param_spec_enum ("mode", "", "", - GIS_TYPE_DRIVER_MODE, - GIS_DRIVER_MODE_EXISTING_USER, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_param_spec_flags ("mode", "", "", + GIS_TYPE_DRIVER_MODE, + GIS_DRIVER_MODE_EXISTING_USER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); obj_props[PROP_USERNAME] = g_param_spec_string ("username", "", "", diff --git a/gnome-initial-setup/gis-driver.h b/gnome-initial-setup/gis-driver.h index 9b935e24..b57db2e2 100644 --- a/gnome-initial-setup/gis-driver.h +++ b/gnome-initial-setup/gis-driver.h @@ -31,6 +31,7 @@ G_BEGIN_DECLS #define GIS_TYPE_DRIVER (gis_driver_get_type ()) +#define GIS_TYPE_DRIVER_MODE (gis_driver_mode_get_type ()) G_DECLARE_FINAL_TYPE (GisDriver, gis_driver, GIS, DRIVER, AdwApplication) @@ -41,10 +42,13 @@ typedef enum { } UmAccountMode; typedef enum { - GIS_DRIVER_MODE_NEW_USER, - GIS_DRIVER_MODE_EXISTING_USER, + GIS_DRIVER_MODE_NEW_USER = 1 << 0, + GIS_DRIVER_MODE_EXISTING_USER = 1 << 1, + GIS_DRIVER_MODE_ALL = (GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), } GisDriverMode; +GType gis_driver_mode_get_type (void); + GisAssistant *gis_driver_get_assistant (GisDriver *driver); void gis_driver_set_user_permissions (GisDriver *driver, diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c index adb04075..a079c705 100644 --- a/gnome-initial-setup/gnome-initial-setup.c +++ b/gnome-initial-setup/gnome-initial-setup.c @@ -55,26 +55,26 @@ typedef GisPage *(*PreparePage) (GisDriver *driver); typedef struct { const gchar *page_id; PreparePage prepare_page_func; - gboolean new_user_only; + GisDriverMode modes; } PageData; -#define PAGE(name, new_user_only) { #name, gis_prepare_ ## name ## _page, new_user_only } +#define PAGE(name, modes) { #name, gis_prepare_ ## name ## _page, modes } static PageData page_table[] = { - PAGE (welcome, FALSE), - PAGE (language, FALSE), - PAGE (keyboard, FALSE), - PAGE (network, FALSE), - PAGE (privacy, FALSE), - PAGE (timezone, TRUE), - PAGE (software, TRUE), - PAGE (account, TRUE), - PAGE (password, TRUE), + PAGE (welcome, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + PAGE (language, GIS_DRIVER_MODE_ALL), + PAGE (keyboard, GIS_DRIVER_MODE_ALL), + PAGE (network, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + PAGE (privacy, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + PAGE (timezone, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + PAGE (software, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + PAGE (account, GIS_DRIVER_MODE_NEW_USER), + PAGE (password, GIS_DRIVER_MODE_NEW_USER), #ifdef HAVE_PARENTAL_CONTROLS - PAGE (parental_controls, TRUE), - PAGE (parent_password, TRUE), + PAGE (parental_controls, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + PAGE (parent_password, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), #endif - PAGE (summary, FALSE), + PAGE (summary, GIS_DRIVER_MODE_NEW_USER), { NULL }, }; @@ -105,26 +105,15 @@ should_skip_page (const gchar *page_id, } static gchar ** -strv_append (gchar **a, - gchar **b) +pages_to_skip_from_file (GisDriver *driver) { - guint n = g_strv_length (a); - guint m = g_strv_length (b); - - a = g_renew (gchar *, a, n + m + 1); - for (guint i = 0; i < m; i++) - a[n + i] = g_strdup (b[i]); - a[n + m] = NULL; - - return a; -} - -static gchar ** -pages_to_skip_from_file (GisDriver *driver, - gboolean is_new_user) -{ - GStrv skip_pages = NULL; - GStrv additional_skip_pages = NULL; + GisDriverMode driver_mode; + GisDriverMode other_modes; + g_autoptr(GStrvBuilder) builder = g_strv_builder_new(); + g_auto (GStrv) skip_pages = NULL; + g_autofree char *mode_group = NULL; + g_autoptr (GFlagsClass) driver_mode_flags_class = NULL; + const GFlagsValue *driver_mode_flags = NULL; /* This code will read the keyfile containing vendor customization options and * look for options under the "pages" group, and supports the following keys: @@ -132,28 +121,68 @@ pages_to_skip_from_file (GisDriver *driver, * - new_user_only (optional): list of pages to be skipped in existing user mode * - existing_user_only (optional): list of pages to be skipped in new user mode * + * In addition it will look for options under the "{mode} pages" group where {mode} is the + * current driver mode for the following keys: + * - skip (optional): list of pages to be skipped for the current mode + * * This is how this file might look on a vendor image: * * [pages] * skip=timezone + * + * [new_user pages] + * skip=language;keyboard + * + * Older files might look like so: + * + * [pages] + * skip=timezone * existing_user_only=language;keyboard */ skip_pages = gis_driver_conf_get_string_list (driver, VENDOR_PAGES_GROUP, VENDOR_SKIP_KEY, NULL); - additional_skip_pages = - gis_driver_conf_get_string_list (driver, VENDOR_PAGES_GROUP, - is_new_user ? VENDOR_EXISTING_USER_ONLY_KEY : VENDOR_NEW_USER_ONLY_KEY, - NULL); - - if (!skip_pages && additional_skip_pages) { - skip_pages = additional_skip_pages; - } else if (skip_pages && additional_skip_pages) { - skip_pages = strv_append (skip_pages, additional_skip_pages); - g_strfreev (additional_skip_pages); + if (skip_pages != NULL) + { + g_strv_builder_addv (builder, (const char **) skip_pages); + g_clear_pointer (&skip_pages, g_strfreev); + } + + driver_mode_flags_class = g_type_class_ref (GIS_TYPE_DRIVER_MODE); + + driver_mode = gis_driver_get_mode (driver); + driver_mode_flags = g_flags_get_first_value (driver_mode_flags_class, driver_mode); + + mode_group = g_strdup_printf ("%s pages", driver_mode_flags->value_nick); + skip_pages = gis_driver_conf_get_string_list (driver, mode_group, + VENDOR_SKIP_KEY, NULL); + if (skip_pages != NULL) + { + g_strv_builder_addv (builder, (const char **) skip_pages); + g_clear_pointer (&skip_pages, g_strfreev); + } + + other_modes = GIS_DRIVER_MODE_ALL & ~driver_mode; + while (other_modes) { + const GFlagsValue *other_mode_flags = g_flags_get_first_value (driver_mode_flags_class, other_modes); + + if (other_mode_flags != NULL) { + g_autofree char *vendor_key = g_strdup_printf ("%s_only", other_mode_flags->value_nick); + + skip_pages = gis_driver_conf_get_string_list (driver, VENDOR_PAGES_GROUP, + vendor_key, NULL); + + if (skip_pages != NULL) + { + g_strv_builder_addv (builder, (const char **) skip_pages); + g_clear_pointer (&skip_pages, g_strfreev); + } + + other_modes &= ~other_mode_flags->value; + } } - return skip_pages; + return g_strv_builder_end (builder); } static void @@ -196,7 +225,8 @@ rebuild_pages_cb (GisDriver *driver) GisAssistant *assistant; GisPage *current_page; gchar **skip_pages; - gboolean is_new_user, skipped; + GisDriverMode driver_mode; + gboolean skipped; assistant = gis_driver_get_assistant (driver); current_page = gis_assistant_get_current_page (assistant); @@ -215,13 +245,13 @@ rebuild_pages_cb (GisDriver *driver) ++page_data; } - is_new_user = (gis_driver_get_mode (driver) == GIS_DRIVER_MODE_NEW_USER); - skip_pages = pages_to_skip_from_file (driver, is_new_user); + driver_mode = gis_driver_get_mode (driver); + skip_pages = pages_to_skip_from_file (driver); for (; page_data->page_id != NULL; ++page_data) { skipped = FALSE; - if ((page_data->new_user_only && !is_new_user) || + if (((page_data->modes & driver_mode) == 0) || (should_skip_page (page_data->page_id, skip_pages))) skipped = TRUE; -- 2.43.0 From 68391446ee06083a06899cdadb09ca90b4b40af3 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 30 Aug 2023 15:08:23 -0400 Subject: [PATCH 06/16] assistant: Show Back button on summary page commit f60b4622350468f7ef17f79d9bc6679bf8cce7b9 changed the assistant to no longer show the back and forward buttons on the last page. Hiding the forward button makes sense: there's no more pages go to. Hiding the back button doesn't really make sense: there are a bunch of pages the user could potentially want to revisit. This commit shows the back button on the last page (either the summary page or the install page). --- gnome-initial-setup/gis-assistant.c | 14 +++++++++----- .../pages/summary/gis-summary-page.c | 16 +++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/gnome-initial-setup/gis-assistant.c b/gnome-initial-setup/gis-assistant.c index a3122b71..8a7fc52b 100644 --- a/gnome-initial-setup/gis-assistant.c +++ b/gnome-initial-setup/gis-assistant.c @@ -59,6 +59,8 @@ struct _GisAssistant GList *pages; GisPage *current_page; + + gboolean data_saved; }; G_DEFINE_TYPE (GisAssistant, gis_assistant, GTK_TYPE_BOX) @@ -182,6 +184,7 @@ update_navigation_buttons (GisAssistant *assistant) { GisPage *page = assistant->current_page; GList *l; + gboolean is_first_page; gboolean is_last_page; if (page == NULL) @@ -189,11 +192,13 @@ update_navigation_buttons (GisAssistant *assistant) l = g_list_find (assistant->pages, page); + is_first_page = (l->prev == NULL); is_last_page = (l->next == NULL); + gtk_widget_set_visible (assistant->back, !is_first_page && !assistant->data_saved); + if (is_last_page) { - gtk_widget_set_visible (assistant->back, FALSE); gtk_widget_set_visible (assistant->forward, FALSE); gtk_widget_set_visible (assistant->skip, FALSE); gtk_widget_set_visible (assistant->cancel, FALSE); @@ -201,12 +206,8 @@ update_navigation_buttons (GisAssistant *assistant) } else { - gboolean is_first_page; GtkWidget *next_widget; - is_first_page = (l->prev == NULL); - gtk_widget_set_visible (assistant->back, !is_first_page); - if (gis_page_get_needs_accept (page)) next_widget = assistant->accept; else @@ -418,6 +419,9 @@ gis_assistant_save_data (GisAssistant *assistant, { GList *l; + assistant->data_saved = TRUE; + gtk_widget_set_visible (assistant->back, FALSE); + for (l = assistant->pages; l != NULL; l = l->next) { if (!gis_page_save_data (l->data, error)) diff --git a/gnome-initial-setup/pages/summary/gis-summary-page.c b/gnome-initial-setup/pages/summary/gis-summary-page.c index 0aee2dad..8a526b64 100644 --- a/gnome-initial-setup/pages/summary/gis-summary-page.c +++ b/gnome-initial-setup/pages/summary/gis-summary-page.c @@ -180,6 +180,15 @@ log_user_in (GisSummaryPage *page) static void done_cb (GtkButton *button, GisSummaryPage *page) { + g_autoptr (GError) error = NULL; + + if (!gis_driver_save_data (GIS_PAGE (page)->driver, &error)) + { + /* FIXME: This should probably be shown to the user and some options + * provided to them. */ + g_warning ("Error saving data: %s", error->message); + } + gis_ensure_stamp_files (GIS_PAGE (page)->driver); switch (gis_driver_get_mode (GIS_PAGE (page)->driver)) @@ -202,13 +211,6 @@ gis_summary_page_shown (GisPage *page) GisSummaryPagePrivate *priv = gis_summary_page_get_instance_private (summary); g_autoptr(GError) local_error = NULL; - if (!gis_driver_save_data (GIS_PAGE (page)->driver, &local_error)) - { - /* FIXME: This should probably be shown to the user and some options - * provided to them. */ - g_warning ("Error saving data: %s", local_error->message); - } - gis_driver_get_user_permissions (GIS_PAGE (page)->driver, &priv->user_account, &priv->user_password); -- 2.43.0 From f1e4b58ced27b028e13dd9bb29794419dc08fb72 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sun, 13 Aug 2023 09:39:07 -0400 Subject: [PATCH 07/16] gnome-initial-setup: Add live user mode This commit adds a new "live user" mode meant to be run in live image environments. It asks questions the user should answer before the installer is started, and provides a way for the user to initiate the installer or just jump into the live session instead. --- data/20-gnome-initial-setup.rules.in | 3 +- gnome-initial-setup/gis-driver.c | 4 +- gnome-initial-setup/gis-driver.h | 4 +- gnome-initial-setup/gis-util.c | 122 ++++++ gnome-initial-setup/gis-util.h | 4 + gnome-initial-setup/gnome-initial-setup.c | 24 +- .../pages/account/gis-account-pages.c | 21 + .../pages/install/gis-install-page.c | 382 ++++++++++++++++++ .../pages/install/gis-install-page.css | 11 + .../pages/install/gis-install-page.h | 52 +++ .../pages/install/gis-install-page.ui | 51 +++ .../pages/install/install.gresource.xml | 8 + gnome-initial-setup/pages/install/meson.build | 9 + .../pages/keyboard/gis-keyboard-page.c | 10 +- .../pages/language/gis-language-page.c | 5 +- gnome-initial-setup/pages/meson.build | 1 + .../pages/password/gis-password-page.c | 6 + 17 files changed, 700 insertions(+), 17 deletions(-) create mode 100644 gnome-initial-setup/pages/install/gis-install-page.c create mode 100644 gnome-initial-setup/pages/install/gis-install-page.css create mode 100644 gnome-initial-setup/pages/install/gis-install-page.h create mode 100644 gnome-initial-setup/pages/install/gis-install-page.ui create mode 100644 gnome-initial-setup/pages/install/install.gresource.xml create mode 100644 gnome-initial-setup/pages/install/meson.build diff --git a/data/20-gnome-initial-setup.rules.in b/data/20-gnome-initial-setup.rules.in index 02fd21d0..881efde9 100644 --- a/data/20-gnome-initial-setup.rules.in +++ b/data/20-gnome-initial-setup.rules.in @@ -16,7 +16,8 @@ polkit.addRule(function(action, subject) { action.id.indexOf('org.freedesktop.timedate1.') === 0 || action.id.indexOf('org.freedesktop.realmd.') === 0 || action.id.indexOf('com.endlessm.ParentalControls.') === 0 || - action.id.indexOf('org.fedoraproject.thirdparty.') === 0); + action.id.indexOf('org.fedoraproject.thirdparty.') === 0 || + action.id.indexOf('org.freedesktop.login1.reboot') === 0); if (actionMatches) { if (subject.local) diff --git a/gnome-initial-setup/gis-driver.c b/gnome-initial-setup/gis-driver.c index 4325b631..6a4727cc 100644 --- a/gnome-initial-setup/gis-driver.c +++ b/gnome-initial-setup/gis-driver.c @@ -40,6 +40,8 @@ gis_driver_mode_get_type (void) { static const GFlagsValue values[] = { { GIS_DRIVER_MODE_NEW_USER, "GIS_DRIVER_MODE_NEW_USER", "new_user" }, { GIS_DRIVER_MODE_EXISTING_USER, "GIS_DRIVER_MODE_EXISTING_USER", "existing_user" }, + { GIS_DRIVER_MODE_LIVE_USER, "GIS_DRIVER_MODE_LIVE_USER", "live_user" }, + { GIS_DRIVER_MODE_SYSTEM, "GIS_DRIVER_MODE_SYSTEM", "system" }, { GIS_DRIVER_MODE_ALL, "GIS_DRIVER_MODE_ALL", "all" }, { 0, NULL, NULL } }; @@ -786,7 +788,7 @@ gis_driver_startup (GApplication *app) G_APPLICATION_CLASS (gis_driver_parent_class)->startup (app); - if (driver->mode == GIS_DRIVER_MODE_NEW_USER) + if (driver->mode & GIS_DRIVER_MODE_SYSTEM) connect_to_gdm (driver); driver->main_window = g_object_new (GTK_TYPE_APPLICATION_WINDOW, diff --git a/gnome-initial-setup/gis-driver.h b/gnome-initial-setup/gis-driver.h index b57db2e2..aedb9a73 100644 --- a/gnome-initial-setup/gis-driver.h +++ b/gnome-initial-setup/gis-driver.h @@ -44,7 +44,9 @@ typedef enum { typedef enum { GIS_DRIVER_MODE_NEW_USER = 1 << 0, GIS_DRIVER_MODE_EXISTING_USER = 1 << 1, - GIS_DRIVER_MODE_ALL = (GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + GIS_DRIVER_MODE_LIVE_USER = 1 << 2, + GIS_DRIVER_MODE_SYSTEM = (GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_LIVE_USER), + GIS_DRIVER_MODE_ALL = (GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER | GIS_DRIVER_MODE_LIVE_USER), } GisDriverMode; GType gis_driver_mode_get_type (void); diff --git a/gnome-initial-setup/gis-util.c b/gnome-initial-setup/gis-util.c index ac153fc1..424c26d7 100644 --- a/gnome-initial-setup/gis-util.c +++ b/gnome-initial-setup/gis-util.c @@ -31,3 +31,125 @@ gis_add_style_from_resource (const char *resource_path) GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } + +gboolean +gis_kernel_command_line_has_argument (const char *arguments[]) +{ + GError *error = NULL; + g_autofree char *contents = NULL; + g_autoptr (GString) pattern = NULL; + gboolean has_argument = FALSE; + size_t i; + + if (!g_file_get_contents ("/proc/cmdline", &contents, NULL, &error)) { + g_error_free (error); + return FALSE; + } + + /* Build up the pattern by iterating through the alternatives, + * escaping all dots so they don't match any character but period, + * and adding word boundary specifiers around the arguments so + * substrings don't get matched. + * + * Also, add a | between each alternative. + */ + pattern = g_string_new (NULL); + for (i = 0; arguments[i] != NULL; i++) { + g_autofree char *escaped_argument = g_regex_escape_string (arguments[i], -1); + + if (i > 0) { + g_string_append (pattern, "|"); + } + + g_string_append (pattern, "\\b"); + + g_string_append (pattern, escaped_argument); + + g_string_append (pattern, "\\b"); + } + + has_argument = g_regex_match_simple (pattern->str, contents, 0, 0); + + return has_argument; +} + +static gboolean +is_valid_shell_identifier_character (char c, + gboolean first) +{ + return (!first && g_ascii_isdigit (c)) || + c == '_' || + g_ascii_isalpha (c); +} + +void +gis_substitute_variables_in_text (char **text, + GisVariableLookupFunc lookup_func, + gpointer user_data) +{ + GString *s = g_string_new (""); + const char *p, *start; + char c; + + p = *text; + while (*p) { + c = *p; + if (c == '\\') { + p++; + c = *p; + if (c != '\0') { + p++; + switch (c) { + case '\\': + g_string_append_c (s, '\\'); + break; + case '$': + g_string_append_c (s, '$'); + break; + default: + g_string_append_c (s, '\\'); + g_string_append_c (s, c); + break; + } + } + } else if (c == '$') { + gboolean brackets = FALSE; + p++; + if (*p == '{') { + brackets = TRUE; + p++; + } + start = p; + while (*p != '\0' && + is_valid_shell_identifier_character (*p, p == start)) { + p++; + } + if (p == start || (brackets && *p != '}')) { + g_string_append_c (s, '$'); + if (brackets) + g_string_append_c (s, '{'); + g_string_append_len (s, start, p - start); + } else { + g_autofree char *variable = NULL; + g_autofree char *value = NULL; + + variable = g_strndup (start, p - start); + + if (brackets && *p == '}') + p++; + + if (lookup_func) + value = lookup_func (variable, user_data); + if (value) { + g_string_append (s, value); + } + } + } else { + p++; + g_string_append_c (s, c); + } + } + g_free (*text); + *text = g_string_free (s, FALSE); +} + diff --git a/gnome-initial-setup/gis-util.h b/gnome-initial-setup/gis-util.h index 5041bddd..80d4f9a0 100644 --- a/gnome-initial-setup/gis-util.h +++ b/gnome-initial-setup/gis-util.h @@ -17,3 +17,7 @@ #pragma once void gis_add_style_from_resource (const char *path); +gboolean gis_kernel_command_line_has_argument (const char *arguments[]); + +typedef char * (* GisVariableLookupFunc) (const char *key, gpointer user_data); +void gis_substitute_variables_in_text (char **text, GisVariableLookupFunc lookup_func, gpointer user_data); diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c index a079c705..02ca2808 100644 --- a/gnome-initial-setup/gnome-initial-setup.c +++ b/gnome-initial-setup/gnome-initial-setup.c @@ -40,13 +40,16 @@ #include "pages/parental-controls/gis-parental-controls-page.h" #include "pages/password/gis-password-page.h" #include "pages/summary/gis-summary-page.h" +#include "pages/install/gis-install-page.h" #define VENDOR_PAGES_GROUP "pages" #define VENDOR_SKIP_KEY "skip" #define VENDOR_NEW_USER_ONLY_KEY "new_user_only" #define VENDOR_EXISTING_USER_ONLY_KEY "existing_user_only" +#define VENDOR_LIVE_USER_ONLY_KEY "live_user_only" static gboolean force_existing_user_mode; +static gboolean force_live_user_mode; static GPtrArray *skipped_pages; @@ -64,17 +67,19 @@ static PageData page_table[] = { PAGE (welcome, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), PAGE (language, GIS_DRIVER_MODE_ALL), PAGE (keyboard, GIS_DRIVER_MODE_ALL), - PAGE (network, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + PAGE (network, GIS_DRIVER_MODE_ALL), PAGE (privacy, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), - PAGE (timezone, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), + PAGE (timezone, GIS_DRIVER_MODE_ALL), PAGE (software, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), - PAGE (account, GIS_DRIVER_MODE_NEW_USER), + /* In live user mode, the account page isn't displayed, it just quietly creates the live user */ + PAGE (account, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_LIVE_USER), PAGE (password, GIS_DRIVER_MODE_NEW_USER), #ifdef HAVE_PARENTAL_CONTROLS PAGE (parental_controls, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), PAGE (parent_password, GIS_DRIVER_MODE_NEW_USER | GIS_DRIVER_MODE_EXISTING_USER), #endif PAGE (summary, GIS_DRIVER_MODE_NEW_USER), + PAGE (install, GIS_DRIVER_MODE_LIVE_USER), { NULL }, }; @@ -118,8 +123,8 @@ pages_to_skip_from_file (GisDriver *driver) /* This code will read the keyfile containing vendor customization options and * look for options under the "pages" group, and supports the following keys: * - skip (optional): list of pages to be skipped always - * - new_user_only (optional): list of pages to be skipped in existing user mode - * - existing_user_only (optional): list of pages to be skipped in new user mode + * - new_user_only (optional): list of pages to be skipped for modes other than new_user + * - existing_user_only (optional): list of pages to be skipped for modes other than existing_user * * In addition it will look for options under the "{mode} pages" group where {mode} is the * current driver mode for the following keys: @@ -275,6 +280,8 @@ get_mode (void) { if (force_existing_user_mode) return GIS_DRIVER_MODE_EXISTING_USER; + else if (force_live_user_mode) + return GIS_DRIVER_MODE_LIVE_USER; else return GIS_DRIVER_MODE_NEW_USER; } @@ -308,6 +315,8 @@ main (int argc, char *argv[]) GOptionEntry entries[] = { { "existing-user", 0, 0, G_OPTION_ARG_NONE, &force_existing_user_mode, _("Force existing user mode"), NULL }, + { "live-user", 0, 0, G_OPTION_ARG_NONE, &force_live_user_mode, + _("Force live user mode"), NULL }, { NULL } }; @@ -326,6 +335,9 @@ main (int argc, char *argv[]) g_option_context_parse (context, &argc, &argv, NULL); + if (gis_kernel_command_line_has_argument ((const char *[]) { "rd.live.image", "endless.live_boot", NULL })) + force_live_user_mode = TRUE; + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); @@ -344,7 +356,7 @@ main (int argc, char *argv[]) * the keyring manually so that we can pass the credentials * along to the new user in the handoff. */ - if (mode == GIS_DRIVER_MODE_NEW_USER && !gis_get_mock_mode ()) + if ((mode & GIS_DRIVER_MODE_SYSTEM) && !gis_get_mock_mode ()) gis_ensure_login_keyring (); driver = gis_driver_new (mode); diff --git a/gnome-initial-setup/pages/account/gis-account-pages.c b/gnome-initial-setup/pages/account/gis-account-pages.c index d9cc8d9f..8b0d8e99 100644 --- a/gnome-initial-setup/pages/account/gis-account-pages.c +++ b/gnome-initial-setup/pages/account/gis-account-pages.c @@ -26,6 +26,27 @@ GisPage * gis_prepare_account_page (GisDriver *driver) { + GisDriverMode driver_mode; + + driver_mode = gis_driver_get_mode (driver); + + if (driver_mode == GIS_DRIVER_MODE_LIVE_USER && !gis_kernel_command_line_has_argument ((const char *[]) { "rd.live.overlay", NULL })) { + ActUserManager *act_client = act_user_manager_get_default (); + const char *username = "liveuser"; + g_autoptr(ActUser) user = NULL; + g_autoptr(GError) error = NULL; + + user = act_user_manager_create_user (act_client, username, username, ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR, &error); + + if (user != NULL) { + act_user_set_password_mode (user, ACT_USER_PASSWORD_MODE_NONE); + gis_driver_set_username (driver, username); + gis_driver_set_account_mode (driver, UM_LOCAL); + gis_driver_set_user_permissions (driver, user, NULL); + } + return NULL; + } + return g_object_new (GIS_TYPE_ACCOUNT_PAGE, "driver", driver, NULL); diff --git a/gnome-initial-setup/pages/install/gis-install-page.c b/gnome-initial-setup/pages/install/gis-install-page.c new file mode 100644 index 00000000..36ed7539 --- /dev/null +++ b/gnome-initial-setup/pages/install/gis-install-page.c @@ -0,0 +1,382 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2023 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Install page {{{1 */ + +#define PAGE_ID "install" + +#include "config.h" +#include "cc-common-language.h" +#include "gis-install-page.h" +#include "gis-pkexec.h" + +#include +#include +#include +#include +#include +#include + +#include + +#define SERVICE_NAME "gdm-password" +#define VENDOR_INSTALLER_GROUP "install" +#define VENDOR_APPLICATION_KEY "application" + +struct _GisInstallPagePrivate { + GtkWidget *try_button; + GtkWidget *install_button; + AdwStatusPage *status_page; + GDesktopAppInfo *installer; + + ActUser *user_account; + const gchar *user_password; +}; +typedef struct _GisInstallPagePrivate GisInstallPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisInstallPage, gis_install_page, GIS_TYPE_PAGE); + +static void +request_info_query (GisInstallPage *page, + GdmUserVerifier *user_verifier, + const char *question, + gboolean is_secret) +{ + /* TODO: pop up modal dialog */ + g_debug ("user verifier asks%s question: %s", + is_secret ? " secret" : "", + question); +} + +static void +on_info (GdmUserVerifier *user_verifier, + const char *service_name, + const char *info, + GisInstallPage *page) +{ + g_debug ("PAM module info: %s", info); +} + +static void +on_problem (GdmUserVerifier *user_verifier, + const char *service_name, + const char *problem, + GisInstallPage *page) +{ + g_warning ("PAM module error: %s", problem); +} + +static void +on_info_query (GdmUserVerifier *user_verifier, + const char *service_name, + const char *question, + GisInstallPage *page) +{ + request_info_query (page, user_verifier, question, FALSE); +} + +static void +on_secret_info_query (GdmUserVerifier *user_verifier, + const char *service_name, + const char *question, + GisInstallPage *page) +{ + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (page); + gboolean should_send_password = priv->user_password != NULL; + + g_debug ("PAM module secret info query: %s", question); + if (should_send_password) { + g_debug ("sending password\n"); + gdm_user_verifier_call_answer_query (user_verifier, + service_name, + priv->user_password, + NULL, NULL, NULL); + priv->user_password = NULL; + } else { + request_info_query (page, user_verifier, question, TRUE); + } +} + +static void +on_session_opened (GdmGreeter *greeter, + const char *service_name, + GisInstallPage *page) +{ + gdm_greeter_call_start_session_when_ready_sync (greeter, service_name, + TRUE, NULL, NULL); +} + +static void +log_user_in (GisInstallPage *page) +{ + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (page); + g_autoptr(GError) error = NULL; + GdmGreeter *greeter = NULL; + GdmUserVerifier *user_verifier = NULL; + + if (!gis_driver_get_gdm_objects (GIS_PAGE (page)->driver, + &greeter, &user_verifier)) { + g_warning ("No GDM connection; not initiating login"); + return; + } + + g_signal_connect (user_verifier, "info", + G_CALLBACK (on_info), page); + g_signal_connect (user_verifier, "problem", + G_CALLBACK (on_problem), page); + g_signal_connect (user_verifier, "info-query", + G_CALLBACK (on_info_query), page); + g_signal_connect (user_verifier, "secret-info-query", + G_CALLBACK (on_secret_info_query), page); + + g_signal_connect (greeter, "session-opened", + G_CALLBACK (on_session_opened), page); + + gdm_user_verifier_call_begin_verification_for_user_sync (user_verifier, + SERVICE_NAME, + act_user_get_user_name (priv->user_account), + NULL, &error); + + if (error != NULL) + g_warning ("Could not begin verification: %s", error->message); +} + +static void +on_try_button_clicked (GtkButton *button, + GisInstallPage *page) +{ + + g_autoptr (GError) error = NULL; + + if (!gis_driver_save_data (GIS_PAGE (page)->driver, &error)) + g_warning ("Error saving data: %s", error->message); + + gis_ensure_stamp_files (GIS_PAGE (page)->driver); + + gis_driver_hide_window (GIS_PAGE (page)->driver); + log_user_in (page); +} + +static void +on_installer_exited (GPid pid, + int exit_status, + gpointer user_data) +{ + g_autoptr (GError) error = NULL; + g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autoptr(GSubprocess) subprocess = NULL; + gboolean started_to_reboot; + + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP); + + g_subprocess_launcher_unsetenv (launcher, "SHELL"); + + subprocess = g_subprocess_launcher_spawn (launcher, &error, "systemctl", "reboot", NULL); + + if (subprocess == NULL) { + g_warning ("Failed to initiate reboot: %s\n", error->message); + return; + } + + started_to_reboot = g_subprocess_wait (subprocess, NULL, &error); + + if (!started_to_reboot) { + g_warning ("Failed to reboot: %s\n", error->message); + return; + } +} + +static void +on_installer_started (GDesktopAppInfo *appinfo, + GPid pid, + gpointer user_data) +{ + g_child_watch_add (pid, on_installer_exited, user_data); +} + +static void +run_installer (GisInstallPage *page) +{ + g_autoptr (GError) error = NULL; + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (page); + gboolean installer_launched; + g_autoptr (GAppLaunchContext) launch_context = NULL; + g_autofree char *language = NULL; + + if (!gis_driver_save_data (GIS_PAGE (page)->driver, &error)) + g_warning ("Error saving data: %s", error->message); + + gis_ensure_stamp_files (GIS_PAGE (page)->driver); + + launch_context = g_app_launch_context_new (); + + g_app_launch_context_unsetenv (launch_context, "SHELL"); + + language = cc_common_language_get_current_language (); + + if (language != NULL) + g_app_launch_context_setenv (launch_context, "LANG", language); + + installer_launched = g_desktop_app_info_launch_uris_as_manager (priv->installer, + NULL, + launch_context, + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_CHILD_INHERITS_STDERR | G_SPAWN_CHILD_INHERITS_STDOUT | G_SPAWN_SEARCH_PATH, + NULL, + NULL, + on_installer_started, + page, + &error); + + if (!installer_launched) + g_warning ("Could not launch installer: %s", error->message); +} + +static void +on_install_button_clicked (GtkButton *button, + GisInstallPage *page) +{ + gis_driver_hide_window (GIS_PAGE (page)->driver); + run_installer (page); +} + +static void +gis_install_page_shown (GisPage *page) +{ + GisInstallPage *install = GIS_INSTALL_PAGE (page); + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (install); + g_autoptr(GError) local_error = NULL; + + gis_driver_get_user_permissions (GIS_PAGE (page)->driver, + &priv->user_account, + &priv->user_password); + + gtk_widget_grab_focus (priv->install_button); +} + +static void +update_distro_name (GisInstallPage *page) +{ + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (page); + g_autofree char *text = NULL; + + text = g_strdup (adw_status_page_get_description (priv->status_page)); + gis_substitute_variables_in_text (&text, (GisVariableLookupFunc) g_get_os_info, NULL); + adw_status_page_set_description (priv->status_page, text); + g_clear_pointer (&text, g_free); + + text = g_strdup (gtk_button_get_label (GTK_BUTTON (priv->try_button))); + gis_substitute_variables_in_text (&text, (GisVariableLookupFunc) g_get_os_info, NULL); + gtk_button_set_label (GTK_BUTTON (priv->try_button), text); + g_clear_pointer (&text, g_free); +} + + +static void +apply_stylesheet (GisInstallPage *page) +{ + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (page); + g_autoptr (GtkCssProvider) css_provider = gtk_css_provider_new(); + + gtk_widget_add_css_class (GTK_WIDGET (priv->status_page), "override-icon-size"); + gtk_widget_add_css_class (GTK_WIDGET (priv->status_page), "override-button-line-height"); + + gtk_css_provider_load_from_resource (css_provider, "/org/gnome/initial-setup/gis-install-page.css"); + + gtk_style_context_add_provider_for_display (gtk_widget_get_display (GTK_WIDGET (priv->status_page)), + GTK_STYLE_PROVIDER (css_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} + +static gboolean +find_installer (GisInstallPage *page) +{ + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (page); + g_autofree char *desktop_file = NULL; + + desktop_file = gis_driver_conf_get_string (GIS_PAGE (page)->driver, + VENDOR_INSTALLER_GROUP, + VENDOR_APPLICATION_KEY); + + if (!desktop_file) + return FALSE; + + priv->installer = g_desktop_app_info_new (desktop_file); + + return priv->installer != NULL; +} + +static void +gis_install_page_constructed (GObject *object) +{ + GisInstallPage *page = GIS_INSTALL_PAGE (object); + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (page); + + G_OBJECT_CLASS (gis_install_page_parent_class)->constructed (object); + + if (!find_installer (page)) + gtk_widget_set_sensitive (priv->install_button, FALSE); + + apply_stylesheet (page); + update_distro_name (page); + g_signal_connect (priv->try_button, "clicked", G_CALLBACK (on_try_button_clicked), page); + g_signal_connect (priv->install_button, "clicked", G_CALLBACK (on_install_button_clicked), page); + + gis_page_set_complete (GIS_PAGE (page), TRUE); + + gtk_widget_set_visible (GTK_WIDGET (page), TRUE); +} + +static void +gis_install_page_locale_changed (GisPage *page) +{ + g_autofree char *title = g_strdup (_("Install ${PRETTY_NAME}")); + gis_substitute_variables_in_text (&title, (GisVariableLookupFunc) g_get_os_info, NULL); + gis_page_set_title (page, title); +} + +static void +gis_install_page_class_init (GisInstallPageClass *klass) +{ + GisPageClass *page_class = GIS_PAGE_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-install-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisInstallPage, try_button); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisInstallPage, install_button); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisInstallPage, status_page); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_install_page_locale_changed; + page_class->shown = gis_install_page_shown; + object_class->constructed = gis_install_page_constructed; +} + +static void +gis_install_page_init (GisInstallPage *page) +{ + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_install_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_INSTALL_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/install/gis-install-page.css b/gnome-initial-setup/pages/install/gis-install-page.css new file mode 100644 index 00000000..f3583b33 --- /dev/null +++ b/gnome-initial-setup/pages/install/gis-install-page.css @@ -0,0 +1,11 @@ +.override-icon-size image { + -gtk-icon-size: 70px; +} + +.override-button-line-height button { + line-height: 1.75; +} + +.description { + line-height: 1.25; +} diff --git a/gnome-initial-setup/pages/install/gis-install-page.h b/gnome-initial-setup/pages/install/gis-install-page.h new file mode 100644 index 00000000..292427d8 --- /dev/null +++ b/gnome-initial-setup/pages/install/gis-install-page.h @@ -0,0 +1,52 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2023 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GIS_INSTALL_PAGE_H__ +#define __GIS_INSTALL_PAGE_H__ + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_INSTALL_PAGE (gis_install_page_get_type ()) +#define GIS_INSTALL_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_INSTALL_PAGE, GisInstallPage)) +#define GIS_INSTALL_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_INSTALL_PAGE, GisInstallPageClass)) +#define GIS_IS_INSTALL_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_INSTALL_PAGE)) +#define GIS_IS_INSTALL_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_INSTALL_PAGE)) +#define GIS_INSTALL_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_INSTALL_PAGE, GisInstallPageClass)) + +typedef struct _GisInstallPage GisInstallPage; +typedef struct _GisInstallPageClass GisInstallPageClass; + +struct _GisInstallPage +{ + GisPage parent; +}; + +struct _GisInstallPageClass +{ + GisPageClass parent_class; +}; + +GType gis_install_page_get_type (void); + +GisPage *gis_prepare_install_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_INSTALL_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/install/gis-install-page.ui b/gnome-initial-setup/pages/install/gis-install-page.ui new file mode 100644 index 00000000..c9ed5c88 --- /dev/null +++ b/gnome-initial-setup/pages/install/gis-install-page.ui @@ -0,0 +1,51 @@ + + + + diff --git a/gnome-initial-setup/pages/install/install.gresource.xml b/gnome-initial-setup/pages/install/install.gresource.xml new file mode 100644 index 00000000..15391108 --- /dev/null +++ b/gnome-initial-setup/pages/install/install.gresource.xml @@ -0,0 +1,8 @@ + + + + gis-install-page.ui + gis-install-page.css + + + diff --git a/gnome-initial-setup/pages/install/meson.build b/gnome-initial-setup/pages/install/meson.build new file mode 100644 index 00000000..e5084e5e --- /dev/null +++ b/gnome-initial-setup/pages/install/meson.build @@ -0,0 +1,9 @@ +sources += gnome.compile_resources( + 'install-resources', + files('install.gresource.xml'), + c_name: 'install' +) +sources += files( + 'gis-install-page.c', + 'gis-install-page.h' +) diff --git a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c index f2bfe164..7801667c 100644 --- a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c +++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c @@ -187,7 +187,7 @@ set_input_settings (GisKeyboardPage *self, g_variant_builder_init (&input_options_builder, G_VARIANT_TYPE ("as")); - is_system_mode = gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER; + is_system_mode = gis_driver_get_mode (GIS_PAGE (self)->driver) & GIS_DRIVER_MODE_SYSTEM; layouts_array = g_ptr_array_new (); variants_array = g_ptr_array_new (); @@ -301,7 +301,7 @@ update_input (GisKeyboardPage *self) set_input_settings (self, type, id); - if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) { + if (gis_driver_get_mode (GIS_PAGE (self)->driver) & GIS_DRIVER_MODE_SYSTEM) { if (g_permission_get_allowed (priv->permission)) { set_localed_input (self); } else if (g_permission_get_can_acquire (priv->permission)) { @@ -363,7 +363,7 @@ preselect_input_source (GisKeyboardPage *self) got_input_source = gnome_get_input_source_from_locale (language, &type, &id); if (got_input_source) { - gboolean is_system_mode = gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER; + gboolean is_system_mode = gis_driver_get_mode (GIS_PAGE (self)->driver) & GIS_DRIVER_MODE_SYSTEM; if (is_system_mode || g_str_equal (type, "ibus")) { cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser), id, @@ -384,7 +384,7 @@ update_page_complete (GisKeyboardPage *self) GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); gboolean complete; - if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) { + if (gis_driver_get_mode (GIS_PAGE (self)->driver) & GIS_DRIVER_MODE_SYSTEM) { complete = (priv->localed != NULL && cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser)) != NULL); } else { @@ -496,7 +496,7 @@ gis_keyboard_page_constructed (GObject *object) gnome_get_default_input_sources (priv->cancellable, on_got_default_sources, self); /* If we're in new user mode then we're manipulating system settings */ - if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) + if (gis_driver_get_mode (GIS_PAGE (self)->driver) & GIS_DRIVER_MODE_SYSTEM) priv->permission = polkit_permission_new_sync ("org.freedesktop.locale1.set-keyboard", NULL, NULL, NULL); update_page_complete (self); diff --git a/gnome-initial-setup/pages/language/gis-language-page.c b/gnome-initial-setup/pages/language/gis-language-page.c index 26a01257..17117fa1 100644 --- a/gnome-initial-setup/pages/language/gis-language-page.c +++ b/gnome-initial-setup/pages/language/gis-language-page.c @@ -145,7 +145,7 @@ language_changed (CcLanguageChooser *chooser, gis_driver_set_user_language (driver, priv->new_locale_id, TRUE); gtk_widget_set_default_direction (gtk_get_locale_direction ()); - if (gis_driver_get_mode (driver) == GIS_DRIVER_MODE_NEW_USER) { + if (gis_driver_get_mode (driver) & GIS_DRIVER_MODE_SYSTEM) { gis_page_set_complete (GIS_PAGE (page), FALSE); @@ -257,8 +257,7 @@ gis_language_page_constructed (GObject *object) g_signal_connect (priv->language_chooser, "confirm", G_CALLBACK (language_confirmed), page); - /* If we're in new user mode then we're manipulating system settings */ - if (gis_driver_get_mode (GIS_PAGE (page)->driver) == GIS_DRIVER_MODE_NEW_USER) + if (gis_driver_get_mode (GIS_PAGE (page)->driver) & GIS_DRIVER_MODE_SYSTEM) { priv->permission = polkit_permission_new_sync ("org.freedesktop.locale1.set-locale", NULL, NULL, NULL); diff --git a/gnome-initial-setup/pages/meson.build b/gnome-initial-setup/pages/meson.build index 8d327f69..ff8406ba 100644 --- a/gnome-initial-setup/pages/meson.build +++ b/gnome-initial-setup/pages/meson.build @@ -1,5 +1,6 @@ pages = [ 'account', + 'install', 'language', 'keyboard', 'network', diff --git a/gnome-initial-setup/pages/password/gis-password-page.c b/gnome-initial-setup/pages/password/gis-password-page.c index 6c12ca38..3d648c48 100644 --- a/gnome-initial-setup/pages/password/gis-password-page.c +++ b/gnome-initial-setup/pages/password/gis-password-page.c @@ -491,6 +491,12 @@ gis_password_page_init (GisPasswordPage *page) GisPage * gis_prepare_password_page (GisDriver *driver) { + GisDriverMode driver_mode; + + driver_mode = gis_driver_get_mode (driver); + if (driver_mode == GIS_DRIVER_MODE_LIVE_USER && !gis_kernel_command_line_has_argument ((const char *[]) { "rd.live.overlay", NULL })) + return NULL; + return g_object_new (GIS_TYPE_PASSWORD_PAGE, "driver", driver, NULL); -- 2.43.0 From 3f15601af333aeddb8e4458e69f81023a8184a8f Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 16 Aug 2023 10:47:13 -0400 Subject: [PATCH 08/16] initial-setup: Don't show duplicated pages between modes It's possible a user just got asked questions in live mode before install that they'll then get asked again on first boot when the initial user is created. This commit tracks that information so it doesn't get reasked. --- data/meson.build | 6 +++ gnome-initial-setup/gis-driver.c | 3 ++ gnome-initial-setup/gnome-initial-setup.c | 65 +++++++++++++++++++++++ meson.build | 4 ++ 4 files changed, 78 insertions(+) diff --git a/data/meson.build b/data/meson.build index 6a4ef7df..0bfccf56 100644 --- a/data/meson.build +++ b/data/meson.build @@ -130,3 +130,9 @@ configure_file( mode_dir = join_paths(data_dir, 'gnome-shell', 'modes') install_data('initial-setup.json', install_dir: mode_dir) + +install_subdir( + 'gnome-initial-setup', + install_dir : working_dir, + strip_directory : true +) diff --git a/gnome-initial-setup/gis-driver.c b/gnome-initial-setup/gis-driver.c index 6a4727cc..78265527 100644 --- a/gnome-initial-setup/gis-driver.c +++ b/gnome-initial-setup/gis-driver.c @@ -101,6 +101,8 @@ struct _GisDriver { const gchar *vendor_conf_file_path; GKeyFile *vendor_conf_file; + + GKeyFile *state_file; }; G_DEFINE_TYPE (GisDriver, gis_driver, ADW_TYPE_APPLICATION) @@ -131,6 +133,7 @@ gis_driver_finalize (GObject *object) g_clear_object (&driver->user_account); g_clear_pointer (&driver->vendor_conf_file, g_key_file_free); + g_clear_pointer (&driver->state_file, g_key_file_free); g_clear_object (&driver->parent_account); g_free (driver->parent_password); diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c index 02ca2808..4e2aa5af 100644 --- a/gnome-initial-setup/gnome-initial-setup.c +++ b/gnome-initial-setup/gnome-initial-setup.c @@ -48,6 +48,8 @@ #define VENDOR_EXISTING_USER_ONLY_KEY "existing_user_only" #define VENDOR_LIVE_USER_ONLY_KEY "live_user_only" +#define STATE_FILE GIS_WORKING_DIR "/state" + static gboolean force_existing_user_mode; static gboolean force_live_user_mode; @@ -119,6 +121,7 @@ pages_to_skip_from_file (GisDriver *driver) g_autofree char *mode_group = NULL; g_autoptr (GFlagsClass) driver_mode_flags_class = NULL; const GFlagsValue *driver_mode_flags = NULL; + g_autoptr (GError) error = NULL; /* This code will read the keyfile containing vendor customization options and * look for options under the "pages" group, and supports the following keys: @@ -187,6 +190,26 @@ pages_to_skip_from_file (GisDriver *driver) } } + /* Also, if this is a system mode, we check if the user already answered questions earlier in + * a different system mode, and skip those pages too. + */ + if (driver_mode & GIS_DRIVER_MODE_NEW_USER) { + g_autoptr(GKeyFile) state = NULL; + gboolean state_loaded; + + state = g_key_file_new (); + state_loaded = g_key_file_load_from_file (state, STATE_FILE, G_KEY_FILE_NONE, &error); + + if (state_loaded) { + skip_pages = g_key_file_get_string_list (state, VENDOR_PAGES_GROUP, VENDOR_SKIP_KEY, NULL, NULL); + + if (skip_pages != NULL) { + g_strv_builder_addv (builder, (const char **) skip_pages); + g_clear_pointer (&skip_pages, g_strfreev); + } + } + } + return g_strv_builder_end (builder); } @@ -396,6 +419,46 @@ main (int argc, char *argv[]) return status; } +static void +write_state (GisDriver *driver) +{ + g_autoptr(GKeyFile) state = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GStrvBuilder) builder = NULL; + g_auto(GStrv) visited_pages = NULL; + GisAssistant *assistant; + GList *pages, *node; + + assistant = gis_driver_get_assistant (driver); + + if (assistant == NULL) + return; + + state = g_key_file_new (); + + builder = g_strv_builder_new (); + + pages = gis_assistant_get_all_pages (assistant); + for (node = pages; node != NULL; node = node->next) { + GisPage *page = node->data; + g_strv_builder_add (builder, GIS_PAGE_GET_CLASS (page)->page_id); + } + + visited_pages = g_strv_builder_end (builder); + + g_key_file_set_string_list (state, + VENDOR_PAGES_GROUP, + VENDOR_SKIP_KEY, + (const char * const *) + visited_pages, + g_strv_length (visited_pages)); + + if (!g_key_file_save_to_file (state, STATE_FILE, &error)) { + g_warning ("Unable to save state to %s: %s", STATE_FILE, error->message); + return; + } +} + void gis_ensure_stamp_files (GisDriver *driver) { @@ -407,6 +470,8 @@ gis_ensure_stamp_files (GisDriver *driver) g_warning ("Unable to create %s: %s", done_file, error->message); g_clear_error (&error); } + + write_state (driver); } /** diff --git a/meson.build b/meson.build index 00e24d7b..95fe8e51 100644 --- a/meson.build +++ b/meson.build @@ -14,19 +14,23 @@ po_dir = join_paths(meson.current_source_dir(), 'po') bin_dir = join_paths(prefix, get_option('bindir')) data_dir = join_paths(prefix, get_option('datadir')) locale_dir = join_paths(prefix, get_option('localedir')) +localstate_dir = join_paths(prefix, get_option('localstatedir')) libexec_dir = join_paths(prefix, get_option('libexecdir')) sysconf_dir = join_paths(prefix, get_option('sysconfdir')) pkgdata_dir = join_paths(data_dir, meson.project_name()) pkgsysconf_dir = join_paths(sysconf_dir, meson.project_name()) +working_dir = join_paths(localstate_dir, 'lib', 'gnome-initial-setup') conf = configuration_data() conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) conf.set_quoted('GNOMELOCALEDIR', locale_dir) conf.set_quoted('PKGDATADIR', pkgdata_dir) conf.set_quoted('DATADIR', data_dir) +conf.set_quoted('LOCALSTATEDIR', localstate_dir) conf.set_quoted('PKGSYSCONFDIR', pkgsysconf_dir) conf.set_quoted('SYSCONFDIR', sysconf_dir) conf.set_quoted('LIBEXECDIR', libexec_dir) +conf.set_quoted('GIS_WORKING_DIR', working_dir) conf.set('SECRET_API_SUBJECT_TO_CHANGE', true) conf.set_quoted('G_LOG_DOMAIN', 'InitialSetup') conf.set('G_LOG_USE_STRUCTURED', true) -- 2.43.0 From 935b2cca6dd7e3ce31e2540d90e5864190885d81 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sun, 13 Aug 2023 16:33:49 -0400 Subject: [PATCH 09/16] polkit: Add fedora specfic rules We should probably add some way to check vendor.conf for the policy updates instead of a hardcoded list. --- data/20-gnome-initial-setup.rules.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/20-gnome-initial-setup.rules.in b/data/20-gnome-initial-setup.rules.in index 881efde9..f5b7d981 100644 --- a/data/20-gnome-initial-setup.rules.in +++ b/data/20-gnome-initial-setup.rules.in @@ -17,7 +17,8 @@ polkit.addRule(function(action, subject) { action.id.indexOf('org.freedesktop.realmd.') === 0 || action.id.indexOf('com.endlessm.ParentalControls.') === 0 || action.id.indexOf('org.fedoraproject.thirdparty.') === 0 || - action.id.indexOf('org.freedesktop.login1.reboot') === 0); + action.id.indexOf('org.freedesktop.login1.reboot') === 0 || + action.id.indexOf('org.fedoraproject.pkexec.liveinst') === 0); if (actionMatches) { if (subject.local) -- 2.43.0 From 03073bd42bf01f5d051f299860be40afd1844447 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Tue, 22 Aug 2023 13:51:40 -0400 Subject: [PATCH 10/16] gnome-initial-setup: Read /etc/sysconfig/anaconda Just as /var/lib/gnome-initial-setup/state may show pages the user has already answered, on Fedora, /etc/sysconfig/anaconda shows pages the user has already answered via Anaconda. This commit skips those from the --new-user mode as well. --- gnome-initial-setup/gnome-initial-setup.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c index 4e2aa5af..2de3ecab 100644 --- a/gnome-initial-setup/gnome-initial-setup.c +++ b/gnome-initial-setup/gnome-initial-setup.c @@ -196,6 +196,8 @@ pages_to_skip_from_file (GisDriver *driver) if (driver_mode & GIS_DRIVER_MODE_NEW_USER) { g_autoptr(GKeyFile) state = NULL; gboolean state_loaded; + g_autoptr(GKeyFile) anaconda = NULL; + gboolean anaconda_loaded; state = g_key_file_new (); state_loaded = g_key_file_load_from_file (state, STATE_FILE, G_KEY_FILE_NONE, &error); @@ -208,6 +210,27 @@ pages_to_skip_from_file (GisDriver *driver) g_clear_pointer (&skip_pages, g_strfreev); } } + + anaconda = g_key_file_new (); + anaconda_loaded = g_key_file_load_from_file (anaconda, "/etc/sysconfig/anaconda", G_KEY_FILE_NONE, NULL); + + if (anaconda_loaded) { + struct { + const char *spoke_name; + const char *page_name; + } spoke_page_map[] = { + { "WelcomeLanguageSpoke", "language" }, + { "DatetimeSpoke", "timezone" }, + { "KeyboardSpoke", "keyboard" }, + { NULL, NULL } + }; + size_t i; + + for (i = 0; spoke_page_map[i].spoke_name != NULL; i++) { + if (g_key_file_get_boolean (anaconda, spoke_page_map[i].spoke_name, "visited", NULL)) + g_strv_builder_add (builder, spoke_page_map[i].page_name); + } + } } return g_strv_builder_end (builder); -- 2.43.0 From baaa29d56bbb91f3ad15f90e3db98eea446b9adb Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Wed, 17 Jan 2024 12:29:54 -0600 Subject: [PATCH 11/16] Fix criticals in set_localed_input() If the default input sources do not match the current input sources, these won't be set in set_input_settings() and we shouldn't try to use them. Fixes: (gnome-initial-setup:41149): GLib-CRITICAL **: 10:09:25.599: g_strjoinv: assertion 'str_array != NULL' failed --- gnome-initial-setup/pages/keyboard/gis-keyboard-page.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c index 7801667c..3b59a0bf 100644 --- a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c +++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c @@ -253,12 +253,12 @@ set_localed_input (GisKeyboardPage *self) g_autofree char *variants = NULL; g_autofree char *options = NULL; - if (!priv->localed) + if (!priv->localed || !priv->system_layouts || !priv->system_variants) return; layouts = g_strjoinv (",", priv->system_layouts); variants = g_strjoinv (",", priv->system_variants); - options = g_strjoinv (",", priv->system_options); + options = priv->system_options ? g_strjoinv (",", priv->system_options) : g_strdup (""); g_dbus_proxy_call (priv->localed, "SetX11Keyboard", -- 2.43.0 From b46a3d7e6df08030680c2e7aaa6408c28315e75f Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Fri, 19 Jan 2024 15:49:15 -0600 Subject: [PATCH 12/16] assistant: assert next page exists when switching to next page If there is no next page, then we should crash nicely on this assert rather than not so nicely. --- gnome-initial-setup/gis-assistant.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gnome-initial-setup/gis-assistant.c b/gnome-initial-setup/gis-assistant.c index 8a7fc52b..c1af2943 100644 --- a/gnome-initial-setup/gis-assistant.c +++ b/gnome-initial-setup/gis-assistant.c @@ -111,7 +111,9 @@ find_next_page (GisAssistant *self, static void switch_to_next_page (GisAssistant *assistant) { - switch_to (assistant, find_next_page (assistant, assistant->current_page)); + GisPage *next = find_next_page (assistant, assistant->current_page); + g_assert (next != NULL); + switch_to (assistant, next); } static void -- 2.43.0 From deecf58106544ac8603575cb80e79ddfa1b822ba Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Fri, 19 Jan 2024 15:50:33 -0600 Subject: [PATCH 13/16] summary: don't crash if there is no user account to create --- gnome-initial-setup/pages/summary/gis-summary-page.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gnome-initial-setup/pages/summary/gis-summary-page.c b/gnome-initial-setup/pages/summary/gis-summary-page.c index 8a526b64..8c005640 100644 --- a/gnome-initial-setup/pages/summary/gis-summary-page.c +++ b/gnome-initial-setup/pages/summary/gis-summary-page.c @@ -151,6 +151,11 @@ log_user_in (GisSummaryPage *page) return; } + if (!priv->user_account) { + g_info ("No new user account (was the account page skipped?); not initiating login"); + return; + } + g_signal_connect (user_verifier, "info", G_CALLBACK (on_info), page); g_signal_connect (user_verifier, "problem", -- 2.43.0 From b3e54dbaa09e944e537050c7393cd69e44488384 Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Fri, 19 Jan 2024 15:52:03 -0600 Subject: [PATCH 14/16] Don't show warnings when failing to connect to gdm This is an expected condition. We need to log it since it might be needed when debugging, but a warning is overkill. --- gnome-initial-setup/gis-driver.c | 3 ++- gnome-initial-setup/pages/install/gis-install-page.c | 2 +- gnome-initial-setup/pages/summary/gis-summary-page.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gnome-initial-setup/gis-driver.c b/gnome-initial-setup/gis-driver.c index 78265527..653ba56b 100644 --- a/gnome-initial-setup/gis-driver.c +++ b/gnome-initial-setup/gis-driver.c @@ -777,7 +777,8 @@ connect_to_gdm (GisDriver *driver) driver->user_verifier = gdm_client_get_user_verifier_sync (driver->client, NULL, &error); if (error != NULL) { - g_warning ("Failed to open connection to GDM: %s", error->message); + /* Not a warning because this is expected if running in a user session */ + g_message ("Failed to open connection to GDM: %s", error->message); g_clear_object (&driver->user_verifier); g_clear_object (&driver->greeter); g_clear_object (&driver->client); diff --git a/gnome-initial-setup/pages/install/gis-install-page.c b/gnome-initial-setup/pages/install/gis-install-page.c index 36ed7539..850c8241 100644 --- a/gnome-initial-setup/pages/install/gis-install-page.c +++ b/gnome-initial-setup/pages/install/gis-install-page.c @@ -131,7 +131,7 @@ log_user_in (GisInstallPage *page) if (!gis_driver_get_gdm_objects (GIS_PAGE (page)->driver, &greeter, &user_verifier)) { - g_warning ("No GDM connection; not initiating login"); + g_info ("No GDM connection; not initiating login"); return; } diff --git a/gnome-initial-setup/pages/summary/gis-summary-page.c b/gnome-initial-setup/pages/summary/gis-summary-page.c index 8c005640..1352cb41 100644 --- a/gnome-initial-setup/pages/summary/gis-summary-page.c +++ b/gnome-initial-setup/pages/summary/gis-summary-page.c @@ -147,7 +147,7 @@ log_user_in (GisSummaryPage *page) if (!gis_driver_get_gdm_objects (GIS_PAGE (page)->driver, &greeter, &user_verifier)) { - g_warning ("No GDM connection; not initiating login"); + g_info ("No GDM connection; not initiating login"); return; } -- 2.43.0 From fa8caf710c38c41fc17f6dacc1c964a849e4c6e1 Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Fri, 19 Jan 2024 15:58:09 -0600 Subject: [PATCH 15/16] Never skip the summary page if available This avoids a crash when finishing the welcome page if there are no other pages available. --- gnome-initial-setup/gnome-initial-setup.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c index 2de3ecab..54bca86f 100644 --- a/gnome-initial-setup/gnome-initial-setup.c +++ b/gnome-initial-setup/gnome-initial-setup.c @@ -99,6 +99,16 @@ should_skip_page (const gchar *page_id, if (strcmp (page_id, "welcome") == 0) return !should_skip_page ("language", skip_pages); + /* We have to make sure the welcome page is not the last page because it + * unconditionally attempts to load the next page. So, always show the + * summary page. + * + * This doesn't work in existing user mode, but that's OK because we don't + * skip arbitrary previously-visited pages when in existing user mode. + */ + if (strcmp (page_id, "summary") == 0) + return FALSE; + /* check through our skip pages list for pages we don't want */ if (skip_pages) { while (skip_pages[i]) { -- 2.43.0 From b4e51c75dce43fb8095909278122c96a32b7a62d Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Mon, 22 Jan 2024 14:07:15 -0600 Subject: [PATCH 16/16] Fix failure to log into user account Currently gis_driver_get_user_permissions() is guaranteed to fail because we call it after gis_driver_save_data(), which creates the user account. We need to call it later at the right point. Also, let's own the user_password that we store on the summary and install page. Even if it's not expected to change, it doesn't seem very safe to rely on the GisDriver not deleting it. --- .../pages/install/gis-install-page.c | 41 ++++++++++++++---- .../pages/summary/gis-summary-page.c | 42 ++++++++++++------- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/gnome-initial-setup/pages/install/gis-install-page.c b/gnome-initial-setup/pages/install/gis-install-page.c index 850c8241..be4d6968 100644 --- a/gnome-initial-setup/pages/install/gis-install-page.c +++ b/gnome-initial-setup/pages/install/gis-install-page.c @@ -43,9 +43,7 @@ struct _GisInstallPagePrivate { GtkWidget *install_button; AdwStatusPage *status_page; GDesktopAppInfo *installer; - - ActUser *user_account; - const gchar *user_password; + char *user_password; }; typedef struct _GisInstallPagePrivate GisInstallPagePrivate; @@ -128,6 +126,24 @@ log_user_in (GisInstallPage *page) g_autoptr(GError) error = NULL; GdmGreeter *greeter = NULL; GdmUserVerifier *user_verifier = NULL; + ActUser *user_account = NULL; + const char *user_password = NULL; + + gis_driver_get_user_permissions (GIS_PAGE (page)->driver, + &user_account, + &user_password); + if (user_account == NULL) { + g_info ("No new user account (was the account page skipped?); not initiating login"); + return; + } + g_assert (priv->user_password == NULL); + priv->user_password = g_strdup (user_password); + + if (!gis_driver_get_gdm_objects (GIS_PAGE (page)->driver, + &greeter, &user_verifier)) { + g_info ("No GDM connection; not initiating login"); + return; + } if (!gis_driver_get_gdm_objects (GIS_PAGE (page)->driver, &greeter, &user_verifier)) { @@ -149,7 +165,7 @@ log_user_in (GisInstallPage *page) gdm_user_verifier_call_begin_verification_for_user_sync (user_verifier, SERVICE_NAME, - act_user_get_user_name (priv->user_account), + act_user_get_user_name (user_account), NULL, &error); if (error != NULL) @@ -259,11 +275,6 @@ gis_install_page_shown (GisPage *page) { GisInstallPage *install = GIS_INSTALL_PAGE (page); GisInstallPagePrivate *priv = gis_install_page_get_instance_private (install); - g_autoptr(GError) local_error = NULL; - - gis_driver_get_user_permissions (GIS_PAGE (page)->driver, - &priv->user_account, - &priv->user_password); gtk_widget_grab_focus (priv->install_button); } @@ -341,6 +352,17 @@ gis_install_page_constructed (GObject *object) gtk_widget_set_visible (GTK_WIDGET (page), TRUE); } +static void +gis_install_page_finalize (GObject *object) +{ + GisInstallPage *page = GIS_INSTALL_PAGE (object); + GisInstallPagePrivate *priv = gis_install_page_get_instance_private (page); + + g_clear_pointer (&priv->user_password, g_free); + + G_OBJECT_CLASS (gis_install_page_parent_class)->finalize (object); +} + static void gis_install_page_locale_changed (GisPage *page) { @@ -365,6 +387,7 @@ gis_install_page_class_init (GisInstallPageClass *klass) page_class->locale_changed = gis_install_page_locale_changed; page_class->shown = gis_install_page_shown; object_class->constructed = gis_install_page_constructed; + object_class->finalize = gis_install_page_finalize; } static void diff --git a/gnome-initial-setup/pages/summary/gis-summary-page.c b/gnome-initial-setup/pages/summary/gis-summary-page.c index 1352cb41..1fc14556 100644 --- a/gnome-initial-setup/pages/summary/gis-summary-page.c +++ b/gnome-initial-setup/pages/summary/gis-summary-page.c @@ -40,9 +40,7 @@ struct _GisSummaryPagePrivate { GtkWidget *start_button; AdwStatusPage *status_page; - - ActUser *user_account; - const gchar *user_password; + char *user_password; }; typedef struct _GisSummaryPagePrivate GisSummaryPagePrivate; @@ -144,15 +142,22 @@ log_user_in (GisSummaryPage *page) g_autoptr(GError) error = NULL; GdmGreeter *greeter = NULL; GdmUserVerifier *user_verifier = NULL; + ActUser *user_account = NULL; + const char *user_password = NULL; - if (!gis_driver_get_gdm_objects (GIS_PAGE (page)->driver, - &greeter, &user_verifier)) { - g_info ("No GDM connection; not initiating login"); + gis_driver_get_user_permissions (GIS_PAGE (page)->driver, + &user_account, + &user_password); + if (user_account == NULL) { + g_info ("No new user account (was the account page skipped?); not initiating login"); return; } + g_assert (priv->user_password == NULL); + priv->user_password = g_strdup (user_password); - if (!priv->user_account) { - g_info ("No new user account (was the account page skipped?); not initiating login"); + if (!gis_driver_get_gdm_objects (GIS_PAGE (page)->driver, + &greeter, &user_verifier)) { + g_info ("No GDM connection; not initiating login"); return; } @@ -171,11 +176,11 @@ log_user_in (GisSummaryPage *page) /* We are in NEW_USER mode and we want to make it possible for third * parties to find out which user ID we created. */ - add_uid_file (act_user_get_uid (priv->user_account)); + add_uid_file (act_user_get_uid (user_account)); gdm_user_verifier_call_begin_verification_for_user_sync (user_verifier, SERVICE_NAME, - act_user_get_user_name (priv->user_account), + act_user_get_user_name (user_account), NULL, &error); if (error != NULL) @@ -214,11 +219,6 @@ gis_summary_page_shown (GisPage *page) { GisSummaryPage *summary = GIS_SUMMARY_PAGE (page); GisSummaryPagePrivate *priv = gis_summary_page_get_instance_private (summary); - g_autoptr(GError) local_error = NULL; - - gis_driver_get_user_permissions (GIS_PAGE (page)->driver, - &priv->user_account, - &priv->user_password); gtk_widget_grab_focus (priv->start_button); } @@ -264,6 +264,17 @@ gis_summary_page_constructed (GObject *object) gtk_widget_set_visible (GTK_WIDGET (page), TRUE); } +static void +gis_summary_page_finalize (GObject *object) +{ + GisSummaryPage *page = GIS_SUMMARY_PAGE (object); + GisSummaryPagePrivate *priv = gis_summary_page_get_instance_private (page); + + g_clear_pointer (&priv->user_password, g_free); + + G_OBJECT_CLASS (gis_summary_page_parent_class)->finalize (object); +} + static void gis_summary_page_locale_changed (GisPage *page) { @@ -286,6 +297,7 @@ gis_summary_page_class_init (GisSummaryPageClass *klass) page_class->locale_changed = gis_summary_page_locale_changed; page_class->shown = gis_summary_page_shown; object_class->constructed = gis_summary_page_constructed; + object_class->finalize = gis_summary_page_finalize; } static void -- 2.43.0