summaryrefslogtreecommitdiffstats
path: root/gnome-initial-setup/gnome-initial-setup.c
diff options
context:
space:
mode:
Diffstat (limited to 'gnome-initial-setup/gnome-initial-setup.c')
-rw-r--r--gnome-initial-setup/gnome-initial-setup.c387
1 files changed, 387 insertions, 0 deletions
diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c
new file mode 100644
index 0000000..113ded3
--- /dev/null
+++ b/gnome-initial-setup/gnome-initial-setup.c
@@ -0,0 +1,387 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 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/>.
+ *
+ * Written by:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#include "config.h"
+
+#include "gnome-initial-setup.h"
+
+#include <adwaita.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+
+#include "pages/welcome/gis-welcome-page.h"
+#include "pages/language/gis-language-page.h"
+#include "pages/keyboard/gis-keyboard-page.h"
+#include "pages/network/gis-network-page.h"
+#include "pages/timezone/gis-timezone-page.h"
+#include "pages/privacy/gis-privacy-page.h"
+#include "pages/software/gis-software-page.h"
+#include "pages/goa/gis-goa-page.h"
+#include "pages/account/gis-account-pages.h"
+#include "pages/parental-controls/gis-parental-controls-page.h"
+#include "pages/password/gis-password-page.h"
+#include "pages/summary/gis-summary-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"
+
+static gboolean force_existing_user_mode;
+
+static GPtrArray *skipped_pages;
+
+typedef GisPage *(*PreparePage) (GisDriver *driver);
+
+typedef struct {
+ const gchar *page_id;
+ PreparePage prepare_page_func;
+ gboolean new_user_only;
+} PageData;
+
+#define PAGE(name, new_user_only) { #name, gis_prepare_ ## name ## _page, new_user_only }
+
+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 (goa, FALSE),
+ PAGE (account, TRUE),
+ PAGE (password, TRUE),
+#ifdef HAVE_PARENTAL_CONTROLS
+ PAGE (parental_controls, TRUE),
+ PAGE (parent_password, TRUE),
+#endif
+ PAGE (summary, FALSE),
+ { NULL },
+};
+
+#undef PAGE
+
+static gboolean
+should_skip_page (const gchar *page_id,
+ gchar **skip_pages)
+{
+ guint i = 0;
+
+ /* special case welcome. We only want to show it if language
+ * is skipped
+ */
+ if (strcmp (page_id, "welcome") == 0)
+ return !should_skip_page ("language", skip_pages);
+
+ /* check through our skip pages list for pages we don't want */
+ if (skip_pages) {
+ while (skip_pages[i]) {
+ if (g_strcmp0 (skip_pages[i], page_id) == 0)
+ return TRUE;
+ i++;
+ }
+ }
+
+ return FALSE;
+}
+
+static gchar **
+strv_append (gchar **a,
+ gchar **b)
+{
+ 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;
+
+ /* 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
+ *
+ * This is how this file might look on a vendor image:
+ *
+ * [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);
+ }
+
+ return skip_pages;
+}
+
+static void
+destroy_pages_after (GisAssistant *assistant,
+ GisPage *page)
+{
+ GList *pages, *l, *next;
+
+ pages = gis_assistant_get_all_pages (assistant);
+
+ for (l = pages; l != NULL; l = l->next)
+ if (l->data == page)
+ break;
+
+ l = l->next;
+ for (; l != NULL; l = next) {
+ next = l->next;
+ gis_assistant_remove_page (assistant, l->data);
+ }
+}
+
+static void
+destroy_page (gpointer data)
+{
+ GtkWidget *assistant;
+ GisPage *page;
+
+ page = data;
+ assistant = gtk_widget_get_ancestor (GTK_WIDGET (page), GIS_TYPE_ASSISTANT);
+
+ if (assistant)
+ gis_assistant_remove_page (GIS_ASSISTANT (assistant), page);
+}
+
+static void
+rebuild_pages_cb (GisDriver *driver)
+{
+ PageData *page_data;
+ GisPage *page;
+ GisAssistant *assistant;
+ GisPage *current_page;
+ gchar **skip_pages;
+ gboolean is_new_user, skipped;
+
+ assistant = gis_driver_get_assistant (driver);
+ current_page = gis_assistant_get_current_page (assistant);
+ page_data = page_table;
+
+ g_ptr_array_free (skipped_pages, TRUE);
+ skipped_pages = g_ptr_array_new_with_free_func (destroy_page);
+
+ if (current_page != NULL) {
+ destroy_pages_after (assistant, current_page);
+
+ for (page_data = page_table; page_data->page_id != NULL; ++page_data)
+ if (g_str_equal (page_data->page_id, GIS_PAGE_GET_CLASS (current_page)->page_id))
+ break;
+
+ ++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);
+
+ for (; page_data->page_id != NULL; ++page_data) {
+ skipped = FALSE;
+
+ if ((page_data->new_user_only && !is_new_user) ||
+ (should_skip_page (page_data->page_id, skip_pages)))
+ skipped = TRUE;
+
+ page = page_data->prepare_page_func (driver);
+ if (!page)
+ continue;
+
+ if (skipped) {
+ gis_page_skip (page);
+ g_ptr_array_add (skipped_pages, page);
+ } else {
+ gis_driver_add_page (driver, page);
+ }
+ }
+
+ g_strfreev (skip_pages);
+}
+
+static GisDriverMode
+get_mode (void)
+{
+ if (force_existing_user_mode)
+ return GIS_DRIVER_MODE_EXISTING_USER;
+ else
+ return GIS_DRIVER_MODE_NEW_USER;
+}
+
+static gboolean
+initial_setup_disabled_by_anaconda (void)
+{
+ const gchar *file_name = SYSCONFDIR "/sysconfig/anaconda";
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GKeyFile) key_file = g_key_file_new ();
+
+ if (!g_key_file_load_from_file (key_file, file_name, G_KEY_FILE_NONE, &error)) {
+ if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) &&
+ !g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) {
+ g_warning ("Could not read %s: %s", file_name, error->message);
+ }
+ return FALSE;
+ }
+
+ return g_key_file_get_boolean (key_file, "General", "post_install_tools_disabled", NULL);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GisDriver *driver;
+ int status;
+ GOptionContext *context;
+ GisDriverMode mode;
+
+ GOptionEntry entries[] = {
+ { "existing-user", 0, 0, G_OPTION_ARG_NONE, &force_existing_user_mode,
+ _("Force existing user mode"), NULL },
+ { NULL }
+ };
+
+ g_unsetenv ("GIO_USE_VFS");
+
+ /* By default, libadwaita reads settings from the Settings portal, which causes
+ * the portal to be started, which causes gnome-keyring to be started. This
+ * interferes with our attempt below to manually start gnome-keyring and set
+ * the login keyring password to a well-known value, which we overwrite with
+ * the user's password once they choose one.
+ */
+ g_setenv ("ADW_DISABLE_PORTAL", "1", TRUE);
+
+ context = g_option_context_new (_("— GNOME initial setup"));
+ g_option_context_add_main_entries (context, entries, NULL);
+
+ g_option_context_parse (context, &argc, &argv, NULL);
+
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ g_message ("Starting gnome-initial-setup");
+ if (gis_get_mock_mode ())
+ g_message ("Mock mode: changes will not be saved to disk");
+ else
+ g_message ("Production mode: changes will be saved to disk");
+
+ skipped_pages = g_ptr_array_new_with_free_func (destroy_page);
+ mode = get_mode ();
+
+ /* When we are running as the gnome-initial-setup user we
+ * dont have a normal user session and need to initialize
+ * 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 ())
+ gis_ensure_login_keyring ();
+
+ driver = gis_driver_new (mode);
+ adw_style_manager_set_color_scheme (adw_style_manager_get_default (),
+ ADW_COLOR_SCHEME_PREFER_LIGHT);
+
+ /* On first login, GNOME Shell offers to run a tour. If we also run Initial
+ * Setup, the two immovable, centred windows will sit atop one another.
+ * Until we have the ability to run Initial Setup in the "kiosk" mode, like
+ * it does in new-user mode, disable Initial Setup for existing users.
+ *
+ * https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/issues/120#note_1019004
+ * https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/issues/12
+ */
+ if (mode == GIS_DRIVER_MODE_EXISTING_USER) {
+ g_message ("Skipping gnome-initial-setup for existing user");
+ gis_ensure_stamp_files (driver);
+ exit (EXIT_SUCCESS);
+ }
+
+ /* We only do this in existing-user mode, because if gdm launches us
+ * in new-user mode and we just exit, gdm's special g-i-s session
+ * never terminates. */
+ if (initial_setup_disabled_by_anaconda () &&
+ mode == GIS_DRIVER_MODE_EXISTING_USER) {
+ gis_ensure_stamp_files (driver);
+ exit (EXIT_SUCCESS);
+ }
+
+ g_signal_connect (driver, "rebuild-pages", G_CALLBACK (rebuild_pages_cb), NULL);
+ status = g_application_run (G_APPLICATION (driver), argc, argv);
+
+ g_ptr_array_free (skipped_pages, TRUE);
+
+ g_object_unref (driver);
+ g_option_context_free (context);
+ return status;
+}
+
+void
+gis_ensure_stamp_files (GisDriver *driver)
+{
+ g_autofree gchar *done_file = NULL;
+ g_autoptr(GError) error = NULL;
+
+ done_file = g_build_filename (g_get_user_config_dir (), "gnome-initial-setup-done", NULL);
+ if (!g_file_set_contents (done_file, "yes", -1, &error)) {
+ g_warning ("Unable to create %s: %s", done_file, error->message);
+ g_clear_error (&error);
+ }
+}
+
+/**
+ * gis_get_mock_mode:
+ *
+ * Gets whether gnome-initial-setup has been built for development, and hence
+ * shouldn’t permanently change any system configuration.
+ *
+ * By default, mock mode is enabled when running in a build environment. This
+ * heuristic may be changed in future.
+ *
+ * Returns: %TRUE if in mock mode, %FALSE otherwise
+ */
+gboolean
+gis_get_mock_mode (void)
+{
+ return (g_getenv ("UNDER_JHBUILD") != NULL);
+}