Blob Blame History Raw
From edb06ad519f139cb6d382fb4fb95727dafc01c7a Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Sat, 9 Sep 2023 17:07:46 -0400
Subject: [PATCH 01/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.44.0


From fda3cd24142f21c14f96f339a2fa10b5f923f50d Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Fri, 8 Sep 2023 11:02:39 -0400
Subject: [PATCH 02/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 0ac8281d..f8f82097 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);
 }
 
@@ -126,6 +145,9 @@ language_changed (CcLanguageChooser  *chooser,
   gis_driver_set_user_language (driver, priv->new_locale_id, TRUE);
 
   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);
       }
@@ -176,6 +198,7 @@ localed_proxy_ready (GObject      *source,
   }
 
   priv->localed = proxy;
+  gis_page_set_complete (GIS_PAGE (self), TRUE);
 }
 
 static void
@@ -250,8 +273,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.44.0


From 633b1c51b2947ab4bc27eeac1384e8efda2b6011 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 24 Aug 2023 21:19:40 -0400
Subject: [PATCH 03/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..ea9b9f35 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, NULL, &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.44.0


From cd68a6bd404679af1eba0c8aea13dfe235d4ee1e Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Sun, 13 Aug 2023 09:09:56 -0400
Subject: [PATCH 04/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 52d5874c..1538f927 100644
--- a/gnome-initial-setup/gis-driver.c
+++ b/gnome-initial-setup/gis-driver.c
@@ -33,8 +33,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
@@ -42,12 +40,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;
 }
@@ -711,7 +710,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);
@@ -926,10 +925,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 ca06391f..36bfa2dd 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.44.0


From 26378d689b12ba869ecce05030b2a8035c536c20 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 30 Aug 2023 15:08:23 -0400
Subject: [PATCH 05/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 d3bb7999..9eaf90dd 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.44.0


From 8c26749626ff7f0391632cc5e01944642ca72692 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Sun, 13 Aug 2023 09:39:07 -0400
Subject: [PATCH 06/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 1538f927..1c22ba86 100644
--- a/gnome-initial-setup/gis-driver.c
+++ b/gnome-initial-setup/gis-driver.c
@@ -43,6 +43,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 }
       };
@@ -855,7 +857,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 36bfa2dd..d4b42b10 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* 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 <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <act/act-user-manager.h>
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GisInstallPage" parent="GisPage">
+    <child>
+      <object class="AdwStatusPage" id="status_page">
+        <property name="icon-name">document-save-symbolic</property>
+        <property name="title" translatable="yes">Try or Install?</property>
+        <property name="description" translatable="yes">You can try a temporary version of ${NAME} without making changes to the computer. If you decide to install later, just launch the installer from the Activities view.
+
+        Alternatively, choose install to launch the installer.</property>
+
+        <child>
+          <object class="GtkButton" id="start_button">
+            <property name="use_underline">True</property>
+            <property name="halign">center</property>
+            <style>
+              <class name="suggested-action"/>
+              <class name="pill"/>
+            </style>
+          </object>
+        </child>
+
+        <child>
+          <object class="GtkBox" id="button_box">
+            <property name="orientation">horizontal</property>
+            <property name="homogeneous">True</property>
+            <property name="spacing">24</property>
+            <property name="halign">center</property>
+            <child>
+              <object class="GtkButton" id="try_button">
+                <property name="label" translatable="yes">Try ${NAME}</property>
+                <style>
+                  <class name="pill"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="install_button">
+                <property name="label" translatable="yes">Install to Diskā€¦</property>
+                <style>
+                  <class name="suggested-action"/>
+                  <class name="pill"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/initial-setup">
+    <file preprocess="xml-stripblanks" alias="gis-install-page.ui">gis-install-page.ui</file>
+    <file alias="gis-install-page.css">gis-install-page.css</file>
+  </gresource>
+</gresources>
+
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 ea9b9f35..debcd146 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 f8f82097..9c2004c0 100644
--- a/gnome-initial-setup/pages/language/gis-language-page.c
+++ b/gnome-initial-setup/pages/language/gis-language-page.c
@@ -144,7 +144,7 @@ language_changed (CcLanguageChooser  *chooser,
 
   gis_driver_set_user_language (driver, priv->new_locale_id, TRUE);
 
-  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);
 
@@ -256,8 +256,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 9a41b50f..a466316d 100644
--- a/gnome-initial-setup/pages/password/gis-password-page.c
+++ b/gnome-initial-setup/pages/password/gis-password-page.c
@@ -507,6 +507,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.44.0


From f700a87a96fe21a20da88df5b775d524f9a8ab85 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 16 Aug 2023 10:47:13 -0400
Subject: [PATCH 07/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 dfa064c5..8203e29e 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 1c22ba86..0934d2c5 100644
--- a/gnome-initial-setup/gis-driver.c
+++ b/gnome-initial-setup/gis-driver.c
@@ -106,6 +106,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)
@@ -136,6 +138,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 0f434482..5fdeced7 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.44.0


From 4bd3eb002d1dcded6efe647c108f76ccd0acd673 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Sun, 13 Aug 2023 16:33:49 -0400
Subject: [PATCH 08/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.44.0


From cae816eb42216cf1c2370fbc88aaba1b70579931 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Tue, 22 Aug 2023 13:51:40 -0400
Subject: [PATCH 09/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.44.0


From da764a5d6b27d3a0a90597a55dc544b6af0deb87 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Wed, 17 Jan 2024 12:29:54 -0600
Subject: [PATCH 10/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 debcd146..fa4c0ea9 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.44.0


From c96fcb4a66a80d988417f1c01231b1c3c26219fc Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Fri, 19 Jan 2024 15:49:15 -0600
Subject: [PATCH 11/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.44.0


From 1c593f6324d5f81348d0c2c2888d393bbeadd3b4 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Fri, 19 Jan 2024 15:50:33 -0600
Subject: [PATCH 12/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 9eaf90dd..f359673f 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.44.0


From d7eb5b4b446412ecd06148908242b6ccc0826ae2 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Fri, 19 Jan 2024 15:52:03 -0600
Subject: [PATCH 13/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 0934d2c5..c3a25c6f 100644
--- a/gnome-initial-setup/gis-driver.c
+++ b/gnome-initial-setup/gis-driver.c
@@ -846,7 +846,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 f359673f..367c5285 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.44.0


From 96ac17aece664173830b467fd2007b421b141004 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Fri, 19 Jan 2024 15:58:09 -0600
Subject: [PATCH 14/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.44.0


From ec202e90d00738e15a9cd5826545994fc20dcd3d Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Mon, 22 Jan 2024 14:07:15 -0600
Subject: [PATCH 15/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 367c5285..37881442 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.44.0


From be7626a013d8838f393ff20d760c4076a164c233 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Thu, 25 Jan 2024 17:09:29 -0600
Subject: [PATCH 16/16] Update POTFILES.in

Related: #209
---
 po/POTFILES.in | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 10536d14..c235b379 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -16,6 +16,8 @@ gnome-initial-setup/pages/account/gis-account-page.ui
 gnome-initial-setup/pages/account/um-photo-dialog.c
 gnome-initial-setup/pages/account/um-realm-manager.c
 gnome-initial-setup/pages/account/um-utils.c
+gnome-initial-setup/pages/install/gis-install-page.c
+gnome-initial-setup/pages/install/gis-install-page.ui
 gnome-initial-setup/pages/keyboard/cc-input-chooser.c
 gnome-initial-setup/pages/keyboard/gis-keyboard-page.c
 gnome-initial-setup/pages/keyboard/gis-keyboard-page.ui
-- 
2.44.0