/* -*- 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 #ifdef HAVE_WEBKITGTK_6_0 #include #else #include #endif #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 gis_driver_mode_get_type (void) { static GType enum_type_id = 0; if (G_UNLIKELY (!enum_type_id)) { static const GEnumValue 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" }, { 0, NULL, NULL } }; enum_type_id = g_enum_register_static("GisDriverMode", values); } return enum_type_id; } enum { REBUILD_PAGES, LOCALE_CHANGED, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL]; typedef enum { PROP_MODE = 1, PROP_USERNAME, PROP_SMALL_SCREEN, PROP_PARENTAL_CONTROLS_ENABLED, PROP_FULL_NAME, PROP_AVATAR, } GisDriverProperty; static GParamSpec *obj_props[PROP_AVATAR + 1]; struct _GisDriver { AdwApplication parent_instance; GtkWindow *main_window; GisAssistant *assistant; GdmClient *client; GdmGreeter *greeter; GdmUserVerifier *user_verifier; ActUser *user_account; gchar *user_password; ActUser *parent_account; /* (owned) (nullable) */ gchar *parent_password; /* (owned) (nullable) */ gboolean parental_controls_enabled; gchar *lang_id; gchar *username; gchar *full_name; /* (owned) (nullable) */ GdkPaintable *avatar; /* (owned) (nullable) */ GisDriverMode mode; UmAccountMode account_mode; gboolean small_screen; locale_t locale; const gchar *vendor_conf_file_path; GKeyFile *vendor_conf_file; }; G_DEFINE_TYPE (GisDriver, gis_driver, ADW_TYPE_APPLICATION) static void gis_driver_dispose (GObject *object) { GisDriver *driver = GIS_DRIVER (object); g_clear_object (&driver->user_verifier); g_clear_object (&driver->greeter); g_clear_object (&driver->client); G_OBJECT_CLASS (gis_driver_parent_class)->dispose (object); } static void gis_driver_finalize (GObject *object) { GisDriver *driver = GIS_DRIVER (object); g_free (driver->lang_id); g_free (driver->username); g_free (driver->full_name); g_free (driver->user_password); g_clear_object (&driver->avatar); g_clear_object (&driver->user_account); g_clear_pointer (&driver->vendor_conf_file, g_key_file_free); g_clear_object (&driver->parent_account); g_free (driver->parent_password); if (driver->locale != (locale_t) 0) { uselocale (LC_GLOBAL_LOCALE); freelocale (driver->locale); } G_OBJECT_CLASS (gis_driver_parent_class)->finalize (object); } static void assistant_page_changed (GtkScrolledWindow *sw) { gtk_adjustment_set_value (gtk_scrolled_window_get_vadjustment (sw), 0); } static void prepare_main_window (GisDriver *driver) { GtkWidget *child, *sw; child = gtk_window_get_child (GTK_WINDOW (driver->main_window)); g_object_ref (child); gtk_window_set_child (GTK_WINDOW (driver->main_window), NULL); sw = gtk_scrolled_window_new (); gtk_window_set_child (GTK_WINDOW (driver->main_window), sw); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child); g_object_unref (child); g_signal_connect_swapped (driver->assistant, "page-changed", G_CALLBACK (assistant_page_changed), sw); gtk_window_set_titlebar (driver->main_window, gis_assistant_get_titlebar (driver->assistant)); } static void rebuild_pages (GisDriver *driver) { g_signal_emit (G_OBJECT (driver), signals[REBUILD_PAGES], 0); } GisAssistant * gis_driver_get_assistant (GisDriver *driver) { return driver->assistant; } static void gis_driver_locale_changed (GisDriver *driver) { GtkTextDirection direction; direction = gtk_get_locale_direction (); gtk_widget_set_default_direction (direction); rebuild_pages (driver); gis_assistant_locale_changed (driver->assistant); g_signal_emit (G_OBJECT (driver), signals[LOCALE_CHANGED], 0); } void gis_driver_set_user_language (GisDriver *driver, const gchar *lang_id, gboolean update_locale) { g_free (driver->lang_id); driver->lang_id = g_strdup (lang_id); cc_common_language_set_current_language (lang_id); if (update_locale) { locale_t locale = newlocale (LC_MESSAGES_MASK, lang_id, (locale_t) 0); if (locale == (locale_t) 0) { g_warning ("Failed to create locale %s: %s", lang_id, g_strerror (errno)); return; } uselocale (locale); if (driver->locale != (locale_t) 0 && driver->locale != LC_GLOBAL_LOCALE) freelocale (driver->locale); driver->locale = locale; gis_driver_locale_changed (driver); } } const gchar * gis_driver_get_user_language (GisDriver *driver) { return driver->lang_id; } void gis_driver_set_username (GisDriver *driver, const gchar *username) { g_free (driver->username); driver->username = g_strdup (username); g_object_notify (G_OBJECT (driver), "username"); } const gchar * gis_driver_get_username (GisDriver *driver) { return driver->username; } /** * gis_driver_set_full_name: * @driver: a #GisDriver * @full_name: (nullable): full name of the main user, or %NULL if not known * * Set the #GisDriver:full-name property. * * Since: 3.36 */ void gis_driver_set_full_name (GisDriver *driver, const gchar *full_name) { g_return_if_fail (GIS_IS_DRIVER (driver)); g_return_if_fail (full_name == NULL || g_utf8_validate (full_name, -1, NULL)); if (g_strcmp0 (driver->full_name, full_name) == 0) return; g_free (driver->full_name); driver->full_name = g_strdup (full_name); g_object_notify_by_pspec (G_OBJECT (driver), obj_props[PROP_FULL_NAME]); } /** * gis_driver_get_full_name: * @driver: a #GisDriver * * Get the #GisDriver:full-name property. * * Returns: (nullable): full name of the main user, or %NULL if not known * Since: 3.36 */ const gchar * gis_driver_get_full_name (GisDriver *driver) { g_return_val_if_fail (GIS_IS_DRIVER (driver), NULL); return driver->full_name; } /** * gis_driver_set_avatar: * @driver: a #GisDriver * @avatar: (nullable) (transfer none): avatar of the main user, or %NULL if not known * * Set the #GisDriver:avatar property. * * Since: 3.36 */ void gis_driver_set_avatar (GisDriver *driver, GdkPaintable *avatar) { g_return_if_fail (GIS_IS_DRIVER (driver)); g_return_if_fail (avatar == NULL || GDK_IS_PAINTABLE (avatar)); if (g_set_object (&driver->avatar, avatar)) g_object_notify_by_pspec (G_OBJECT (driver), obj_props[PROP_AVATAR]); } /** * gis_driver_get_avatar: * @driver: a #GisDriver * * Get the #GisDriver:avatar property. * * Returns: (nullable) (transfer none): avatar of the main user, or %NULL if not known * Since: 3.36 */ GdkPaintable * gis_driver_get_avatar (GisDriver *driver) { g_return_val_if_fail (GIS_IS_DRIVER (driver), NULL); return driver->avatar; } void gis_driver_set_user_permissions (GisDriver *driver, ActUser *user, const gchar *password) { g_set_object (&driver->user_account, user); g_free (driver->user_password); driver->user_password = g_strdup (password); } void gis_driver_get_user_permissions (GisDriver *driver, ActUser **user, const gchar **password) { if (user != NULL) *user = driver->user_account; if (password != NULL) *password = driver->user_password; } /** * gis_driver_set_parent_permissions: * @driver: a #GisDriver * @parent: (transfer none): user account for the parent * @password: password for the parent * * Stores the parent account details for later use when saving the initial setup * data. * * Since: 3.36 */ void gis_driver_set_parent_permissions (GisDriver *driver, ActUser *parent, const gchar *password) { g_set_object (&driver->parent_account, parent); g_free (driver->parent_password); driver->parent_password = g_strdup (password); } /** * gis_driver_get_parent_permissions: * @driver: a #GisDriver * @parent: (out) (transfer none) (optional) (nullable): return location for the * user account for the parent, which may be %NULL * @password: (out) (transfer none) (optional) (nullable): return location for * the password for the parent * * Gets the parent account details saved from an earlier step in the initial * setup process. They may be %NULL if not set yet. * * Since: 3.36 */ void gis_driver_get_parent_permissions (GisDriver *driver, ActUser **parent, const gchar **password) { if (parent != NULL) *parent = driver->parent_account; if (password != NULL) *password = driver->parent_password; } void gis_driver_set_account_mode (GisDriver *driver, UmAccountMode mode) { driver->account_mode = mode; } UmAccountMode gis_driver_get_account_mode (GisDriver *driver) { return driver->account_mode; } /** * gis_driver_set_parental_controls_enabled: * @driver: a #GisDriver * @parental_controls_enabled: whether parental controls are enabled for the main user * * Set the #GisDriver:parental-controls-enabled property. * * Since: 3.36 */ void gis_driver_set_parental_controls_enabled (GisDriver *driver, gboolean parental_controls_enabled) { if (driver->parental_controls_enabled == parental_controls_enabled) return; driver->parental_controls_enabled = parental_controls_enabled; rebuild_pages (driver); g_object_notify_by_pspec (G_OBJECT (driver), obj_props[PROP_PARENTAL_CONTROLS_ENABLED]); } /** * gis_driver_get_parental_controls_enabled: * @driver: a #GisDriver * * Get the #GisDriver:parental-controls-enabled property. * * Returns: whether parental controls are enabled for the main user * Since: 3.36 */ gboolean gis_driver_get_parental_controls_enabled (GisDriver *driver) { return driver->parental_controls_enabled; } gboolean gis_driver_get_gdm_objects (GisDriver *driver, GdmGreeter **greeter, GdmUserVerifier **user_verifier) { if (driver->greeter == NULL || driver->user_verifier == NULL) return FALSE; *greeter = driver->greeter; *user_verifier = driver->user_verifier; return TRUE; } void gis_driver_add_page (GisDriver *driver, GisPage *page) { gis_assistant_add_page (driver->assistant, page); } void gis_driver_hide_window (GisDriver *driver) { gtk_widget_hide (GTK_WIDGET (driver->main_window)); } static gboolean load_vendor_conf_file_at_path (GisDriver *driver, const char *path) { g_autoptr(GError) error = NULL; g_autoptr(GKeyFile) vendor_conf_file = g_key_file_new (); if (!g_key_file_load_from_file (vendor_conf_file, path, G_KEY_FILE_NONE, &error)) { if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) g_warning ("Could not read file %s: %s:", path, error->message); return FALSE; } driver->vendor_conf_file_path = path; driver->vendor_conf_file = g_steal_pointer (&vendor_conf_file); return TRUE; } static void load_vendor_conf_file (GisDriver *driver) { #ifdef VENDOR_CONF_FILE load_vendor_conf_file_at_path (driver, VENDOR_CONF_FILE); #else /* If no path was passed at build time, then we have search path: * * - First check $(sysconfdir)/gnome-initial-setup/vendor.conf * - Then check $(datadir)/gnome-initial-setup/vendor.conf * * This allows distributions to provide a default packaged config in a * location that might be managed by ostree, and allows OEMs to * override using an unmanaged location. */ if (!load_vendor_conf_file_at_path (driver, PKGSYSCONFDIR "/vendor.conf")) load_vendor_conf_file_at_path (driver, PKGDATADIR "/vendor.conf"); #endif } static void report_conf_error_if_needed (GisDriver *driver, const gchar *group, const gchar *key, const GError *error) { if (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND) && !g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) g_warning ("Error getting the value for key '%s' of group [%s] in %s: %s", group, key, driver->vendor_conf_file_path, error->message); } gboolean gis_driver_conf_get_boolean (GisDriver *driver, const gchar *group, const gchar *key, gboolean default_value) { if (driver->vendor_conf_file) { g_autoptr(GError) error = NULL; gboolean new_value = g_key_file_get_boolean (driver->vendor_conf_file, group, key, &error); if (error == NULL) return new_value; report_conf_error_if_needed (driver, group, key, error); } return default_value; } GStrv gis_driver_conf_get_string_list (GisDriver *driver, const gchar *group, const gchar *key, gsize *out_length) { if (driver->vendor_conf_file) { g_autoptr(GError) error = NULL; GStrv new_value = g_key_file_get_string_list (driver->vendor_conf_file, group, key, out_length, &error); if (error == NULL) return new_value; report_conf_error_if_needed (driver, group, key, error); } return NULL; } gchar * gis_driver_conf_get_string (GisDriver *driver, const gchar *group, const gchar *key) { if (driver->vendor_conf_file) { g_autoptr(GError) error = NULL; gchar *new_value = g_key_file_get_string (driver->vendor_conf_file, group, key, &error); if (error == NULL) return new_value; report_conf_error_if_needed (driver, group, key, error); } return NULL; } GisDriverMode gis_driver_get_mode (GisDriver *driver) { return driver->mode; } gboolean gis_driver_is_small_screen (GisDriver *driver) { return driver->small_screen; } static gboolean monitor_is_small (GdkMonitor *monitor) { GdkRectangle geom; if (g_getenv ("GIS_SMALL_SCREEN")) return TRUE; gdk_monitor_get_geometry (monitor, &geom); return geom.height < 800; } static void gis_driver_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GisDriver *driver = GIS_DRIVER (object); switch ((GisDriverProperty) prop_id) { case PROP_MODE: g_value_set_enum (value, driver->mode); break; case PROP_USERNAME: g_value_set_string (value, driver->username); break; case PROP_SMALL_SCREEN: g_value_set_boolean (value, driver->small_screen); break; case PROP_PARENTAL_CONTROLS_ENABLED: g_value_set_boolean (value, driver->parental_controls_enabled); break; case PROP_FULL_NAME: g_value_set_string (value, driver->full_name); break; case PROP_AVATAR: g_value_set_object (value, driver->avatar); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gis_driver_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GisDriver *driver = GIS_DRIVER (object); switch ((GisDriverProperty) prop_id) { case PROP_MODE: driver->mode = g_value_get_enum (value); break; case PROP_USERNAME: g_free (driver->username); driver->username = g_value_dup_string (value); break; case PROP_PARENTAL_CONTROLS_ENABLED: gis_driver_set_parental_controls_enabled (driver, g_value_get_boolean (value)); break; case PROP_FULL_NAME: gis_driver_set_full_name (driver, g_value_get_string (value)); break; case PROP_AVATAR: gis_driver_set_avatar (driver, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gis_driver_activate (GApplication *app) { GisDriver *driver = GIS_DRIVER (app); G_APPLICATION_CLASS (gis_driver_parent_class)->activate (app); gtk_window_present (GTK_WINDOW (driver->main_window)); } /* Recompute driver->small_screen based on the monitor where the window is * located, if the window is actually realized. If not, recompute it based on * the primary monitor of the default display. */ static void recompute_small_screen (GisDriver *driver) { GdkMonitor *active_monitor; gboolean old_value = driver->small_screen; if (gtk_widget_get_realized (GTK_WIDGET (driver->main_window))) { GdkDisplay *default_display = gdk_display_get_default (); GdkSurface *surface; surface = gtk_native_get_surface (GTK_NATIVE (driver->main_window)); active_monitor = gdk_display_get_monitor_at_surface (default_display, surface); driver->small_screen = monitor_is_small (active_monitor); } if (driver->small_screen != old_value) g_object_notify (G_OBJECT (driver), "small-screen"); } static void update_screen_size (GisDriver *driver) { GtkWidget *sw; recompute_small_screen (driver); if (!gtk_widget_get_realized (GTK_WIDGET (driver->main_window))) return; sw = gtk_window_get_child (GTK_WINDOW (driver->main_window)); if (driver->small_screen) { gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_window_set_default_size (driver->main_window, -1, -1); gtk_window_set_resizable (driver->main_window, TRUE); gtk_window_maximize (driver->main_window); gtk_window_present (driver->main_window); } else { gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_NEVER); gtk_window_set_default_size (driver->main_window, 1024, 768); gtk_window_set_resizable (driver->main_window, FALSE); gtk_window_unmaximize (driver->main_window); gtk_window_present (driver->main_window); } } static void on_surface_enter_monitor_cb (GdkSurface *surface, GdkMonitor *monitor, GisDriver *driver) { update_screen_size (driver); } static void window_realize_cb (GtkWidget *widget, gpointer user_data) { GdkSurface *surface; GisDriver *driver; driver = GIS_DRIVER (user_data); surface = gtk_native_get_surface (GTK_NATIVE (widget)); g_signal_connect (surface, "enter-monitor", G_CALLBACK (on_surface_enter_monitor_cb), driver); update_screen_size (driver); } static void connect_to_gdm (GisDriver *driver) { g_autoptr(GError) error = NULL; driver->client = gdm_client_new (); driver->greeter = gdm_client_get_greeter_sync (driver->client, NULL, &error); if (error == NULL) 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); g_clear_object (&driver->user_verifier); g_clear_object (&driver->greeter); g_clear_object (&driver->client); } } static void gis_driver_startup (GApplication *app) { GisDriver *driver = GIS_DRIVER (app); WebKitWebContext *context = webkit_web_context_get_default (); G_APPLICATION_CLASS (gis_driver_parent_class)->startup (app); webkit_web_context_set_sandbox_enabled (context, TRUE); if (driver->mode == GIS_DRIVER_MODE_NEW_USER) connect_to_gdm (driver); driver->main_window = g_object_new (GTK_TYPE_APPLICATION_WINDOW, "application", app, "icon-name", "preferences-system", "deletable", FALSE, NULL); g_signal_connect (driver->main_window, "realize", G_CALLBACK (window_realize_cb), (gpointer)app); driver->assistant = g_object_new (GIS_TYPE_ASSISTANT, NULL); gtk_window_set_child (GTK_WINDOW (driver->main_window), GTK_WIDGET (driver->assistant)); gis_driver_set_user_language (driver, setlocale (LC_MESSAGES, NULL), FALSE); prepare_main_window (driver); rebuild_pages (driver); } static void gis_driver_init (GisDriver *driver) { load_vendor_conf_file (driver); } static void gis_driver_class_init (GisDriverClass *klass) { GApplicationClass *application_class = G_APPLICATION_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = gis_driver_get_property; gobject_class->set_property = gis_driver_set_property; gobject_class->dispose = gis_driver_dispose; gobject_class->finalize = gis_driver_finalize; application_class->startup = gis_driver_startup; application_class->activate = gis_driver_activate; signals[REBUILD_PAGES] = g_signal_new ("rebuild-pages", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[LOCALE_CHANGED] = g_signal_new ("locale-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, 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); obj_props[PROP_USERNAME] = g_param_spec_string ("username", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_props[PROP_SMALL_SCREEN] = g_param_spec_boolean ("small-screen", "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * GisDriver:parental-controls-enabled: * * Whether parental controls are enabled for the main user. If this is %TRUE, * two user accounts will be created when this page is saved: one for the main * user (a child) which will be a standard account; and one for the parent * which will be an administrative account. * * Since: 3.36 */ obj_props[PROP_PARENTAL_CONTROLS_ENABLED] = g_param_spec_boolean ("parental-controls-enabled", "Parental Controls Enabled", "Whether parental controls are enabled for the main user.", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * GisDriver:full-name: (nullable) * * Full name of the main user. May be %NULL if unknown or not set yet. * * Since: 3.36 */ obj_props[PROP_FULL_NAME] = g_param_spec_string ("full-name", "Full Name", "Full name of the main user.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * GisDriver:avatar: (nullable) * * Avatar of the main user. May be %NULL if unknown or not set yet. * * Since: 3.36 */ obj_props[PROP_AVATAR] = g_param_spec_object ("avatar", "Avatar", "Avatar of the main user.", GDK_TYPE_PAINTABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (gobject_class, G_N_ELEMENTS (obj_props), obj_props); } gboolean gis_driver_save_data (GisDriver *driver, GError **error) { if (gis_get_mock_mode ()) { g_message ("%s: Skipping saving data due to being in mock mode", G_STRFUNC); return TRUE; } return gis_assistant_save_data (driver->assistant, error); } GisDriver * gis_driver_new (GisDriverMode mode) { return g_object_new (GIS_TYPE_DRIVER, "application-id", "org.gnome.InitialSetup", "mode", mode, NULL); }