/* -*- 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 . * * Written by: * Jasper St. Pierre */ #include "config.h" #include "gnome-initial-setup.h" #include #include #include #include #include #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); }