/* -*- 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);
}