diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 18:09:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 18:09:04 +0000 |
commit | 829aea9a4dce048f63c6d9568b50ab4bdb7609e3 (patch) | |
tree | 7c5d5ac80e81dc5186f19649f48387a67df20425 /gnome-initial-setup | |
parent | Initial commit. (diff) | |
download | gnome-initial-setup-829aea9a4dce048f63c6d9568b50ab4bdb7609e3.tar.xz gnome-initial-setup-829aea9a4dce048f63c6d9568b50ab4bdb7609e3.zip |
Adding upstream version 3.38.4.upstream/3.38.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
208 files changed, 20260 insertions, 0 deletions
diff --git a/gnome-initial-setup.doap b/gnome-initial-setup.doap new file mode 100644 index 0000000..c194c82 --- /dev/null +++ b/gnome-initial-setup.doap @@ -0,0 +1,31 @@ +<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" + xmlns:foaf="http://xmlns.com/foaf/0.1/" + xmlns:gnome="http://api.gnome.org/doap-extensions#" + xmlns="http://usefulinc.com/ns/doap#"> + + <name xml:lang="en">GNOME Initial Setup</name> + <shortdesc xml:lang="en">Bootstrapping your OS</shortdesc> + <description xml:lang="en">gnome-initial-setup helps you set up your OS when you boot or log in for the first time.</description> + + <download-page rdf:resource="http://download.gnome.org/sources/gnome-initial-setup/" /> + <bug-database rdf:resource="https://gitlab.gnome.org/GNOME/gnome-initial-setup/issues/" /> + <category rdf:resource="http://api.gnome.org/doap-extensions#apps" /> + <programming-language>C</programming-language> + + <maintainer> + <foaf:Person> + <foaf:name>Matthias Clasen</foaf:name> + <foaf:mbox rdf:resource="mailto:mclasen@redhat.com" /> + <gnome:userid>matthiasc</gnome:userid> + </foaf:Person> + </maintainer> + + <maintainer> + <foaf:Person> + <foaf:name>Will Thompson</foaf:name> + <foaf:mbox rdf:resource="mailto:wjt@gnome.org" /> + <gnome:userid>wjt</gnome:userid> + </foaf:Person> + </maintainer> +</Project> diff --git a/gnome-initial-setup/cc-common-language.c b/gnome-initial-setup/cc-common-language.c new file mode 100644 index 0000000..0aba41f --- /dev/null +++ b/gnome-initial-setup/cc-common-language.c @@ -0,0 +1,318 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 Red Hat, Inc, + * + * 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: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> +#include <locale.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include <fontconfig/fontconfig.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> + +#include "cc-common-language.h" + +static char *get_lang_for_user_object_path (const char *path); + +static char *current_language; + +gboolean +cc_common_language_has_font (const gchar *locale) +{ + const FcCharSet *charset; + FcPattern *pattern; + FcObjectSet *object_set; + FcFontSet *font_set; + gchar *language_code; + gboolean is_displayable; + + is_displayable = FALSE; + pattern = NULL; + object_set = NULL; + font_set = NULL; + + if (!gnome_parse_locale (locale, &language_code, NULL, NULL, NULL)) + return FALSE; + + charset = FcLangGetCharSet ((FcChar8 *) language_code); + if (!charset) { + /* fontconfig does not know about this language */ + is_displayable = TRUE; + } + else { + /* see if any fonts support rendering it */ + pattern = FcPatternBuild (NULL, FC_LANG, FcTypeString, language_code, NULL); + + if (pattern == NULL) + goto done; + + object_set = FcObjectSetCreate (); + + if (object_set == NULL) + goto done; + + font_set = FcFontList (NULL, pattern, object_set); + + if (font_set == NULL) + goto done; + + is_displayable = (font_set->nfont > 0); + } + + done: + if (font_set != NULL) + FcFontSetDestroy (font_set); + + if (object_set != NULL) + FcObjectSetDestroy (object_set); + + if (pattern != NULL) + FcPatternDestroy (pattern); + + g_free (language_code); + + return is_displayable; +} + +gchar * +cc_common_language_get_current_language (void) +{ + g_assert (current_language != NULL); + return g_strdup (current_language); +} + +void +cc_common_language_set_current_language (const char *locale) +{ + g_clear_pointer (¤t_language, g_free); + current_language = gnome_normalize_locale (locale); +} + +static gboolean +user_language_has_translations (const char *locale) +{ + char *name, *language_code, *territory_code; + gboolean ret; + + gnome_parse_locale (locale, + &language_code, + &territory_code, + NULL, NULL); + name = g_strdup_printf ("%s%s%s", + language_code, + territory_code != NULL? "_" : "", + territory_code != NULL? territory_code : ""); + g_free (language_code); + g_free (territory_code); + ret = gnome_language_has_translations (name); + g_free (name); + + return ret; +} + +static char * +get_lang_for_user_object_path (const char *path) +{ + GError *error = NULL; + GDBusProxy *user; + GVariant *props; + char *lang; + + user = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.Accounts", + path, + "org.freedesktop.Accounts.User", + NULL, + &error); + if (user == NULL) { + g_warning ("Failed to get proxy for user '%s': %s", + path, error->message); + g_error_free (error); + return NULL; + } + + lang = NULL; + props = g_dbus_proxy_get_cached_property (user, "Language"); + if (props != NULL) { + lang = g_variant_dup_string (props, NULL); + g_variant_unref (props); + } + + g_object_unref (user); + return lang; +} + +static void +add_other_users_language (GHashTable *ht) +{ + GVariant *variant; + GVariantIter *vi; + GError *error = NULL; + const char *str; + GDBusProxy *proxy; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.Accounts", + "/org/freedesktop/Accounts", + "org.freedesktop.Accounts", + NULL, + NULL); + + if (proxy == NULL) + return; + + variant = g_dbus_proxy_call_sync (proxy, + "ListCachedUsers", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (variant == NULL) { + g_warning ("Failed to list existing users: %s", error->message); + g_error_free (error); + g_object_unref (proxy); + return; + } + g_variant_get (variant, "(ao)", &vi); + while (g_variant_iter_loop (vi, "o", &str)) { + char *lang; + char *name; + char *language; + + lang = get_lang_for_user_object_path (str); + if (lang != NULL && *lang != '\0' && + cc_common_language_has_font (lang) && + user_language_has_translations (lang)) { + name = gnome_normalize_locale (lang); + if (!g_hash_table_lookup (ht, name)) { + language = gnome_get_language_from_locale (name, NULL); + g_hash_table_insert (ht, name, language); + } + else { + g_free (name); + } + } + g_free (lang); + } + g_variant_iter_free (vi); + g_variant_unref (variant); + + g_object_unref (proxy); +} + +/* + * Note that @lang needs to be formatted like the locale strings + * returned by gnome_get_all_locales(). + */ +static void +insert_language (GHashTable *ht, + const char *lang) +{ + locale_t locale; + char *label_own_lang; + char *label_current_lang; + char *label_untranslated; + char *key; + + locale = newlocale (LC_MESSAGES_MASK, lang, (locale_t) 0); + if (locale == (locale_t) 0) { + g_debug ("%s: Failed to create locale %s", G_STRFUNC, lang); + return; + } + freelocale (locale); + + + key = g_strdup (lang); + + label_own_lang = gnome_get_language_from_locale (key, key); + label_current_lang = gnome_get_language_from_locale (key, current_language); + label_untranslated = gnome_get_language_from_locale (key, "C"); + + /* We don't have a translation for the label in + * its own language? */ + if (label_own_lang == NULL || g_strcmp0 (label_own_lang, label_untranslated) == 0) { + if (g_strcmp0 (label_current_lang, label_untranslated) == 0) + g_hash_table_insert (ht, key, g_strdup (label_untranslated)); + else + g_hash_table_insert (ht, key, g_strdup (label_current_lang)); + } else { + g_hash_table_insert (ht, key, g_strdup (label_own_lang)); + } + + g_free (label_own_lang); + g_free (label_current_lang); + g_free (label_untranslated); +} + +static void +insert_user_languages (GHashTable *ht) +{ + char *name; + + /* Add the languages used by other users on the system */ + add_other_users_language (ht); + + /* Add current locale */ + name = cc_common_language_get_current_language (); + if (g_hash_table_lookup (ht, name) == NULL) { + insert_language (ht, name); + } else { + g_free (name); + } +} + +GHashTable * +cc_common_language_get_initial_languages (void) +{ + GHashTable *ht; + + ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + insert_language (ht, "en_US.UTF-8"); +#if 0 + /* Having 9 languages in the list initially makes the window + * too high. With 8 languages, we end up exactly 768 pixels + * high. Sadly, that means we can't affort to show English + * twice. + */ + insert_language (ht, "en_GB.UTF-8"); +#endif + insert_language (ht, "de_DE.UTF-8"); + insert_language (ht, "fr_FR.UTF-8"); + insert_language (ht, "es_ES.UTF-8"); + insert_language (ht, "zh_CN.UTF-8"); + insert_language (ht, "ja_JP.UTF-8"); + insert_language (ht, "ru_RU.UTF-8"); + insert_language (ht, "ar_EG.UTF-8"); + + insert_user_languages (ht); + + return ht; +} diff --git a/gnome-initial-setup/cc-common-language.h b/gnome-initial-setup/cc-common-language.h new file mode 100644 index 0000000..49ead3a --- /dev/null +++ b/gnome-initial-setup/cc-common-language.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 Red Hat, Inc, + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#ifndef __CC_COMMON_LANGUAGE_H__ +#define __CC_COMMON_LANGUAGE_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +gboolean cc_common_language_has_font (const gchar *locale); +void cc_common_language_set_current_language (const gchar *locale); +gchar *cc_common_language_get_current_language (void); +GHashTable *cc_common_language_get_initial_languages (void); + +G_END_DECLS + +#endif diff --git a/gnome-initial-setup/gis-assistant.c b/gnome-initial-setup/gis-assistant.c new file mode 100644 index 0000000..14887f6 --- /dev/null +++ b/gnome-initial-setup/gis-assistant.c @@ -0,0 +1,538 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* -*- encoding: utf8 -*- */ +/* + * 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 <glib-object.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gis-assistant.h" + +enum { + PROP_0, + PROP_TITLE, + PROP_LAST, +}; + +enum { + PAGE_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; +static GParamSpec *obj_props[PROP_LAST]; + +struct _GisAssistantPrivate +{ + GtkWidget *forward; + GtkWidget *accept; + GtkWidget *skip; + GtkWidget *back; + GtkWidget *cancel; + + GtkWidget *main_layout; + GtkWidget *spinner; + GtkWidget *titlebar; + GtkWidget *title; + GtkWidget *stack; + + GList *pages; + GisPage *current_page; +}; +typedef struct _GisAssistantPrivate GisAssistantPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisAssistant, gis_assistant, GTK_TYPE_BOX) + +struct _GisAssistantPagePrivate +{ + GList *link; +}; + +static void +visible_child_changed (GisAssistant *assistant) +{ + g_signal_emit (assistant, signals[PAGE_CHANGED], 0); +} + +static void +widget_destroyed (GtkWidget *widget, + GisAssistant *assistant) +{ + GisPage *page = GIS_PAGE (widget); + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + + priv->pages = g_list_delete_link (priv->pages, page->assistant_priv->link); + if (page == priv->current_page) + priv->current_page = NULL; + + g_slice_free (GisAssistantPagePrivate, page->assistant_priv); + page->assistant_priv = NULL; +} + +static void +switch_to (GisAssistant *assistant, + GisPage *page) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + + g_return_if_fail (page != NULL); + + gtk_stack_set_visible_child (GTK_STACK (priv->stack), GTK_WIDGET (page)); +} + +static inline gboolean +should_show_page (GisPage *page) +{ + return gtk_widget_get_visible (GTK_WIDGET (page)); +} + +static GisPage * +find_next_page (GisPage *page) +{ + GList *l = page->assistant_priv->link->next; + + for (; l != NULL; l = l->next) + { + GisPage *page = GIS_PAGE (l->data); + + if (should_show_page (page)) + return page; + } + + return NULL; +} + +static void +switch_to_next_page (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + switch_to (assistant, find_next_page (priv->current_page)); +} + +static void +on_apply_done (GisPage *page, + gboolean valid, + gpointer user_data) +{ + GisAssistant *assistant = GIS_ASSISTANT (user_data); + + if (valid) + switch_to_next_page (assistant); +} + +void +gis_assistant_next_page (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + if (priv->current_page) + gis_page_apply_begin (priv->current_page, on_apply_done, assistant); + else + switch_to_next_page (assistant); +} + +static GisPage * +find_prev_page (GisPage *page) +{ + GList *l = page->assistant_priv->link->prev; + + for (; l != NULL; l = l->prev) + { + GisPage *page = GIS_PAGE (l->data); + + if (should_show_page (page)) + return page; + } + + return NULL; +} + +void +gis_assistant_previous_page (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + g_return_if_fail (priv->current_page != NULL); + switch_to (assistant, find_prev_page (priv->current_page)); +} + +static void +set_suggested_action_sensitive (GtkWidget *widget, + gboolean sensitive) +{ + gtk_widget_set_sensitive (widget, sensitive); +} + +static void +set_navigation_button (GisAssistant *assistant, + GtkWidget *widget) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + + gtk_widget_set_visible (priv->forward, (widget == priv->forward)); + gtk_widget_set_visible (priv->accept, (widget == priv->accept)); + gtk_widget_set_visible (priv->skip, (widget == priv->skip)); +} + +void +update_navigation_buttons (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + GisPage *page = priv->current_page; + GisAssistantPagePrivate *page_priv; + gboolean is_last_page; + + if (page == NULL) + return; + + page_priv = page->assistant_priv; + + is_last_page = (page_priv->link->next == NULL); + + if (is_last_page) + { + gtk_widget_hide (priv->back); + gtk_widget_hide (priv->forward); + gtk_widget_hide (priv->skip); + gtk_widget_hide (priv->cancel); + gtk_widget_hide (priv->accept); + /* FIXME: workaround for a GTK+ issue */ + gtk_widget_queue_resize (priv->titlebar); + } + else + { + gboolean is_first_page; + GtkWidget *next_widget; + + is_first_page = (page_priv->link->prev == NULL); + gtk_widget_set_visible (priv->back, !is_first_page); + + if (gis_page_get_needs_accept (page)) + next_widget = priv->accept; + else + next_widget = priv->forward; + + if (gis_page_get_complete (page)) { + set_suggested_action_sensitive (next_widget, TRUE); + set_navigation_button (assistant, next_widget); + } else if (gis_page_get_skippable (page)) { + set_navigation_button (assistant, priv->skip); + } else { + set_suggested_action_sensitive (next_widget, FALSE); + set_navigation_button (assistant, next_widget); + } + + if (gis_page_get_has_forward (page)) { + gtk_widget_hide (next_widget); + } + } +} + +static void +update_applying_state (GisAssistant *assistant) +{ + gboolean applying = FALSE; + gboolean is_first_page = FALSE; + + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + if (priv->current_page) + { + applying = gis_page_get_applying (priv->current_page); + is_first_page = priv->current_page->assistant_priv->link->prev == NULL; + } + gtk_widget_set_sensitive (priv->forward, !applying); + gtk_widget_set_visible (priv->back, !applying && !is_first_page); + gtk_widget_set_visible (priv->cancel, applying); + gtk_widget_set_visible (priv->spinner, applying); + + if (applying) + gtk_spinner_start (GTK_SPINNER (priv->spinner)); + else + gtk_spinner_stop (GTK_SPINNER (priv->spinner)); +} + +static void +update_titlebar (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + + gtk_label_set_label (GTK_LABEL (priv->title), + gis_assistant_get_title (assistant)); +} + +static void +page_notify (GisPage *page, + GParamSpec *pspec, + GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + + if (page != priv->current_page) + return; + + if (strcmp (pspec->name, "title") == 0) + { + g_object_notify_by_pspec (G_OBJECT (assistant), obj_props[PROP_TITLE]); + update_titlebar (assistant); + } + else if (strcmp (pspec->name, "applying") == 0) + { + update_applying_state (assistant); + } + else + { + update_navigation_buttons (assistant); + } +} + +void +gis_assistant_add_page (GisAssistant *assistant, + GisPage *page) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + GList *link; + + g_return_if_fail (page->assistant_priv == NULL); + + page->assistant_priv = g_slice_new0 (GisAssistantPagePrivate); + priv->pages = g_list_append (priv->pages, page); + link = page->assistant_priv->link = g_list_last (priv->pages); + + g_signal_connect (page, "destroy", G_CALLBACK (widget_destroyed), assistant); + g_signal_connect (page, "notify", G_CALLBACK (page_notify), assistant); + + gtk_container_add (GTK_CONTAINER (priv->stack), GTK_WIDGET (page)); + + if (priv->current_page && + priv->current_page->assistant_priv->link == link->prev) + update_navigation_buttons (assistant); +} + +GisPage * +gis_assistant_get_current_page (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + return priv->current_page; +} + +GList * +gis_assistant_get_all_pages (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + return priv->pages; +} + +static void +go_forward (GtkWidget *button, + GisAssistant *assistant) +{ + gis_assistant_next_page (assistant); +} + +static void +go_backward (GtkWidget *button, + GisAssistant *assistant) +{ + gis_assistant_previous_page (assistant); +} + +static void +do_cancel (GtkWidget *button, + GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + if (priv->current_page) + gis_page_apply_cancel (priv->current_page); +} + +const gchar * +gis_assistant_get_title (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + if (priv->current_page != NULL) + return gis_page_get_title (priv->current_page); + else + return ""; +} + +GtkWidget * +gis_assistant_get_titlebar (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + return priv->titlebar; +} + +static void +update_current_page (GisAssistant *assistant, + GisPage *page) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + + if (priv->current_page == page) + return; + + priv->current_page = page; + g_object_notify_by_pspec (G_OBJECT (assistant), obj_props[PROP_TITLE]); + + update_titlebar (assistant); + update_applying_state (assistant); + update_navigation_buttons (assistant); + + gtk_widget_grab_focus (priv->forward); + + if (page) + gis_page_shown (page); +} + +static void +current_page_changed (GObject *gobject, + GParamSpec *pspec, + gpointer user_data) +{ + GisAssistant *assistant = GIS_ASSISTANT (user_data); + GtkStack *stack = GTK_STACK (gobject); + GtkWidget *new_page = gtk_stack_get_visible_child (stack); + + update_current_page (assistant, GIS_PAGE (new_page)); +} + +void +gis_assistant_locale_changed (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + GList *l; + + gtk_button_set_label (GTK_BUTTON (priv->forward), _("_Next")); + gtk_button_set_label (GTK_BUTTON (priv->accept), _("_Accept")); + gtk_button_set_label (GTK_BUTTON (priv->skip), _("_Skip")); + gtk_button_set_label (GTK_BUTTON (priv->back), _("_Previous")); + gtk_button_set_label (GTK_BUTTON (priv->cancel), _("_Cancel")); + + for (l = priv->pages; l != NULL; l = l->next) + gis_page_locale_changed (l->data); + + update_titlebar (assistant); +} + +gboolean +gis_assistant_save_data (GisAssistant *assistant, + GError **error) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + GList *l; + + for (l = priv->pages; l != NULL; l = l->next) + { + if (!gis_page_save_data (l->data, error)) + return FALSE; + } + + return TRUE; +} + +static void +gis_assistant_init (GisAssistant *assistant) +{ + GisAssistantPrivate *priv = gis_assistant_get_instance_private (assistant); + + gtk_widget_init_template (GTK_WIDGET (assistant)); + + g_signal_connect (priv->stack, "notify::visible-child", + G_CALLBACK (current_page_changed), assistant); + + g_signal_connect (priv->forward, "clicked", G_CALLBACK (go_forward), assistant); + g_signal_connect (priv->accept, "clicked", G_CALLBACK (go_forward), assistant); + g_signal_connect (priv->skip, "clicked", G_CALLBACK (go_forward), assistant); + + g_signal_connect (priv->back, "clicked", G_CALLBACK (go_backward), assistant); + g_signal_connect (priv->cancel, "clicked", G_CALLBACK (do_cancel), assistant); + + gis_assistant_locale_changed (assistant); + update_applying_state (assistant); +} + +static void +gis_assistant_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GisAssistant *assistant = GIS_ASSISTANT (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, gis_assistant_get_title (assistant)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gis_assistant_class_init (GisAssistantClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-assistant.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, forward); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, accept); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, skip); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, back); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, cancel); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, main_layout); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, spinner); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, titlebar); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, title); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAssistant, stack); + + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), visible_child_changed); + + gobject_class->get_property = gis_assistant_get_property; + + obj_props[PROP_TITLE] = + g_param_spec_string ("title", + "", "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + + /** + * GisAssistant::page-changed: + * @assistant: the #GisAssistant + * + * The ::page-changed signal is emitted when the visible page + * changed. + */ + signals[PAGE_CHANGED] = + g_signal_new ("page-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); +} diff --git a/gnome-initial-setup/gis-assistant.gresource.xml b/gnome-initial-setup/gis-assistant.gresource.xml new file mode 100644 index 0000000..041cfb0 --- /dev/null +++ b/gnome-initial-setup/gis-assistant.gresource.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks">gis-assistant.ui</file> + <file preprocess="xml-stripblanks">gis-page-header.ui</file> + <file>gis-page-header.css</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/gis-assistant.h b/gnome-initial-setup/gis-assistant.h new file mode 100644 index 0000000..5e2dd9f --- /dev/null +++ b/gnome-initial-setup/gis-assistant.h @@ -0,0 +1,67 @@ +/* -*- 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> + */ + +#ifndef __GIS_ASSISTANT_H__ +#define __GIS_ASSISTANT_H__ + +#include "gis-page.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_ASSISTANT (gis_assistant_get_type ()) +#define GIS_ASSISTANT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_ASSISTANT, GisAssistant)) +#define GIS_ASSISTANT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_ASSISTANT, GisAssistantClass)) +#define GIS_IS_ASSISTANT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_ASSISTANT)) +#define GIS_IS_ASSISTANT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_ASSISTANT)) +#define GIS_ASSISTANT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_ASSISTANT, GisAssistantClass)) + +typedef struct _GisAssistant GisAssistant; +typedef struct _GisAssistantClass GisAssistantClass; + +struct _GisAssistant +{ + GtkBox parent; +}; + +struct _GisAssistantClass +{ + GtkBoxClass parent_class; +}; + +GType gis_assistant_get_type (void); + +void gis_assistant_add_page (GisAssistant *assistant, + GisPage *page); + +void gis_assistant_next_page (GisAssistant *assistant); +void gis_assistant_previous_page (GisAssistant *assistant); +GisPage * gis_assistant_get_current_page (GisAssistant *assistant); +GList * gis_assistant_get_all_pages (GisAssistant *assistant); +const gchar *gis_assistant_get_title (GisAssistant *assistant); +GtkWidget *gis_assistant_get_titlebar (GisAssistant *assistant); + +void gis_assistant_locale_changed (GisAssistant *assistant); +gboolean gis_assistant_save_data (GisAssistant *assistant, + GError **error); + +G_END_DECLS + +#endif /* __GIS_ASSISTANT_H__ */ diff --git a/gnome-initial-setup/gis-assistant.ui b/gnome-initial-setup/gis-assistant.ui new file mode 100644 index 0000000..936f829 --- /dev/null +++ b/gnome-initial-setup/gis-assistant.ui @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface domain="gtk30"> + <!-- interface-requires gtk+ 3.10 --> + <object class="GtkHeaderBar" id="titlebar"> + <property name="visible">True</property> + <child type="title"> + <object class="GtkLabel" id="title"> + <property name="visible">True</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="visible">True</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkButton" id="back"> + <property name="visible">True</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkLabel" id="placeholder"> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkSpinner" id="spinner"> + <property name="visible">True</property> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + <child> + <object class="GtkButton" id="skip"> + <property name="use-underline">True</property> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + <child> + <object class="GtkButton" id="forward"> + <property name="use-underline">True</property> + <property name="can-default">True</property> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + <child> + <object class="GtkButton" id="accept"> + <property name="use-underline">True</property> + <property name="can-default">True</property> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + </object> + + <object class="GtkSizeGroup" id="headerheight"> + <property name="mode">vertical</property> + <widgets> + <widget name="title"/> + <widget name="placeholder"/> + </widgets> + </object> + + <template class="GisAssistant" parent="GtkBox"> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="main_layout"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="transition-type">slide-left-right</property> + <property name="vexpand">True</property> + <property name="hexpand">True</property> + <property name="hhomogeneous">False</property> + <property name="vhomogeneous">False</property> + <signal name="notify::visible-child" handler="visible_child_changed" object="GisAssistant" swapped="yes"/> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/gis-driver.c b/gnome-initial-setup/gis-driver.c new file mode 100644 index 0000000..4ecf313 --- /dev/null +++ b/gnome-initial-setup/gis-driver.c @@ -0,0 +1,1031 @@ +/* -*- 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 <errno.h> +#include <locale.h> +#include <stdlib.h> +#include <webkit2/webkit2.h> + +#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 _GisDriverPrivate { + 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) */ + + GdkPixbuf *avatar; /* (owned) (nullable) */ + + GisDriverMode mode; + UmAccountMode account_mode; + gboolean small_screen; + + locale_t locale; + + const gchar *vendor_conf_file_path; + GKeyFile *vendor_conf_file; +}; +typedef struct _GisDriverPrivate GisDriverPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE(GisDriver, gis_driver, GTK_TYPE_APPLICATION) + +static void +gis_driver_dispose (GObject *object) +{ + GisDriver *driver = GIS_DRIVER (object); + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + g_clear_object (&priv->user_verifier); + g_clear_object (&priv->greeter); + g_clear_object (&priv->client); + + G_OBJECT_CLASS (gis_driver_parent_class)->dispose (object); +} + +static void +gis_driver_finalize (GObject *object) +{ + GisDriver *driver = GIS_DRIVER (object); + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + g_free (priv->lang_id); + g_free (priv->username); + g_free (priv->full_name); + g_free (priv->user_password); + + g_clear_object (&priv->avatar); + + g_clear_object (&priv->user_account); + g_clear_pointer (&priv->vendor_conf_file, g_key_file_free); + + g_clear_object (&priv->parent_account); + g_free (priv->parent_password); + + if (priv->locale != (locale_t) 0) + { + uselocale (LC_GLOBAL_LOCALE); + freelocale (priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + GtkWidget *child, *sw; + + child = g_object_ref (gtk_bin_get_child (GTK_BIN (priv->main_window))); + gtk_container_remove (GTK_CONTAINER (priv->main_window), child); + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_container_add (GTK_CONTAINER (priv->main_window), sw); + gtk_container_add (GTK_CONTAINER (sw), child); + g_object_unref (child); + + g_signal_connect_swapped (priv->assistant, + "page-changed", + G_CALLBACK (assistant_page_changed), + sw); + + gtk_window_set_titlebar (priv->main_window, + gis_assistant_get_titlebar (priv->assistant)); +} + +static void +rebuild_pages (GisDriver *driver) +{ + g_signal_emit (G_OBJECT (driver), signals[REBUILD_PAGES], 0); +} + +GisAssistant * +gis_driver_get_assistant (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + return priv->assistant; +} + +static void +gis_driver_real_locale_changed (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + GtkTextDirection direction; + + direction = gtk_get_locale_direction (); + gtk_widget_set_default_direction (direction); + + rebuild_pages (driver); + gis_assistant_locale_changed (priv->assistant); +} + +static void +gis_driver_locale_changed (GisDriver *driver) +{ + 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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + g_free (priv->lang_id); + priv->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 (priv->locale != (locale_t) 0 && priv->locale != LC_GLOBAL_LOCALE) + freelocale (priv->locale); + priv->locale = locale; + + gis_driver_locale_changed (driver); + } +} + +const gchar * +gis_driver_get_user_language (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + return priv->lang_id; +} + +void +gis_driver_set_username (GisDriver *driver, const gchar *username) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + g_free (priv->username); + priv->username = g_strdup (username); + g_object_notify (G_OBJECT (driver), "username"); +} + +const gchar * +gis_driver_get_username (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + return priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + 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 (priv->full_name, full_name) == 0) + return; + + g_free (priv->full_name); + priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + g_return_val_if_fail (GIS_IS_DRIVER (driver), NULL); + + return priv->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, + GdkPixbuf *avatar) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + g_return_if_fail (GIS_IS_DRIVER (driver)); + g_return_if_fail (avatar == NULL || GDK_IS_PIXBUF (avatar)); + + if (g_set_object (&priv->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 + */ +GdkPixbuf * +gis_driver_get_avatar (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + g_return_val_if_fail (GIS_IS_DRIVER (driver), NULL); + + return priv->avatar; +} + +void +gis_driver_set_user_permissions (GisDriver *driver, + ActUser *user, + const gchar *password) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + g_set_object (&priv->user_account, user); + g_free (priv->user_password); + priv->user_password = g_strdup (password); +} + +void +gis_driver_get_user_permissions (GisDriver *driver, + ActUser **user, + const gchar **password) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + if (user != NULL) + *user = priv->user_account; + + if (password != NULL) + *password = priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + g_set_object (&priv->parent_account, parent); + g_free (priv->parent_password); + priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + if (parent != NULL) + *parent = priv->parent_account; + if (password != NULL) + *password = priv->parent_password; +} + +void +gis_driver_set_account_mode (GisDriver *driver, + UmAccountMode mode) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + priv->account_mode = mode; +} + +UmAccountMode +gis_driver_get_account_mode (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + return priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + if (priv->parental_controls_enabled == parental_controls_enabled) + return; + + priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + return priv->parental_controls_enabled; +} + +gboolean +gis_driver_get_gdm_objects (GisDriver *driver, + GdmGreeter **greeter, + GdmUserVerifier **user_verifier) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + if (priv->greeter == NULL || priv->user_verifier == NULL) + return FALSE; + + *greeter = priv->greeter; + *user_verifier = priv->user_verifier; + + return TRUE; +} + +void +gis_driver_add_page (GisDriver *driver, + GisPage *page) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + gis_assistant_add_page (priv->assistant, page); +} + +void +gis_driver_hide_window (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + gtk_widget_hide (GTK_WIDGET (priv->main_window)); +} + +static gboolean +load_vendor_conf_file_at_path (GisDriver *driver, + const char *path) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + 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; + } + + priv->vendor_conf_file_path = path; + priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + 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, priv->vendor_conf_file_path, error->message); +} + +gboolean +gis_driver_conf_get_boolean (GisDriver *driver, + const gchar *group, + const gchar *key, + gboolean default_value) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + if (priv->vendor_conf_file) { + g_autoptr(GError) error = NULL; + gboolean new_value = g_key_file_get_boolean (priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + if (priv->vendor_conf_file) { + g_autoptr(GError) error = NULL; + GStrv new_value = g_key_file_get_string_list (priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + if (priv->vendor_conf_file) { + g_autoptr(GError) error = NULL; + gchar *new_value = g_key_file_get_string (priv->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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + return priv->mode; +} + +gboolean +gis_driver_is_small_screen (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + return priv->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); + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + switch ((GisDriverProperty) prop_id) + { + case PROP_MODE: + g_value_set_enum (value, priv->mode); + break; + case PROP_USERNAME: + g_value_set_string (value, priv->username); + break; + case PROP_SMALL_SCREEN: + g_value_set_boolean (value, priv->small_screen); + break; + case PROP_PARENTAL_CONTROLS_ENABLED: + g_value_set_boolean (value, priv->parental_controls_enabled); + break; + case PROP_FULL_NAME: + g_value_set_string (value, priv->full_name); + break; + case PROP_AVATAR: + g_value_set_object (value, priv->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); + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + switch ((GisDriverProperty) prop_id) + { + case PROP_MODE: + priv->mode = g_value_get_enum (value); + break; + case PROP_USERNAME: + g_free (priv->username); + priv->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); + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + G_APPLICATION_CLASS (gis_driver_parent_class)->activate (app); + + gtk_window_present (GTK_WINDOW (priv->main_window)); +} + +static void +set_small_screen_based_on_primary_monitor (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + GdkDisplay *default_display; + GdkMonitor *primary_monitor; + + default_display = gdk_display_get_default (); + if (default_display == NULL) + return; + + primary_monitor = gdk_display_get_primary_monitor (default_display); + if (primary_monitor == NULL) + return; + + priv->small_screen = monitor_is_small (primary_monitor); +} + +/* Recompute priv->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) { + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + GdkWindow *window; + GdkDisplay *default_display = gdk_display_get_default (); + GdkMonitor *active_monitor; + gboolean old_value = priv->small_screen; + + if (!gtk_widget_get_realized (GTK_WIDGET (priv->main_window))) + { + set_small_screen_based_on_primary_monitor (driver); + } + else + { + window = gtk_widget_get_window (GTK_WIDGET (priv->main_window)); + active_monitor = gdk_display_get_monitor_at_window (default_display, window); + priv->small_screen = monitor_is_small (active_monitor); + } + + if (priv->small_screen != old_value) + g_object_notify (G_OBJECT (driver), "small-screen"); +} + +static void +update_screen_size (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + GdkWindow *window; + GdkGeometry size_hints; + GtkWidget *sw; + + recompute_small_screen (driver); + + if (!gtk_widget_get_realized (GTK_WIDGET (priv->main_window))) + return; + + sw = gtk_bin_get_child (GTK_BIN (priv->main_window)); + window = gtk_widget_get_window (GTK_WIDGET (priv->main_window)); + + if (priv->small_screen) + { + if (window) + gdk_window_set_functions (window, + GDK_FUNC_ALL | GDK_FUNC_MINIMIZE | GDK_FUNC_CLOSE); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_window_set_geometry_hints (priv->main_window, NULL, NULL, 0); + gtk_window_set_resizable (priv->main_window, TRUE); + gtk_window_set_position (priv->main_window, GTK_WIN_POS_NONE); + + gtk_window_maximize (priv->main_window); + gtk_window_present (priv->main_window); + } + else + { + if (window) + gdk_window_set_functions (window, + GDK_FUNC_ALL | GDK_FUNC_MINIMIZE | GDK_FUNC_CLOSE | + GDK_FUNC_RESIZE | GDK_FUNC_MOVE | GDK_FUNC_MAXIMIZE); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_NEVER, + GTK_POLICY_NEVER); + + size_hints.min_width = size_hints.max_width = 1024; + size_hints.min_height = size_hints.max_height = 768; + size_hints.win_gravity = GDK_GRAVITY_CENTER; + + gtk_window_set_geometry_hints (priv->main_window, + NULL, + &size_hints, + GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE | GDK_HINT_WIN_GRAVITY); + gtk_window_set_resizable (priv->main_window, FALSE); + gtk_window_set_position (priv->main_window, GTK_WIN_POS_CENTER_ALWAYS); + + gtk_window_unmaximize (priv->main_window); + gtk_window_present (priv->main_window); + } +} + +static void +screen_size_changed (GdkScreen *screen, GisDriver *driver) +{ + update_screen_size (driver); +} + +static void +window_realize_cb (GtkWidget *widget, gpointer user_data) +{ + update_screen_size (GIS_DRIVER (user_data)); +} + +static void +connect_to_gdm (GisDriver *driver) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + g_autoptr(GError) error = NULL; + + priv->client = gdm_client_new (); + + priv->greeter = gdm_client_get_greeter_sync (priv->client, NULL, &error); + if (error == NULL) + priv->user_verifier = gdm_client_get_user_verifier_sync (priv->client, NULL, &error); + + if (error != NULL) { + g_warning ("Failed to open connection to GDM: %s", error->message); + g_clear_object (&priv->user_verifier); + g_clear_object (&priv->greeter); + g_clear_object (&priv->client); + } +} + +static void +gis_driver_startup (GApplication *app) +{ + GisDriver *driver = GIS_DRIVER (app); + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + 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 (priv->mode == GIS_DRIVER_MODE_NEW_USER) + connect_to_gdm (driver); + + priv->main_window = g_object_new (GTK_TYPE_APPLICATION_WINDOW, + "application", app, + "type", GTK_WINDOW_TOPLEVEL, + "icon-name", "preferences-system", + "deletable", FALSE, + NULL); + + g_signal_connect (priv->main_window, + "realize", + G_CALLBACK (window_realize_cb), + (gpointer)app); + + priv->assistant = g_object_new (GIS_TYPE_ASSISTANT, NULL); + gtk_container_add (GTK_CONTAINER (priv->main_window), GTK_WIDGET (priv->assistant)); + + gtk_widget_show (GTK_WIDGET (priv->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) +{ + GdkScreen *screen; + + screen = gdk_screen_get_default (); + + set_small_screen_based_on_primary_monitor (driver); + + load_vendor_conf_file (driver); + + if (screen != NULL) + g_signal_connect (screen, "size-changed", + G_CALLBACK (screen_size_changed), 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; + klass->locale_changed = gis_driver_real_locale_changed; + + signals[REBUILD_PAGES] = + g_signal_new ("rebuild-pages", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GisDriverClass, rebuild_pages), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[LOCALE_CHANGED] = + g_signal_new ("locale-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GisDriverClass, locale_changed), + 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_PIXBUF, + 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) +{ + GisDriverPrivate *priv = gis_driver_get_instance_private (driver); + + 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 (priv->assistant, error); +} + +GisDriver * +gis_driver_new (GisDriverMode mode) +{ + return g_object_new (GIS_TYPE_DRIVER, + "application-id", "org.gnome.InitialSetup", + "mode", mode, + NULL); +} diff --git a/gnome-initial-setup/gis-driver.h b/gnome-initial-setup/gis-driver.h new file mode 100644 index 0000000..a8f1922 --- /dev/null +++ b/gnome-initial-setup/gis-driver.h @@ -0,0 +1,148 @@ +/* -*- 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> + */ + +#ifndef __GIS_DRIVER_H__ +#define __GIS_DRIVER_H__ + +#include "gis-assistant.h" +#include "gis-page.h" +#include <act/act-user-manager.h> +#include <gdm/gdm-client.h> + +G_BEGIN_DECLS + +#define GIS_TYPE_DRIVER (gis_driver_get_type ()) +#define GIS_DRIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_DRIVER, GisDriver)) +#define GIS_DRIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_DRIVER, GisDriverClass)) +#define GIS_IS_DRIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_DRIVER)) +#define GIS_IS_DRIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_DRIVER)) +#define GIS_DRIVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_DRIVER, GisDriverClass)) + +typedef struct _GisDriver GisDriver; +typedef struct _GisDriverClass GisDriverClass; + +typedef enum { + UM_LOCAL, + UM_ENTERPRISE, + NUM_MODES, +} UmAccountMode; + +struct _GisDriver +{ + GtkApplication parent; +}; + +struct _GisDriverClass +{ + GtkApplicationClass parent_class; + + void (* rebuild_pages) (GisDriver *driver); + void (* locale_changed) (GisDriver *driver); +}; + +typedef enum { + GIS_DRIVER_MODE_NEW_USER, + GIS_DRIVER_MODE_EXISTING_USER, +} GisDriverMode; + +GType gis_driver_get_type (void); + +GisAssistant *gis_driver_get_assistant (GisDriver *driver); + +void gis_driver_set_user_permissions (GisDriver *driver, + ActUser *user, + const gchar *password); + +void gis_driver_get_user_permissions (GisDriver *driver, + ActUser **user, + const gchar **password); + +void gis_driver_set_parent_permissions (GisDriver *driver, + ActUser *parent, + const gchar *password); + +void gis_driver_get_parent_permissions (GisDriver *driver, + ActUser **parent, + const gchar **password); + +void gis_driver_set_account_mode (GisDriver *driver, + UmAccountMode mode); + +UmAccountMode gis_driver_get_account_mode (GisDriver *driver); + +void gis_driver_set_parental_controls_enabled (GisDriver *driver, + gboolean parental_controls_enabled); + +gboolean gis_driver_get_parental_controls_enabled (GisDriver *driver); + +void gis_driver_set_user_language (GisDriver *driver, + const gchar *lang_id, + gboolean update_locale); + +const gchar *gis_driver_get_user_language (GisDriver *driver); + +void gis_driver_set_username (GisDriver *driver, + const gchar *username); +const gchar *gis_driver_get_username (GisDriver *driver); + +void gis_driver_set_full_name (GisDriver *driver, + const gchar *full_name); +const gchar *gis_driver_get_full_name (GisDriver *driver); + +void gis_driver_set_avatar (GisDriver *driver, + GdkPixbuf *avatar); +GdkPixbuf *gis_driver_get_avatar (GisDriver *driver); + +gboolean gis_driver_get_gdm_objects (GisDriver *driver, + GdmGreeter **greeter, + GdmUserVerifier **user_verifier); + +GisDriverMode gis_driver_get_mode (GisDriver *driver); + +gboolean gis_driver_is_small_screen (GisDriver *driver); + +void gis_driver_add_page (GisDriver *driver, + GisPage *page); + +void gis_driver_hide_window (GisDriver *driver); + +gboolean gis_driver_save_data (GisDriver *driver, + GError **error); + +gboolean gis_driver_conf_get_boolean (GisDriver *driver, + const gchar *group, + const gchar *key, + gboolean default_value); + +GStrv gis_driver_conf_get_string_list (GisDriver *driver, + const gchar *group, + const gchar *key, + gsize *out_length); + +gchar *gis_driver_conf_get_string (GisDriver *driver, + const gchar *group, + const gchar *key); + +GisDriver *gis_driver_new (GisDriverMode mode); + +G_END_DECLS + +#endif /* __GIS_DRIVER_H__ */ diff --git a/gnome-initial-setup/gis-keyring.c b/gnome-initial-setup/gis-keyring.c new file mode 100644 index 0000000..7035e6f --- /dev/null +++ b/gnome-initial-setup/gis-keyring.c @@ -0,0 +1,104 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2014 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: + * Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <string.h> + +#include <gio/gio.h> + +#include "gis-keyring.h" + +#include <libsecret/secret.h> + +#define DUMMY_PWD "gis" + +/* We never want to see a keyring dialog, but we need to make + * sure a keyring is present. + * + * To achieve this, install a prompter for gnome-keyring that + * never shows any UI, and create a keyring, if one does not + * exist yet. + */ + +void +gis_ensure_login_keyring () +{ + g_autoptr(GSubprocess) subprocess = NULL; + g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autoptr(GError) error = NULL; + + g_debug ("launching gnome-keyring-daemon --unlock"); + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_SILENCE); + subprocess = g_subprocess_launcher_spawn (launcher, &error, "gnome-keyring-daemon", "--unlock", NULL); + if (subprocess == NULL) { + g_warning ("Failed to spawn gnome-keyring-daemon --unlock: %s", error->message); + return; + } + + if (!g_subprocess_communicate_utf8 (subprocess, DUMMY_PWD, NULL, NULL, NULL, &error)) { + g_warning ("Failed to communicate with gnome-keyring-daemon: %s", error->message); + return; + } +} + +void +gis_update_login_keyring_password (const gchar *new_) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(SecretService) service = NULL; + g_autoptr(SecretValue) old_secret = NULL; + g_autoptr(SecretValue) new_secret = NULL; + g_autoptr(GError) error = NULL; + + service = secret_service_get_sync (SECRET_SERVICE_OPEN_SESSION, NULL, &error); + if (service == NULL) { + g_warning ("Failed to get secret service: %s", error->message); + return; + } + + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (bus == NULL) { + g_warning ("Failed to get session bus: %s", error->message); + return; + } + + old_secret = secret_value_new (DUMMY_PWD, strlen (DUMMY_PWD), "text/plain"); + new_secret = secret_value_new (new_, strlen (new_), "text/plain"); + + g_dbus_connection_call_sync (bus, + "org.gnome.keyring", + "/org/freedesktop/secrets", + "org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface", + "ChangeWithMasterPassword", + g_variant_new ("(o@(oayays)@(oayays))", + "/org/freedesktop/secrets/collection/login", + secret_service_encode_dbus_secret (service, old_secret), + secret_service_encode_dbus_secret (service, new_secret)), + NULL, + 0, + G_MAXINT, + NULL, &error); + + if (error != NULL) { + g_warning ("Failed to change keyring password: %s", error->message); + } +} diff --git a/gnome-initial-setup/gis-keyring.h b/gnome-initial-setup/gis-keyring.h new file mode 100644 index 0000000..764f1e6 --- /dev/null +++ b/gnome-initial-setup/gis-keyring.h @@ -0,0 +1,35 @@ + +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2014 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: + * Matthias Clasen <mclasen@redhat.com> + */ + +#ifndef __GIS_KEYRING_H__ +#define __GIS_KEYRING_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +void gis_ensure_login_keyring (); +void gis_update_login_keyring_password (const gchar *new_); + +G_END_DECLS + +#endif /* __GIS_KEYRING_H__ */ diff --git a/gnome-initial-setup/gis-page-header.c b/gnome-initial-setup/gis-page-header.c new file mode 100644 index 0000000..8da1ee4 --- /dev/null +++ b/gnome-initial-setup/gis-page-header.c @@ -0,0 +1,203 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* -*- encoding: utf8 -*- */ +/* + * Copyright (C) 2019 Purism SPC + * + * 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: + * Adrien Plazas <kekun.plazas@laposte.net> + */ + +#include "config.h" + +#include <glib/gi18n.h> + +#include "gis-page-header.h" + +enum { + PROP_0, + PROP_TITLE, + PROP_SUBTITLE, + PROP_ICON_NAME, + PROP_PIXBUF, + PROP_SHOW_ICON, + PROP_LAST, +}; + +static GParamSpec *obj_props[PROP_LAST]; + +struct _GisPageHeader +{ + GtkBox parent; + + GtkWidget *box; + GtkWidget *icon; + GtkWidget *subtitle; + GtkWidget *title; +}; + +G_DEFINE_TYPE (GisPageHeader, gis_page_header, GTK_TYPE_BOX) + +static gboolean +is_valid_string (const gchar *s) +{ + return s != NULL && g_strcmp0 (s, "") != 0; +} + +static void +update_box_visibility (GisPageHeader *header) +{ + gtk_widget_set_visible (header->box, gtk_widget_get_visible (header->subtitle) || + gtk_widget_get_visible (header->title)); +} + +static void +gis_page_header_init (GisPageHeader *header) +{ + gtk_widget_init_template (GTK_WIDGET (header)); + + g_signal_connect_swapped (header->subtitle, "notify::visible", + G_CALLBACK(update_box_visibility), header); + g_signal_connect_swapped (header->title, "notify::visible", + G_CALLBACK(update_box_visibility), header); +} + +static void +gis_page_header_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GisPageHeader *header = GIS_PAGE_HEADER (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (header->title))); + break; + + case PROP_SUBTITLE: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (header->subtitle))); + break; + + case PROP_ICON_NAME: + g_object_get_property (G_OBJECT (header->icon), "icon-name", value); + break; + + case PROP_PIXBUF: + g_object_get_property (G_OBJECT (header->icon), "pixbuf", value); + break; + + case PROP_SHOW_ICON: + g_value_set_boolean (value, gtk_widget_get_visible (header->icon)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gis_page_header_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GisPageHeader *header = GIS_PAGE_HEADER (object); + + switch (prop_id) + { + case PROP_TITLE: + gtk_label_set_label (GTK_LABEL (header->title), g_value_get_string (value)); + gtk_widget_set_visible (header->title, is_valid_string (g_value_get_string (value))); + break; + + case PROP_SUBTITLE: + gtk_label_set_label (GTK_LABEL (header->subtitle), g_value_get_string (value)); + gtk_widget_set_visible (header->subtitle, is_valid_string (g_value_get_string (value))); + break; + + case PROP_ICON_NAME: + g_object_set_property (G_OBJECT (header->icon), "icon-name", value); + break; + + case PROP_PIXBUF: + g_object_set_property (G_OBJECT (header->icon), "pixbuf", value); + break; + + case PROP_SHOW_ICON: + gtk_widget_set_visible (header->icon, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gis_page_header_class_init (GisPageHeaderClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-page-header.ui"); + + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisPageHeader, box); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisPageHeader, icon); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisPageHeader, subtitle); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisPageHeader, title); + + gobject_class->get_property = gis_page_header_get_property; + gobject_class->set_property = gis_page_header_set_property; + + obj_props[PROP_TITLE] = + g_param_spec_string ("title", + "", "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_props[PROP_SUBTITLE] = + g_param_spec_string ("subtitle", + "", "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_props[PROP_ICON_NAME] = + g_param_spec_string ("icon-name", + "", "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_props[PROP_PIXBUF] = + g_param_spec_object ("pixbuf", + "", "", + GDK_TYPE_PIXBUF, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_props[PROP_SHOW_ICON] = + g_param_spec_boolean ("show-icon", + "", "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); + + g_autoptr(GtkCssProvider) provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/initial-setup/gis-page-header.css"); + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} diff --git a/gnome-initial-setup/gis-page-header.css b/gnome-initial-setup/gis-page-header.css new file mode 100644 index 0000000..ce02cc8 --- /dev/null +++ b/gnome-initial-setup/gis-page-header.css @@ -0,0 +1,13 @@ +/* Styles borrowed from GTK 4 + * https://gitlab.gnome.org/GNOME/gtk/blob/672d7f679adf543785042ab45d7e59688103464c/gtk/theme/Adwaita/_common.scss#L287-327 + */ +.large-title { + font-weight: 300; + font-size: 24pt; + letter-spacing: 0.2rem; +} + +.title-1 { + font-weight: 800; + font-size: 20pt; +} diff --git a/gnome-initial-setup/gis-page-header.h b/gnome-initial-setup/gis-page-header.h new file mode 100644 index 0000000..2ae6978 --- /dev/null +++ b/gnome-initial-setup/gis-page-header.h @@ -0,0 +1,36 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* -*- encoding: utf8 -*- */ +/* + * Copyright (C) 2019 Purism SPC + * + * 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: + * Adrien Plazas <kekun.plazas@laposte.net> + */ + +#ifndef __GIS_PAGE_HEADER_H__ +#define __GIS_PAGE_HEADER_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GIS_TYPE_PAGE_HEADER (gis_page_header_get_type ()) + +G_DECLARE_FINAL_TYPE (GisPageHeader, gis_page_header, GIS, PAGE_HEADER, GtkBox) + +G_END_DECLS + +#endif /* __GIS_PAGE_HEADER_H__ */ diff --git a/gnome-initial-setup/gis-page-header.ui b/gnome-initial-setup/gis-page-header.ui new file mode 100644 index 0000000..e5e1795 --- /dev/null +++ b/gnome-initial-setup/gis-page-header.ui @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface domain="gtk30"> + <!-- interface-requires gtk+ 3.10 --> + <template class="GisPageHeader" parent="GtkBox"> + <property name="orientation">vertical</property> + <property name="spacing">18</property> + <child> + <object class="GtkImage" id="icon"> + <property name="can_focus">False</property> + <property name="pixel_size">96</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkBox" id="box"> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="title"> + <property name="can_focus">False</property> + <property name="justify">center</property> + <property name="max_width_chars">65</property> + <property name="wrap">True</property> + <style> + <class name="title-1"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="subtitle"> + <property name="can_focus">False</property> + <property name="justify">center</property> + <property name="max_width_chars">65</property> + <property name="wrap">True</property> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/gis-page.c b/gnome-initial-setup/gis-page.c new file mode 100644 index 0000000..25729d0 --- /dev/null +++ b/gnome-initial-setup/gis-page.c @@ -0,0 +1,426 @@ +/* -*- 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 <glib-object.h> + +#include "gis-page.h" + +struct _GisPagePrivate +{ + char *title; + + gboolean applying; + GCancellable *apply_cancel; + GisPageApplyCallback apply_cb; + gpointer apply_data; + + guint complete : 1; + guint skippable : 1; + guint needs_accept : 1; + guint has_forward : 1; + guint padding : 5; +}; +typedef struct _GisPagePrivate GisPagePrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GisPage, gis_page, GTK_TYPE_BIN); + +enum +{ + PROP_0, + PROP_DRIVER, + PROP_TITLE, + PROP_COMPLETE, + PROP_SKIPPABLE, + PROP_NEEDS_ACCEPT, + PROP_APPLYING, + PROP_SMALL_SCREEN, + PROP_HAS_FORWARD, + PROP_LAST, +}; + +static GParamSpec *obj_props[PROP_LAST]; + +static void +gis_page_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GisPage *page = GIS_PAGE (object); + GisPagePrivate *priv = gis_page_get_instance_private (page); + switch (prop_id) + { + case PROP_DRIVER: + g_value_set_object (value, page->driver); + break; + case PROP_TITLE: + g_value_set_string (value, priv->title); + break; + case PROP_COMPLETE: + g_value_set_boolean (value, priv->complete); + break; + case PROP_SKIPPABLE: + g_value_set_boolean (value, priv->skippable); + break; + case PROP_NEEDS_ACCEPT: + g_value_set_boolean (value, priv->needs_accept); + break; + case PROP_HAS_FORWARD: + g_value_set_boolean (value, priv->has_forward); + break; + case PROP_APPLYING: + g_value_set_boolean (value, gis_page_get_applying (page)); + break; + case PROP_SMALL_SCREEN: + if (page->driver) + g_object_get_property (G_OBJECT (page->driver), "small-screen", value); + else + g_value_set_boolean (value, FALSE); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +small_screen_changed (GisPage *page) +{ + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_SMALL_SCREEN]); +} + +static void +gis_page_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GisPage *page = GIS_PAGE (object); + GisPagePrivate *priv = gis_page_get_instance_private (page); + switch (prop_id) + { + case PROP_DRIVER: + page->driver = g_value_dup_object (value); + g_signal_connect_swapped (page->driver, "notify::small-screen", + G_CALLBACK (small_screen_changed), page); + small_screen_changed (page); + break; + case PROP_TITLE: + gis_page_set_title (page, (char *) g_value_get_string (value)); + break; + case PROP_SKIPPABLE: + priv->skippable = g_value_get_boolean (value); + break; + case PROP_NEEDS_ACCEPT: + priv->needs_accept = g_value_get_boolean (value); + break; + case PROP_HAS_FORWARD: + priv->has_forward = g_value_get_boolean (value); + break; + case PROP_COMPLETE: + priv->complete = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gis_page_finalize (GObject *object) +{ + GisPage *page = GIS_PAGE (object); + GisPagePrivate *priv = gis_page_get_instance_private (page); + + g_free (priv->title); + g_assert (!priv->applying); + g_assert (priv->apply_cb == NULL); + g_assert (priv->apply_cancel == NULL); + + G_OBJECT_CLASS (gis_page_parent_class)->finalize (object); +} + +static void +gis_page_dispose (GObject *object) +{ + GisPage *page = GIS_PAGE (object); + GisPagePrivate *priv = gis_page_get_instance_private (page); + + if (priv->apply_cancel) + g_cancellable_cancel (priv->apply_cancel); + + if (page->driver) + g_signal_handlers_disconnect_by_func (page->driver, small_screen_changed, page); + g_clear_object (&page->driver); + + G_OBJECT_CLASS (gis_page_parent_class)->dispose (object); +} + +static void +gis_page_constructed (GObject *object) +{ + GisPage *page = GIS_PAGE (object); + + gis_page_locale_changed (page); + + G_OBJECT_CLASS (gis_page_parent_class)->constructed (object); + +} + +static gboolean +gis_page_real_apply (GisPage *page, + GCancellable *cancellable) +{ + return FALSE; +} + +static void +gis_page_class_init (GisPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gis_page_constructed; + object_class->dispose = gis_page_dispose; + object_class->finalize = gis_page_finalize; + object_class->get_property = gis_page_get_property; + object_class->set_property = gis_page_set_property; + + klass->apply = gis_page_real_apply; + + obj_props[PROP_DRIVER] = + g_param_spec_object ("driver", "", "", GIS_TYPE_DRIVER, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + obj_props[PROP_TITLE] = + g_param_spec_string ("title", "", "", "", + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + obj_props[PROP_COMPLETE] = + g_param_spec_boolean ("complete", "", "", FALSE, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + obj_props[PROP_SKIPPABLE] = + g_param_spec_boolean ("skippable", "", "", FALSE, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + obj_props[PROP_NEEDS_ACCEPT] = + g_param_spec_boolean ("needs-accept", "", "", FALSE, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + obj_props[PROP_HAS_FORWARD] = + g_param_spec_boolean ("has-forward", "", "", FALSE, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + obj_props[PROP_APPLYING] = + g_param_spec_boolean ("applying", "", "", FALSE, + G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + obj_props[PROP_SMALL_SCREEN] = + g_param_spec_boolean ("small-screen", "", "", FALSE, + G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + + g_object_class_install_properties (object_class, PROP_LAST, obj_props); +} + +static void +gis_page_init (GisPage *page) +{ + gtk_widget_set_margin_start (GTK_WIDGET (page), 12); + gtk_widget_set_margin_top (GTK_WIDGET (page), 12); + gtk_widget_set_margin_bottom (GTK_WIDGET (page), 12); + gtk_widget_set_margin_end (GTK_WIDGET (page), 12); +} + +char * +gis_page_get_title (GisPage *page) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + if (priv->title != NULL) + return priv->title; + else + return ""; +} + +void +gis_page_set_title (GisPage *page, char *title) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + g_clear_pointer (&priv->title, g_free); + priv->title = g_strdup (title); + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_TITLE]); +} + +gboolean +gis_page_get_complete (GisPage *page) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + return priv->complete; +} + +void +gis_page_set_complete (GisPage *page, gboolean complete) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + priv->complete = complete; + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_COMPLETE]); +} + +gboolean +gis_page_get_skippable (GisPage *page) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + return priv->skippable; +} + +void +gis_page_set_skippable (GisPage *page, gboolean skippable) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + priv->skippable = skippable; + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_SKIPPABLE]); +} + +gboolean +gis_page_get_needs_accept (GisPage *page) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + return priv->needs_accept; +} + +void +gis_page_set_needs_accept (GisPage *page, gboolean needs_accept) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + priv->needs_accept = needs_accept; + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_NEEDS_ACCEPT]); +} + +gboolean +gis_page_get_has_forward (GisPage *page) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + return priv->has_forward; +} + +void +gis_page_set_has_forward (GisPage *page, gboolean has_forward) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + if (priv->has_forward != has_forward) + { + priv->has_forward = has_forward; + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_HAS_FORWARD]); + } +} + +void +gis_page_locale_changed (GisPage *page) +{ + if (GIS_PAGE_GET_CLASS (page)->locale_changed) + return GIS_PAGE_GET_CLASS (page)->locale_changed (page); +} + +void +gis_page_apply_begin (GisPage *page, + GisPageApplyCallback callback, + gpointer user_data) +{ + GisPageClass *klass; + GisPagePrivate *priv = gis_page_get_instance_private (page); + + g_return_if_fail (GIS_IS_PAGE (page)); + g_return_if_fail (priv->applying == FALSE); + + klass = GIS_PAGE_GET_CLASS (page); + + priv->apply_cb = callback; + priv->apply_data = user_data; + priv->apply_cancel = g_cancellable_new (); + priv->applying = TRUE; + + if (!klass->apply (page, priv->apply_cancel)) + { + /* Shortcut case where we don't want apply, to avoid flicker */ + gis_page_apply_complete (page, TRUE); + } + + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_APPLYING]); +} + +void +gis_page_apply_complete (GisPage *page, + gboolean valid) +{ + GisPageApplyCallback callback; + gpointer user_data; + GisPagePrivate *priv = gis_page_get_instance_private (page); + + g_return_if_fail (GIS_IS_PAGE (page)); + g_return_if_fail (priv->applying == TRUE); + + callback = priv->apply_cb; + priv->apply_cb = NULL; + user_data = priv->apply_data; + priv->apply_data = NULL; + + g_clear_object (&priv->apply_cancel); + priv->applying = FALSE; + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_APPLYING]); + + if (callback) + (callback) (page, valid, user_data); +} + +gboolean +gis_page_get_applying (GisPage *page) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + return priv->applying; +} + +void +gis_page_apply_cancel (GisPage *page) +{ + GisPagePrivate *priv = gis_page_get_instance_private (page); + g_cancellable_cancel (priv->apply_cancel); +} + +gboolean +gis_page_save_data (GisPage *page, + GError **error) +{ + if (GIS_PAGE_GET_CLASS (page)->save_data == NULL) + { + /* Not implemented, which presumably means the page has nothing to save. */ + return TRUE; + } + + return GIS_PAGE_GET_CLASS (page)->save_data (page, error); +} + +void +gis_page_shown (GisPage *page) +{ + if (GIS_PAGE_GET_CLASS (page)->shown) + GIS_PAGE_GET_CLASS (page)->shown (page); +} + +void +gis_page_skip (GisPage *page) +{ + if (GIS_PAGE_GET_CLASS (page)->skip) + GIS_PAGE_GET_CLASS (page)->skip (page); +} diff --git a/gnome-initial-setup/gis-page.h b/gnome-initial-setup/gis-page.h new file mode 100644 index 0000000..56aa0f8 --- /dev/null +++ b/gnome-initial-setup/gis-page.h @@ -0,0 +1,93 @@ +/* -*- 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> + */ + +#ifndef __GIS_PAGE_H__ +#define __GIS_PAGE_H__ + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_PAGE (gis_page_get_type ()) +#define GIS_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_PAGE, GisPage)) +#define GIS_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_PAGE, GisPageClass)) +#define GIS_IS_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_PAGE)) +#define GIS_IS_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_PAGE)) +#define GIS_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_PAGE, GisPageClass)) + +typedef struct _GisPage GisPage; +typedef struct _GisPageClass GisPageClass; +typedef struct _GisAssistantPagePrivate GisAssistantPagePrivate; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GisPage, g_object_unref) + +typedef void (* GisPageApplyCallback) (GisPage *page, + gboolean valid, + gpointer user_data); + +struct _GisPage +{ + GtkBin parent; + + GisDriver *driver; + + GisAssistantPagePrivate *assistant_priv; +}; + +struct _GisPageClass +{ + GtkBinClass parent_class; + char *page_id; + + void (*locale_changed) (GisPage *page); + gboolean (*apply) (GisPage *page, + GCancellable *cancellable); + gboolean (*save_data) (GisPage *page, + GError **error); + void (*shown) (GisPage *page); + void (*skip) (GisPage *page); +}; + +GType gis_page_get_type (void); + +char * gis_page_get_title (GisPage *page); +void gis_page_set_title (GisPage *page, char *title); +gboolean gis_page_get_complete (GisPage *page); +void gis_page_set_complete (GisPage *page, gboolean complete); +gboolean gis_page_get_skippable (GisPage *page); +void gis_page_set_skippable (GisPage *page, gboolean skippable); +gboolean gis_page_get_needs_accept (GisPage *page); +void gis_page_set_needs_accept (GisPage *page, gboolean needs_accept); +gboolean gis_page_get_has_forward (GisPage *page); +void gis_page_set_has_forward (GisPage *page, gboolean has_forward); +void gis_page_locale_changed (GisPage *page); +void gis_page_apply_begin (GisPage *page, GisPageApplyCallback callback, gpointer user_data); +void gis_page_apply_cancel (GisPage *page); +void gis_page_apply_complete (GisPage *page, gboolean valid); +gboolean gis_page_get_applying (GisPage *page); +gboolean gis_page_save_data (GisPage *page, + GError **error); +void gis_page_shown (GisPage *page); +void gis_page_skip (GisPage *page); + +G_END_DECLS + +#endif /* __GIS_PAGE_H__ */ diff --git a/gnome-initial-setup/gnome-initial-setup-copy-worker.c b/gnome-initial-setup/gnome-initial-setup-copy-worker.c new file mode 100644 index 0000000..df07155 --- /dev/null +++ b/gnome-initial-setup/gnome-initial-setup-copy-worker.c @@ -0,0 +1,99 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Copies settings installed from gnome-initial-setup and + * sticks them in the user's profile */ + +#include <pwd.h> +#include <string.h> +#include <gio/gio.h> +#include <stdlib.h> + +static char * +get_gnome_initial_setup_home_dir (void) +{ + struct passwd pw, *pwp; + char buf[4096]; + + getpwnam_r ("gnome-initial-setup", &pw, buf, sizeof (buf), &pwp); + if (pwp != NULL) + return g_strdup (pwp->pw_dir); + else + return NULL; +} + +static gboolean +file_is_ours (GFile *file) +{ + GFileInfo *info; + uid_t uid; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_UNIX_UID, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + NULL); + if (!info) + return FALSE; + + uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID); + g_object_unref (info); + + return uid == geteuid (); +} + +static void +move_file_from_homedir (GFile *src_base, + GFile *dest_base, + const gchar *path) +{ + GFile *dest = g_file_get_child (dest_base, path); + GFile *dest_parent = g_file_get_parent (dest); + GFile *src = g_file_get_child (src_base, path); + + GError *error = NULL; + + g_file_make_directory_with_parents (dest_parent, NULL, NULL); + + if (!g_file_move (src, dest, G_FILE_COPY_NONE, + NULL, NULL, NULL, &error)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + g_warning ("Unable to move %s to %s: %s", + g_file_get_path (src), + g_file_get_path (dest), + error->message); + } + } +} + +int +main (int argc, + char **argv) +{ + GFile *src; + GFile *dest; + char *initial_setup_homedir; + + initial_setup_homedir = get_gnome_initial_setup_home_dir (); + if (initial_setup_homedir == NULL) + exit (EXIT_SUCCESS); + + src = g_file_new_for_path (initial_setup_homedir); + + if (!g_file_query_exists (src, NULL) || + !file_is_ours (src)) + exit (EXIT_SUCCESS); + + dest = g_file_new_for_path (g_get_home_dir ()); + +#define FILE(path) \ + move_file_from_homedir (src, dest, path); + + FILE (".config/gnome-initial-setup-done"); + FILE (".config/run-welcome-tour"); + FILE (".config/dconf/user"); + FILE (".config/goa-1.0/accounts.conf"); + FILE (".config/monitors.xml"); + FILE (".local/share/keyrings/login.keyring"); + + return EXIT_SUCCESS; +} diff --git a/gnome-initial-setup/gnome-initial-setup.c b/gnome-initial-setup/gnome-initial-setup.c new file mode 100644 index 0000000..0ab987a --- /dev/null +++ b/gnome-initial-setup/gnome-initial-setup.c @@ -0,0 +1,370 @@ +/* -*- 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 <pwd.h> +#include <unistd.h> +#include <stdlib.h> +#include <glib/gi18n.h> + +#ifdef HAVE_CHEESE +#include <cheese-gtk.h> +#endif + +#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/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" +#define VENDOR_RUN_WELCOME_TOUR_KEY "run_welcome_tour" + +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 (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; + gtk_widget_destroy (GTK_WIDGET (l->data)); + } +} + +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 ((GDestroyNotify) gtk_widget_destroy); + + 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"); + + 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); + +#ifdef HAVE_CHEESE + cheese_gtk_init (NULL, NULL); +#endif + + gtk_init (&argc, &argv); + + 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 ((GDestroyNotify) gtk_widget_destroy); + 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); + + /* 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 *welcome_file = NULL; + g_autofree gchar *done_file = NULL; + g_autoptr(GError) error = NULL; + + if (gis_driver_conf_get_boolean (driver, + VENDOR_PAGES_GROUP, + VENDOR_RUN_WELCOME_TOUR_KEY, + TRUE)) { + welcome_file = g_build_filename (g_get_user_config_dir (), "run-welcome-tour", NULL); + if (!g_file_set_contents (welcome_file, "yes", -1, &error)) { + g_warning ("Unable to create %s: %s", welcome_file, error->message); + g_clear_error (&error); + } + } + + 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); +} diff --git a/gnome-initial-setup/gnome-initial-setup.h b/gnome-initial-setup/gnome-initial-setup.h new file mode 100644 index 0000000..55fcc7d --- /dev/null +++ b/gnome-initial-setup/gnome-initial-setup.h @@ -0,0 +1,42 @@ +/* -*- 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> + */ + +#ifndef __GNOME_INITIAL_SETUP_H__ +#define __GNOME_INITIAL_SETUP_H__ + +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <glib/gi18n.h> + +typedef struct _GisDriver GisDriver; +typedef struct _GisAssistant GisAssistant; +typedef struct _GisPage GisPage; + +#include "gis-driver.h" +#include "gis-assistant.h" +#include "gis-page.h" +#include "gis-keyring.h" + +void gis_ensure_stamp_files (GisDriver *driver); +gboolean gis_get_mock_mode (void); + +#endif /* __GNOME_INITIAL_SETUP_H__ */ + diff --git a/gnome-initial-setup/meson.build b/gnome-initial-setup/meson.build new file mode 100644 index 0000000..75c4483 --- /dev/null +++ b/gnome-initial-setup/meson.build @@ -0,0 +1,76 @@ +sources = [] + +subdir('pages') + +resources = gnome.compile_resources( + 'gis-assistant-resources', + files('gis-assistant.gresource.xml'), + c_name: 'gis_assistant' +) + +sources += [ + resources, + 'cc-common-language.c', + 'gnome-initial-setup.c', + 'gis-assistant.c', + 'gis-page.c', + 'gis-page-header.c', + 'gis-driver.c', + 'gis-keyring.c', + 'gnome-initial-setup.h', + 'gis-assistant.h', + 'gis-page.h', + 'gis-page-header.h', + 'gis-driver.h', + 'gis-keyring.h' +] + +dependencies = [ + dependency ('libnm', version: '>= 1.2'), + dependency ('libnma', version: '>= 1.0'), + dependency ('polkit-gobject-1', version: '>= 0.103'), + dependency ('accountsservice'), + dependency ('gnome-desktop-3.0', version: '>= 3.7.5'), + dependency ('gsettings-desktop-schemas', version: '>= 3.37.1'), + dependency ('fontconfig'), + dependency ('gweather-3.0'), + dependency ('goa-1.0'), + dependency ('goa-backend-1.0'), + dependency ('gtk+-3.0', version: '>= 3.11.3'), + dependency ('glib-2.0', version: '>= 2.63.1'), + dependency ('gio-unix-2.0', version: '>= 2.53.0'), + dependency ('gdm', version: '>= 3.8.3'), + dependency ('geocode-glib-1.0'), + dependency ('libgeoclue-2.0', version: '>= 2.3.1'), + cc.find_library('m', required: false), + dependency ('pango', version: '>= 1.32.5'), + dependency ('rest-0.7'), + dependency ('json-glib-1.0'), + dependency ('krb5'), + dependency ('libsecret-1', version: '>= 0.18.8'), + dependency ('pwquality'), + dependency ('webkit2gtk-4.0', version: '>= 2.26.0'), + cheese_dep, + cheese_gtk_dep, + ibus_dep, + libmalcontent_dep, + libmalcontent_ui_dep, +] + +executable( + 'gnome-initial-setup', + sources, + include_directories: config_h_dir, + dependencies: dependencies, + install: true, + install_dir: get_option('libexecdir') +) + +executable( + 'gnome-initial-setup-copy-worker', + ['gnome-initial-setup-copy-worker.c'], + include_directories: config_h_dir, + dependencies: dependencies, + install: true, + install_dir: get_option('libexecdir') +) diff --git a/gnome-initial-setup/pages/account/account.gresource.xml b/gnome-initial-setup/pages/account/account.gresource.xml new file mode 100644 index 0000000..9822e02 --- /dev/null +++ b/gnome-initial-setup/pages/account/account.gresource.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks" alias="gis-account-avatar-chooser.ui">gis-account-avatar-chooser.ui</file> + <file preprocess="xml-stripblanks" alias="gis-account-page.ui">gis-account-page.ui</file> + <file preprocess="xml-stripblanks" alias="gis-account-page-local.ui">gis-account-page-local.ui</file> + <file preprocess="xml-stripblanks" alias="gis-account-page-enterprise.ui">gis-account-page-enterprise.ui</file> + <file alias="gis-account-page-style.css">gis-account-page-style.css</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/account/gis-account-avatar-chooser.ui b/gnome-initial-setup/pages/account/gis-account-avatar-chooser.ui new file mode 100644 index 0000000..871470b --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-avatar-chooser.ui @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 3.8 --> + <template class="UmPhotoDialog" parent="GtkPopover"> + <property name="height-request">360</property> + <property name="width-request">480</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">GTK_ORIENTATION_VERTICAL</property> + <child> + <object class="GtkFlowBox" id="recent_pictures"> + <property name="visible">True</property> + <property name="halign">start</property> + <property name="margin">20</property> + <property name="margin-bottom">0</property> + <property name="selection-mode">none</property> + </object> + </child> + <child> + <object class="GtkFlowBox" id="flowbox"> + <property name="visible">True</property> + <property name="border-width">20</property> + <property name="selection-mode">none</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_CENTER</property> + <property name="border-width">10</property> + <property name="spacing">10</property> + <child> + <object class="GtkButton" id="take_picture_button"> + <property name="visible">False</property> + <property name="label" translatable="yes">Take a Picture…</property> + <signal name="clicked" handler="webcam_icon_selected" swapped="yes"/> + </object> + </child> + </object> + <packing> + <property name="pack-type">GTK_PACK_END</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/account/gis-account-page-enterprise.c b/gnome-initial-setup/pages/account/gis-account-page-enterprise.c new file mode 100644 index 0000000..799c51f --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page-enterprise.c @@ -0,0 +1,855 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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 "gis-account-page-enterprise.h" +#include "gnome-initial-setup.h" + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include <act/act-user-manager.h> + +#include "um-realm-manager.h" +#include "um-utils.h" + +#include "gis-page-header.h" + +static void join_show_prompt (GisAccountPageEnterprise *page, + GError *error); + +static void on_join_login (GObject *source, + GAsyncResult *result, + gpointer user_data); + +static void on_realm_joined (GObject *source, + GAsyncResult *result, + gpointer user_data); + +struct _GisAccountPageEnterprisePrivate +{ + GtkWidget *header; + GtkWidget *login; + GtkWidget *password; + GtkWidget *domain; + GtkWidget *domain_entry; + GtkTreeModel *realms_model; + + GtkWidget *join_dialog; + GtkWidget *join_name; + GtkWidget *join_password; + GtkWidget *join_domain; + GtkWidget *join_computer; + + ActUserManager *act_client; + ActUser *act_user; + + guint realmd_watch; + UmRealmManager *realm_manager; + gboolean domain_chosen; + + /* Valid during apply */ + UmRealmObject *realm; + GCancellable *cancellable; + gboolean join_prompted; + + GisPageApplyCallback apply_complete_callback; + gpointer apply_complete_data; +}; +typedef struct _GisAccountPageEnterprisePrivate GisAccountPageEnterprisePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisAccountPageEnterprise, gis_account_page_enterprise, GTK_TYPE_BIN); + +enum { + VALIDATION_CHANGED, + USER_CACHED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +validation_changed (GisAccountPageEnterprise *page) +{ + g_signal_emit (page, signals[VALIDATION_CHANGED], 0); +} + +static void +apply_complete (GisAccountPageEnterprise *page, + gboolean valid) +{ + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + priv->apply_complete_callback (NULL, valid, priv->apply_complete_data); +} + +static void +show_error_dialog (GisAccountPageEnterprise *page, + const gchar *message, + GError *error) +{ + GtkWidget *dialog; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", message); + + if (error != NULL) { + g_dbus_error_strip_remote_error (error); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", error->message); + } + + g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); + gtk_window_present (GTK_WINDOW (dialog)); +} + +gboolean +gis_account_page_enterprise_validate (GisAccountPageEnterprise *page) +{ + const gchar *name; + gboolean valid_name; + gboolean valid_domain; + GtkTreeIter iter; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + + name = gtk_entry_get_text (GTK_ENTRY (priv->login)); + valid_name = is_valid_name (name); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (priv->domain), &iter)) { + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (priv->domain)), + &iter, 0, &name, -1); + } else { + name = gtk_entry_get_text (GTK_ENTRY (priv->domain_entry)); + } + + valid_domain = is_valid_name (name); + return valid_name && valid_domain; +} + +static void +on_permit_user_login (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + UmRealmCommon *common; + GError *error = NULL; + gchar *login; + + common = UM_REALM_COMMON (source); + um_realm_common_call_change_login_policy_finish (common, result, &error); + if (error == NULL) { + + /* + * Now tell the account service about this user. The account service + * should also lookup information about this via the realm and make + * sure all that is functional. + */ + login = um_realm_calculate_login (common, gtk_entry_get_text (GTK_ENTRY (priv->login))); + g_return_if_fail (login != NULL); + + g_debug ("Caching remote user: %s", login); + + priv->act_user = act_user_manager_cache_user (priv->act_client, login, NULL); + g_signal_emit (page, signals[USER_CACHED], 0, priv->act_user, gtk_entry_get_text (GTK_ENTRY (priv->password))); + apply_complete (page, TRUE); + + g_free (login); + } else { + show_error_dialog (page, _("Failed to register account"), error); + g_message ("Couldn't permit logins on account: %s", error->message); + g_error_free (error); + apply_complete (page, FALSE); + } +} + +static void +enterprise_permit_user_login (GisAccountPageEnterprise *page, UmRealmObject *realm) +{ + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + UmRealmCommon *common; + gchar *login; + const gchar *add[2]; + const gchar *remove[1]; + GVariant *options; + + common = um_realm_object_get_common (realm); + + login = um_realm_calculate_login (common, gtk_entry_get_text (GTK_ENTRY (priv->login))); + g_return_if_fail (login != NULL); + + add[0] = login; + add[1] = NULL; + remove[0] = NULL; + + g_debug ("Permitting login for: %s", login); + options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0); + + um_realm_common_call_change_login_policy (common, "", + add, remove, options, + priv->cancellable, + on_permit_user_login, + page); + + g_object_unref (common); + g_free (login); +} + +static void +on_set_static_hostname (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GError *error = NULL; + GVariant *retval; + + retval = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); + if (error != NULL) { + join_show_prompt (page, error); + g_error_free (error); + return; + } + + g_variant_unref (retval); + + /* Prompted for some admin credentials, try to use them to log in */ + um_realm_login (priv->realm, + gtk_entry_get_text (GTK_ENTRY (priv->join_name)), + gtk_entry_get_text (GTK_ENTRY (priv->join_password)), + priv->cancellable, on_join_login, page); +} + +static void +on_join_response (GtkDialog *dialog, + gint response, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GDBusConnection *connection; + GError *error = NULL; + gchar hostname[128]; + const gchar *name; + + gtk_widget_hide (GTK_WIDGET (dialog)); + if (response != GTK_RESPONSE_OK) { + apply_complete (page, FALSE); + return; + } + + name = gtk_entry_get_text (GTK_ENTRY (priv->join_computer)); + if (gethostname (hostname, sizeof (hostname)) == 0 && + !g_str_equal (name, hostname)) { + g_debug ("Setting StaticHostname to '%s'", name); + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, priv->cancellable, &error); + if (error != NULL) { + apply_complete (page, FALSE); + g_warning ("Could not get DBus connection: %s", error->message); + g_error_free (error); + return; + } + + g_dbus_connection_call (connection, "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", + "SetStaticHostname", + g_variant_new ("(sb)", name, TRUE), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, NULL, on_set_static_hostname, page); + + } else { + name = gtk_entry_get_text (GTK_ENTRY (priv->join_name)); + g_debug ("Logging in as admin user: %s", name); + + /* Prompted for some admin credentials, try to use them to log in */ + um_realm_login (priv->realm, name, + gtk_entry_get_text (GTK_ENTRY (priv->join_password)), + NULL, on_join_login, page); + } +} + +static void +join_show_prompt (GisAccountPageEnterprise *page, + GError *error) +{ + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + UmRealmKerberosMembership *membership; + UmRealmKerberos *kerberos; + gchar hostname[128]; + const gchar *name; + + gtk_entry_set_text (GTK_ENTRY (priv->join_password), ""); + gtk_widget_grab_focus (GTK_WIDGET (priv->join_password)); + + kerberos = um_realm_object_get_kerberos (priv->realm); + membership = um_realm_object_get_kerberos_membership (priv->realm); + + gtk_label_set_text (GTK_LABEL (priv->join_domain), + um_realm_kerberos_get_domain_name (kerberos)); + + if (gethostname (hostname, sizeof (hostname)) == 0) + gtk_entry_set_text (GTK_ENTRY (priv->join_computer), hostname); + + clear_entry_validation_error (GTK_ENTRY (priv->join_name)); + clear_entry_validation_error (GTK_ENTRY (priv->join_password)); + + if (!priv->join_prompted) { + name = um_realm_kerberos_membership_get_suggested_administrator (membership); + if (name && !g_str_equal (name, "")) { + g_debug ("Suggesting admin user: %s", name); + gtk_entry_set_text (GTK_ENTRY (priv->join_name), name); + } else { + gtk_widget_grab_focus (GTK_WIDGET (priv->join_name)); + } + + } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME)) { + g_debug ("Bad host name: %s", error->message); + set_entry_validation_error (GTK_ENTRY (priv->join_computer), error->message); + + } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD)) { + g_debug ("Bad admin password: %s", error->message); + set_entry_validation_error (GTK_ENTRY (priv->join_password), error->message); + + } else { + g_debug ("Admin login failure: %s", error->message); + g_dbus_error_strip_remote_error (error); + set_entry_validation_error (GTK_ENTRY (priv->join_name), error->message); + } + + g_debug ("Showing admin password dialog"); + gtk_window_set_transient_for (GTK_WINDOW (priv->join_dialog), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page)))); + gtk_window_set_modal (GTK_WINDOW (priv->join_dialog), TRUE); + gtk_window_present (GTK_WINDOW (priv->join_dialog)); + + priv->join_prompted = TRUE; + g_object_unref (kerberos); + g_object_unref (membership); + + /* And now we wait for on_join_response() */ +} + +static void +on_join_login (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GError *error = NULL; + GBytes *creds; + + um_realm_login_finish (priv->realm, result, &creds, &error); + + /* Logged in as admin successfully, use creds to join domain */ + if (error == NULL) { + if (!um_realm_join_as_admin (priv->realm, + gtk_entry_get_text (GTK_ENTRY (priv->join_name)), + gtk_entry_get_text (GTK_ENTRY (priv->join_password)), + creds, NULL, on_realm_joined, page)) { + show_error_dialog (page, _("No supported way to authenticate with this domain"), NULL); + g_message ("Authenticating as admin is not supported by the realm"); + } + + g_bytes_unref (creds); + + /* Couldn't login as admin, show prompt again */ + } else { + join_show_prompt (page, error); + g_message ("Couldn't log in as admin to join domain: %s", error->message); + g_error_free (error); + } +} + +static void +on_realm_joined (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GError *error = NULL; + + um_realm_join_finish (priv->realm, result, &error); + + /* Yay, joined the domain, register the user locally */ + if (error == NULL) { + g_debug ("Joining realm completed successfully"); + enterprise_permit_user_login (page, priv->realm); + + /* Credential failure while joining domain, prompt for admin creds */ + } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN) || + g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD) || + g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME)) { + g_debug ("Joining realm failed due to credentials or host name"); + + join_show_prompt (page, error); + + /* Other failure */ + } else { + show_error_dialog (page, _("Failed to join domain"), error); + g_message ("Failed to join the domain: %s", error->message); + apply_complete (page, FALSE); + } + + g_clear_error (&error); +} + +static void +on_realm_login (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GError *error = NULL; + GBytes *creds = NULL; + + um_realm_login_finish (priv->realm, result, &creds, &error); + + /* + * User login is valid, but cannot authenticate right now (eg: user needs + * to change password at next login etc.) + */ + if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_CANNOT_AUTH)) { + g_clear_error (&error); + creds = NULL; + } + + if (error == NULL) { + + /* Already joined to the domain, just register this user */ + if (um_realm_is_configured (priv->realm)) { + g_debug ("Already joined to this realm"); + enterprise_permit_user_login (page, priv->realm); + + /* Join the domain, try using the user's creds */ + } else if (creds == NULL || + !um_realm_join_as_user (priv->realm, + gtk_entry_get_text (GTK_ENTRY (priv->login)), + gtk_entry_get_text (GTK_ENTRY (priv->password)), + creds, priv->cancellable, + on_realm_joined, + page)) { + + /* If we can't do user auth, try to authenticate as admin */ + g_debug ("Cannot join with user credentials"); + + join_show_prompt (page, error); + } + + g_bytes_unref (creds); + + /* A problem with the user's login name or password */ + } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN)) { + g_debug ("Problem with the user's login: %s", error->message); + set_entry_validation_error (GTK_ENTRY (priv->login), error->message); + gtk_widget_grab_focus (priv->login); + apply_complete (page, FALSE); + + } else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD)) { + g_debug ("Problem with the user's password: %s", error->message); + set_entry_validation_error (GTK_ENTRY (priv->password), error->message); + gtk_widget_grab_focus (priv->password); + apply_complete (page, FALSE); + + /* Other login failure */ + } else { + show_error_dialog (page, _("Failed to log into domain"), error); + g_message ("Couldn't log in as user: %s", error->message); + apply_complete (page, FALSE); + } + + g_clear_error (&error); +} + +static void +enterprise_check_login (GisAccountPageEnterprise *page) +{ + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + + g_assert (priv->realm); + + um_realm_login (priv->realm, + gtk_entry_get_text (GTK_ENTRY (priv->login)), + gtk_entry_get_text (GTK_ENTRY (priv->password)), + priv->cancellable, + on_realm_login, + page); +} + +static void +on_realm_discover_input (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GError *error = NULL; + GList *realms; + + realms = um_realm_manager_discover_finish (priv->realm_manager, + result, &error); + + /* Found a realm, log user into domain */ + if (error == NULL) { + g_assert (realms != NULL); + priv->realm = g_object_ref (realms->data); + enterprise_check_login (page); + g_list_free_full (realms, g_object_unref); + + } else { + /* The domain is likely invalid */ + g_dbus_error_strip_remote_error (error); + g_message ("Couldn't discover domain: %s", error->message); + gtk_widget_grab_focus (priv->domain_entry); + set_entry_validation_error (GTK_ENTRY (priv->domain_entry), error->message); + apply_complete (page, FALSE); + g_error_free (error); + } +} + +static void +enterprise_add_user (GisAccountPageEnterprise *page) +{ + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GtkTreeIter iter; + + priv->join_prompted = FALSE; + g_clear_object (&priv->realm); + + /* Already know about this realm, try to login as user */ + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (priv->domain), &iter)) { + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (priv->domain)), + &iter, 1, &priv->realm, -1); + enterprise_check_login (page); + + /* Something the user typed, we need to discover realm */ + } else { + um_realm_manager_discover (priv->realm_manager, + gtk_entry_get_text (GTK_ENTRY (priv->domain_entry)), + priv->cancellable, + on_realm_discover_input, + page); + } +} + +gboolean +gis_account_page_enterprise_apply (GisAccountPageEnterprise *page, + GCancellable *cancellable, + GisPageApplyCallback callback, + gpointer data) +{ + GisPage *account_page = GIS_PAGE (data); + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + + /* Parental controls are not enterprise ready. It’s possible for them to have + * been enabled if the user enabled them, applied the account-local page, and + * then went back and decided to go all enterprise instead. */ + gis_driver_set_parental_controls_enabled (account_page->driver, FALSE); + + priv->apply_complete_callback = callback; + priv->apply_complete_data = data; + priv->cancellable = g_object_ref (cancellable); + enterprise_add_user (page); + return TRUE; +} + +static gchar * +realm_get_name (UmRealmObject *realm) +{ + UmRealmCommon *common; + gchar *name; + + common = um_realm_object_get_common (realm); + name = g_strdup (um_realm_common_get_name (common)); + g_object_unref (common); + + return name; +} + +static gboolean +model_contains_realm (GtkTreeModel *model, + const gchar *realm_name) +{ + gboolean contains = FALSE; + GtkTreeIter iter; + gboolean match; + gchar *name; + gboolean ret; + + ret = gtk_tree_model_get_iter_first (model, &iter); + while (ret) { + gtk_tree_model_get (model, &iter, 0, &name, -1); + match = (g_strcmp0 (name, realm_name) == 0); + g_free (name); + if (match) { + g_debug ("ignoring duplicate realm: %s", realm_name); + contains = TRUE; + break; + } + ret = gtk_tree_model_iter_next (model, &iter); + } + + return contains; +} + +static void +enterprise_add_realm (GisAccountPageEnterprise *page, + UmRealmObject *realm) +{ + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GtkTreeIter iter; + gchar *name; + + name = realm_get_name (realm); + + /* + * Don't add a second realm if we already have one with this name. + * Sometimes realmd returns two realms for the same name, if it has + * different ways to use that realm. The first one that realmd + * returns is the one it prefers. + */ + + if (!model_contains_realm (GTK_TREE_MODEL (priv->realms_model), name)) { + gtk_list_store_append (GTK_LIST_STORE (priv->realms_model), &iter); + gtk_list_store_set (GTK_LIST_STORE (priv->realms_model), &iter, + 0, name, + 1, realm, + -1); + + if (!priv->domain_chosen && um_realm_is_configured (realm)) + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->domain), &iter); + + g_debug ("added realm to drop down: %s %s", name, + g_dbus_object_get_object_path (G_DBUS_OBJECT (realm))); + } + + g_free (name); +} + +static void +on_manager_realm_added (UmRealmManager *manager, + UmRealmObject *realm, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + enterprise_add_realm (page, realm); +} + +static void +on_realm_manager_created (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GError *error = NULL; + GList *realms, *l; + + g_clear_object (&priv->realm_manager); + priv->realm_manager = um_realm_manager_new_finish (result, &error); + + if (error != NULL) { + g_warning ("Couldn't contact realmd service: %s", error->message); + g_error_free (error); + return; + } + + /* Lookup all the realm objects */ + realms = um_realm_manager_get_realms (priv->realm_manager); + for (l = realms; l != NULL; l = g_list_next (l)) + enterprise_add_realm (page, l->data); + + g_list_free (realms); + g_signal_connect (priv->realm_manager, "realm-added", + G_CALLBACK (on_manager_realm_added), page); + + /* When no realms try to discover a sensible default, triggers realm-added signal */ + um_realm_manager_discover (priv->realm_manager, "", NULL, NULL, NULL); + gtk_widget_set_visible (GTK_WIDGET (page), TRUE); +} + +static void +on_realmd_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + um_realm_manager_new (NULL, on_realm_manager_created, page); +} + +static void +on_realmd_disappeared (GDBusConnection *unused1, + const gchar *unused2, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + + if (priv->realm_manager != NULL) { + g_signal_handlers_disconnect_by_func (priv->realm_manager, + on_manager_realm_added, + page); + g_clear_object (&priv->realm_manager); + } + + gtk_widget_set_visible (GTK_WIDGET (page), FALSE); +} + +static void +on_domain_changed (GtkComboBox *widget, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + + priv->domain_chosen = TRUE; + validation_changed (page); + clear_entry_validation_error (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (widget)))); +} + +static void +on_entry_changed (GtkEditable *editable, + gpointer user_data) +{ + GisAccountPageEnterprise *page = user_data; + validation_changed (page); + clear_entry_validation_error (GTK_ENTRY (editable)); +} + +static void +gis_account_page_enterprise_realize (GtkWidget *widget) +{ + GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (widget); + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + GtkWidget *gis_page; + + gis_page = gtk_widget_get_ancestor (widget, GIS_TYPE_PAGE); + g_object_bind_property (gis_page, "small-screen", + priv->header, "show-icon", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + GTK_WIDGET_CLASS (gis_account_page_enterprise_parent_class)->realize (widget); +} + +static void +gis_account_page_enterprise_constructed (GObject *object) +{ + GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (object); + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + + G_OBJECT_CLASS (gis_account_page_enterprise_parent_class)->constructed (object); + + priv->act_client = act_user_manager_get_default (); + + priv->realmd_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM, "org.freedesktop.realmd", + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + on_realmd_appeared, on_realmd_disappeared, + page, NULL); + + g_signal_connect (priv->join_dialog, "response", + G_CALLBACK (on_join_response), page); + g_signal_connect (priv->domain, "changed", + G_CALLBACK (on_domain_changed), page); + g_signal_connect (priv->login, "changed", + G_CALLBACK (on_entry_changed), page); +} + +static void +gis_account_page_enterprise_dispose (GObject *object) +{ + GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (object); + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + + if (priv->realmd_watch) + g_bus_unwatch_name (priv->realmd_watch); + + priv->realmd_watch = 0; + + g_cancellable_cancel (priv->cancellable); + + g_clear_object (&priv->realm_manager); + g_clear_object (&priv->realm); + g_clear_object (&priv->cancellable); + + G_OBJECT_CLASS (gis_account_page_enterprise_parent_class)->dispose (object); +} + +static void +gis_account_page_enterprise_class_init (GisAccountPageEnterpriseClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gis_account_page_enterprise_constructed; + object_class->dispose = gis_account_page_enterprise_dispose; + + widget_class->realize = gis_account_page_enterprise_realize; + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-account-page-enterprise.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, login); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, password); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, domain); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, domain_entry); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, realms_model); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, header); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_dialog); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_name); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_password); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_domain); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_computer); + + signals[VALIDATION_CHANGED] = g_signal_new ("validation-changed", GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + signals[USER_CACHED] = g_signal_new ("user-cached", GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING); +} + +static void +gis_account_page_enterprise_init (GisAccountPageEnterprise *page) +{ + g_type_ensure (GIS_TYPE_PAGE_HEADER); + gtk_widget_init_template (GTK_WIDGET (page)); +} + +void +gis_account_page_enterprise_shown (GisAccountPageEnterprise *page) +{ + GisAccountPageEnterprisePrivate *priv = gis_account_page_enterprise_get_instance_private (page); + + gtk_widget_grab_focus (priv->domain_entry); +} diff --git a/gnome-initial-setup/pages/account/gis-account-page-enterprise.h b/gnome-initial-setup/pages/account/gis-account-page-enterprise.h new file mode 100644 index 0000000..55f0bb2 --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page-enterprise.h @@ -0,0 +1,63 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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> + */ + +#ifndef __GIS_ACCOUNT_PAGE_ENTERPRISE_H__ +#define __GIS_ACCOUNT_PAGE_ENTERPRISE_H__ + +#include <gtk/gtk.h> + +/* For GisPageApplyCallback */ +#include "gis-page.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE (gis_account_page_enterprise_get_type ()) +#define GIS_ACCOUNT_PAGE_ENTERPRISE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE, GisAccountPageEnterprise)) +#define GIS_ACCOUNT_PAGE_ENTERPRISE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE, GisAccountPageEnterpriseClass)) +#define GIS_IS_ACCOUNT_PAGE_ENTERPRISE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE)) +#define GIS_IS_ACCOUNT_PAGE_ENTERPRISE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE)) +#define GIS_ACCOUNT_PAGE_ENTERPRISE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE, GisAccountPageEnterpriseClass)) + +typedef struct _GisAccountPageEnterprise GisAccountPageEnterprise; +typedef struct _GisAccountPageEnterpriseClass GisAccountPageEnterpriseClass; + +struct _GisAccountPageEnterprise +{ + GtkBin parent; +}; + +struct _GisAccountPageEnterpriseClass +{ + GtkBinClass parent_class; +}; + +GType gis_account_page_enterprise_get_type (void); + +gboolean gis_account_page_enterprise_validate (GisAccountPageEnterprise *enterprise); +gboolean gis_account_page_enterprise_apply (GisAccountPageEnterprise *enterprise, + GCancellable *cancellable, + GisPageApplyCallback callback, + gpointer data); +void gis_account_page_enterprise_shown (GisAccountPageEnterprise *enterprise); + +G_END_DECLS + +#endif /* __GIS_ACCOUNT_PAGE_ENTERPRISE_H__ */ diff --git a/gnome-initial-setup/pages/account/gis-account-page-enterprise.ui b/gnome-initial-setup/pages/account/gis-account-page-enterprise.ui new file mode 100644 index 0000000..5432e55 --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page-enterprise.ui @@ -0,0 +1,447 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisAccountPageEnterprise" parent="GtkBin"> + <child> + <object class="GtkBox" id="area"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin_bottom">26</property> + <property name="margin_top">24</property> + <property name="title" translatable="yes">Enterprise Login</property> + <property name="subtitle" translatable="yes">Enterprise login allows an existing centrally managed user account to be used on this device. You can also use this account to access company resources on the internet.</property> + <property name="icon_name">dialog-password-symbolic</property> + <property name="show_icon">True</property> + </object> + </child> + <child> + <object class="GtkGrid" id="form"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">12</property> + <property name="column_spacing">12</property> + <property name="margin_bottom">32</property> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Domain</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">domain</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Username</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">login</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label9"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="login"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <property name="max-length">255</property> + <property name="width-chars">25</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="password"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="activates_default">True</property> + <property name="invisible_char_set">True</property> + <property name="max-length">255</property> + <property name="width-chars">25</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="domain"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="model">realms_model</property> + <property name="has_entry">True</property> + <property name="entry_text_column">0</property> + <child internal-child="entry"> + <object class="GtkEntry" id="domain_entry"> + <property name="can_focus">True</property> + <property name="max-length">255</property> + <property name="width-chars">25</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label10"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_bottom">12</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Enterprise domain or realm name</property> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="filler"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkDialog" id="join_dialog"> + <property name="can_focus">False</property> + <property name="border_width">10</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">dialog</property> + <property name="title"></property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button1"> + <property name="label">gtk-cancel</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button2"> + <property name="label" translatable="yes">C_ontinue</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="orientation">vertical</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel" id="label71"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Domain Administrator Login</property> + <attributes> + <attribute name="weight" value="bold"/> + <attribute name="scale" value="1.2"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0</property> + <property name="wrap">True</property> + <property name="max-width-chars">60</property> + <property name="label" translatable="yes">In order to use enterprise logins, this computer needs to be enrolled in a domain. Please have your network administrator type the domain password here, and choose a unique computer name for your computer.</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">12</property> + <property name="hexpand">True</property> + <property name="row_spacing">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Domain</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">join_domain</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="join_domain"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label18"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Computer</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">join_computer</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="join_computer"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label14"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Administrator _Name</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">join_name</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="join_name"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Administrator Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">join_password</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="join_password"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="activates_default">True</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">button1</action-widget> + <action-widget response="-5">button2</action-widget> + </action-widgets> + </object> + <object class="GtkListStore" id="realms_model"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + <!-- column-name gobject --> + <column type="GObject"/> + </columns> + </object> +</interface> diff --git a/gnome-initial-setup/pages/account/gis-account-page-local.c b/gnome-initial-setup/pages/account/gis-account-page-local.c new file mode 100644 index 0000000..c858c60 --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page-local.c @@ -0,0 +1,721 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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 "gis-page.h" +#include "gis-account-page-local.h" +#include "gnome-initial-setup.h" + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include <string.h> +#include <act/act-user-manager.h> +#include "um-utils.h" +#include "um-photo-dialog.h" + +#include "gis-page-header.h" + +#define GOA_API_IS_SUBJECT_TO_CHANGE +#include <goa/goa.h> + +#include <rest/oauth-proxy.h> +#include <json-glib/json-glib.h> + +#define VALIDATION_TIMEOUT 600 + +struct _GisAccountPageLocalPrivate +{ + GtkWidget *avatar_button; + GtkWidget *avatar_image; + GtkWidget *header; + GtkWidget *fullname_entry; + GtkWidget *username_combo; + GtkWidget *enable_parental_controls_box; + GtkWidget *enable_parental_controls_check_button; + gboolean has_custom_username; + GtkWidget *username_explanation; + UmPhotoDialog *photo_dialog; + + gint timeout_id; + + GdkPixbuf *avatar_pixbuf; + gchar *avatar_filename; + + ActUserManager *act_client; + + GoaClient *goa_client; + + gboolean valid_name; + gboolean valid_username; + ActUserAccountType account_type; +}; +typedef struct _GisAccountPageLocalPrivate GisAccountPageLocalPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisAccountPageLocal, gis_account_page_local, GTK_TYPE_BIN); + +enum { + VALIDATION_CHANGED, + MAIN_USER_CREATED, + PARENT_USER_CREATED, + CONFIRM, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +validation_changed (GisAccountPageLocal *page) +{ + g_signal_emit (page, signals[VALIDATION_CHANGED], 0); +} + +static gboolean +get_profile_sync (const gchar *access_token, + gchar **out_name, + gchar **out_picture, + GCancellable *cancellable, + GError **error) +{ + GError *identity_error; + RestProxy *proxy; + RestProxyCall *call; + JsonParser *parser; + JsonObject *json_object; + gboolean ret; + + ret = FALSE; + + identity_error = NULL; + proxy = NULL; + call = NULL; + parser = NULL; + + /* TODO: cancellable */ + + proxy = rest_proxy_new ("https://www.googleapis.com/oauth2/v2/userinfo", FALSE); + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_method (call, "GET"); + rest_proxy_call_add_param (call, "access_token", access_token); + + if (!rest_proxy_call_sync (call, error)) + goto out; + + if (rest_proxy_call_get_status_code (call) != 200) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Expected status 200 when requesting your identity, instead got status %d (%s)", + rest_proxy_call_get_status_code (call), + rest_proxy_call_get_status_message (call)); + goto out; + } + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call), + &identity_error)) + { + g_warning ("json_parser_load_from_data() failed: %s (%s, %d)", + identity_error->message, + g_quark_to_string (identity_error->domain), + identity_error->code); + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, + "Could not parse response"); + goto out; + } + + ret = TRUE; + + json_object = json_node_get_object (json_parser_get_root (parser)); + if (out_name != NULL) + *out_name = g_strdup (json_object_get_string_member (json_object, "name")); + + if (out_picture != NULL) + *out_picture = g_strdup (json_object_get_string_member (json_object, "picture")); + + out: + g_clear_error (&identity_error); + if (call != NULL) + g_object_unref (call); + if (proxy != NULL) + g_object_unref (proxy); + + return ret; +} + +static void +prepopulate_account_page (GisAccountPageLocal *page) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + gchar *name = NULL; + gchar *picture = NULL; + GdkPixbuf *pixbuf = NULL; + + if (priv->goa_client) { + GList *accounts, *l; + accounts = goa_client_get_accounts (priv->goa_client); + for (l = accounts; l != NULL; l = l->next) { + GoaOAuth2Based *oa2; + oa2 = goa_object_get_oauth2_based (GOA_OBJECT (l->data)); + if (oa2) { + gchar *token = NULL; + GError *error = NULL; + if (!goa_oauth2_based_call_get_access_token_sync (oa2, &token, NULL, NULL, &error)) + { + g_warning ("Couldn't get oauth2 token: %s", error->message); + g_error_free (error); + } + else if (!get_profile_sync (token, &name, &picture, NULL, &error)) + { + g_warning ("Couldn't get profile information: %s", error->message); + g_error_free (error); + } + /* FIXME: collect information from more than one account + * and present at least the pictures in the avatar chooser + */ + break; + } + } + g_list_free_full (accounts, (GDestroyNotify) g_object_unref); + } + + if (name) { + g_object_set (priv->header, "subtitle", _("Please check the name and username. You can choose a picture too."), NULL); + gtk_entry_set_text (GTK_ENTRY (priv->fullname_entry), name); + } + + if (picture) { + GFile *file; + GFileInputStream *stream; + GError *error = NULL; + file = g_file_new_for_uri (picture); + stream = g_file_read (file, NULL, &error); + if (!stream) + { + g_warning ("Failed to read picture %s: %s", picture, error->message); + g_error_free (error); + } + else + { + pixbuf = gdk_pixbuf_new_from_stream_at_scale (G_INPUT_STREAM (stream), -1, 96, TRUE, NULL, NULL); + g_object_unref (stream); + } + g_object_unref (file); + } + + if (pixbuf) { + GdkPixbuf *rounded = round_image (pixbuf); + + gtk_image_set_from_pixbuf (GTK_IMAGE (priv->avatar_image), rounded); + g_object_unref (rounded); + priv->avatar_pixbuf = pixbuf; + } + + g_free (name); + g_free (picture); +} + +static void +accounts_changed (GoaClient *client, GoaObject *object, gpointer data) +{ + GisAccountPageLocal *page = data; + + prepopulate_account_page (page); +} + +static gboolean +validate (GisAccountPageLocal *page) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + GtkWidget *entry; + const gchar *name, *username; + gboolean parental_controls_enabled; + gchar *tip; + + g_clear_handle_id (&priv->timeout_id, g_source_remove); + + entry = gtk_bin_get_child (GTK_BIN (priv->username_combo)); + + name = gtk_entry_get_text (GTK_ENTRY (priv->fullname_entry)); + username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (priv->username_combo)); +#ifdef HAVE_PARENTAL_CONTROLS + parental_controls_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->enable_parental_controls_check_button)); +#else + parental_controls_enabled = FALSE; +#endif + + priv->valid_name = is_valid_name (name); + if (priv->valid_name) + set_entry_validation_checkmark (GTK_ENTRY (priv->fullname_entry)); + + priv->valid_username = is_valid_username (username, parental_controls_enabled, &tip); + if (priv->valid_username) + set_entry_validation_checkmark (GTK_ENTRY (entry)); + + gtk_label_set_text (GTK_LABEL (priv->username_explanation), tip); + g_free (tip); + + um_photo_dialog_generate_avatar (priv->photo_dialog, name); + + validation_changed (page); + + return G_SOURCE_REMOVE; +} + +static gboolean +on_focusout (GisAccountPageLocal *page) +{ + validate (page); + + return FALSE; +} + +static void +fullname_changed (GtkWidget *w, + GParamSpec *pspec, + GisAccountPageLocal *page) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + GtkWidget *entry; + GtkTreeModel *model; + const char *name; + + name = gtk_entry_get_text (GTK_ENTRY (w)); + + entry = gtk_bin_get_child (GTK_BIN (priv->username_combo)); + model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->username_combo)); + + gtk_list_store_clear (GTK_LIST_STORE (model)); + + if ((name == NULL || strlen (name) == 0) && !priv->has_custom_username) { + gtk_entry_set_text (GTK_ENTRY (entry), ""); + } + else if (name != NULL && strlen (name) != 0) { + generate_username_choices (name, GTK_LIST_STORE (model)); + if (!priv->has_custom_username) + gtk_combo_box_set_active (GTK_COMBO_BOX (priv->username_combo), 0); + } + + clear_entry_validation_error (GTK_ENTRY (w)); + + priv->valid_name = FALSE; + + /* username_changed() is called consequently due to changes */ +} + +static void +username_changed (GtkComboBoxText *combo, + GisAccountPageLocal *page) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + GtkWidget *entry; + const gchar *username; + + entry = gtk_bin_get_child (GTK_BIN (combo)); + username = gtk_entry_get_text (GTK_ENTRY (entry)); + if (*username == '\0') + priv->has_custom_username = FALSE; + else if (gtk_widget_has_focus (entry) || + gtk_combo_box_get_active (GTK_COMBO_BOX (priv->username_combo)) > 0) + priv->has_custom_username = TRUE; + + clear_entry_validation_error (GTK_ENTRY (entry)); + + priv->valid_username = FALSE; + validation_changed (page); + + if (priv->timeout_id != 0) + g_source_remove (priv->timeout_id); + priv->timeout_id = g_timeout_add (VALIDATION_TIMEOUT, (GSourceFunc)validate, page); +} + +static void +avatar_callback (GdkPixbuf *pixbuf, + const gchar *filename, + gpointer user_data) +{ + GisAccountPageLocal *page = user_data; + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + g_autoptr(GdkPixbuf) tmp = NULL; + g_autoptr(GdkPixbuf) rounded = NULL; + + g_clear_object (&priv->avatar_pixbuf); + g_clear_pointer (&priv->avatar_filename, g_free); + + if (pixbuf) { + priv->avatar_pixbuf = g_object_ref (pixbuf); + rounded = round_image (pixbuf); + } + else if (filename) { + priv->avatar_filename = g_strdup (filename); + tmp = gdk_pixbuf_new_from_file_at_size (filename, 96, 96, NULL); + + if (tmp != NULL) + rounded = round_image (tmp); + } + + if (rounded != NULL) { + gtk_image_set_from_pixbuf (GTK_IMAGE (priv->avatar_image), rounded); + } + else { + /* Fallback. */ + gtk_image_set_pixel_size (GTK_IMAGE (priv->avatar_image), 96); + gtk_image_set_from_icon_name (GTK_IMAGE (priv->avatar_image), "avatar-default-symbolic", 1); + } +} + +static void +confirm (GisAccountPageLocal *page) +{ + if (gis_account_page_local_validate (page)) + g_signal_emit (page, signals[CONFIRM], 0); +} + +static void +enable_parental_controls_check_button_toggled_cb (GtkToggleButton *toggle_button, + gpointer user_data) +{ + GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (user_data); + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + gboolean parental_controls_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->enable_parental_controls_check_button)); + + /* This sets the account type of the main user. When we save_data(), we create + * two users if parental controls are enabled: the first user is always an + * admin, and the second user is the main user using this @account_type. */ + priv->account_type = parental_controls_enabled ? ACT_USER_ACCOUNT_TYPE_STANDARD : ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR; + + validate (page); +} + +static void +gis_account_page_local_constructed (GObject *object) +{ + GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (object); + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + GtkCssProvider *provider; + + G_OBJECT_CLASS (gis_account_page_local_parent_class)->constructed (object); + + priv->act_client = act_user_manager_get_default (); + + g_signal_connect (priv->fullname_entry, "notify::text", + G_CALLBACK (fullname_changed), page); + g_signal_connect_swapped (priv->fullname_entry, "focus-out-event", + G_CALLBACK (on_focusout), page); + g_signal_connect_swapped (priv->fullname_entry, "activate", + G_CALLBACK (validate), page); + g_signal_connect (priv->username_combo, "changed", + G_CALLBACK (username_changed), page); + g_signal_connect_swapped (priv->username_combo, "focus-out-event", + G_CALLBACK (on_focusout), page); + g_signal_connect_swapped (gtk_bin_get_child (GTK_BIN (priv->username_combo)), + "activate", G_CALLBACK (confirm), page); + g_signal_connect_swapped (priv->fullname_entry, "activate", + G_CALLBACK (confirm), page); + g_signal_connect (priv->enable_parental_controls_check_button, "toggled", + G_CALLBACK (enable_parental_controls_check_button_toggled_cb), page); + + /* Disable parental controls if support is not compiled in. */ +#ifndef HAVE_PARENTAL_CONTROLS + gtk_widget_hide (priv->enable_parental_controls_box); +#endif + + priv->valid_name = FALSE; + priv->valid_username = FALSE; + + /* FIXME: change this for a large deployment scenario; maybe through a GSetting? */ + priv->account_type = ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR; + + g_object_set (priv->header, "subtitle", _("We need a few details to complete setup."), NULL); + gtk_entry_set_text (GTK_ENTRY (priv->fullname_entry), ""); + gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (priv->username_combo)))); + priv->has_custom_username = FALSE; + + gtk_image_set_pixel_size (GTK_IMAGE (priv->avatar_image), 96); + gtk_image_set_from_icon_name (GTK_IMAGE (priv->avatar_image), "avatar-default-symbolic", 1); + + priv->goa_client = goa_client_new_sync (NULL, NULL); + if (priv->goa_client) { + g_signal_connect (priv->goa_client, "account-added", + G_CALLBACK (accounts_changed), page); + g_signal_connect (priv->goa_client, "account-removed", + G_CALLBACK (accounts_changed), page); + prepopulate_account_page (page); + } + + priv->photo_dialog = um_photo_dialog_new (priv->avatar_button, + avatar_callback, + page); + um_photo_dialog_generate_avatar (priv->photo_dialog, ""); + + validate (page); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/initial-setup/gis-account-page-style.css"); + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); +} + +static void +gis_account_page_local_dispose (GObject *object) +{ + GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (object); + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + + g_clear_object (&priv->goa_client); + g_clear_object (&priv->avatar_pixbuf); + g_clear_pointer (&priv->avatar_filename, g_free); + g_clear_handle_id (&priv->timeout_id, g_source_remove); + + G_OBJECT_CLASS (gis_account_page_local_parent_class)->dispose (object); +} + +static void +set_user_avatar (GisAccountPageLocal *page, + ActUser *user) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + GFile *file = NULL; + GFileIOStream *io_stream = NULL; + GOutputStream *stream = NULL; + GError *error = NULL; + + if (priv->avatar_filename != NULL) { + act_user_set_icon_file (user, priv->avatar_filename); + return; + } + + if (priv->avatar_pixbuf == NULL) { + return; + } + + file = g_file_new_tmp ("usericonXXXXXX", &io_stream, &error); + if (error != NULL) + goto out; + + stream = g_io_stream_get_output_stream (G_IO_STREAM (io_stream)); + if (!gdk_pixbuf_save_to_stream (priv->avatar_pixbuf, stream, "png", NULL, &error, NULL)) + goto out; + + act_user_set_icon_file (user, g_file_get_path (file)); + + out: + if (error != NULL) { + g_warning ("failed to save image: %s", error->message); + g_error_free (error); + } + g_clear_object (&io_stream); + g_clear_object (&file); +} + +static gboolean +local_create_user (GisAccountPageLocal *local, + GisPage *page, + GError **error) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (local); + const gchar *username; + const gchar *fullname; + gboolean parental_controls_enabled; + g_autoptr(ActUser) main_user = NULL; + g_autoptr(ActUser) parent_user = NULL; + + username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (priv->username_combo)); + fullname = gtk_entry_get_text (GTK_ENTRY (priv->fullname_entry)); + parental_controls_enabled = gis_driver_get_parental_controls_enabled (page->driver); + + /* Always create the admin user first, in case of failure part-way through + * this function, which would leave us with no admin user at all. */ + if (parental_controls_enabled) { + g_autoptr(GError) local_error = NULL; + g_autoptr(GDBusConnection) connection = NULL; + const gchar *parent_username = "administrator"; + const gchar *parent_fullname = _("Administrator"); + + parent_user = act_user_manager_create_user (priv->act_client, parent_username, parent_fullname, ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR, error); + if (parent_user == NULL) + { + g_prefix_error (error, + _("Failed to create user '%s': "), + parent_username); + return FALSE; + } + + /* Make the admin account usable in case g-i-s crashes. If all goes + * according to plan a password will be set on it in gis-password-page.c */ + act_user_set_password_mode (parent_user, ACT_USER_PASSWORD_MODE_SET_AT_LOGIN); + + /* Mark it as the parent user account. + * FIXME: This should be async. */ + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &local_error); + if (connection != NULL) { + g_dbus_connection_call_sync (connection, + "org.freedesktop.Accounts", + act_user_get_object_path (parent_user), + "org.freedesktop.DBus.Properties", + "Set", + g_variant_new ("(ssv)", + "com.endlessm.ParentalControls.AccountInfo", + "IsParent", + g_variant_new_boolean (TRUE)), + NULL, /* reply type */ + G_DBUS_CALL_FLAGS_NONE, + -1, /* default timeout */ + NULL, /* cancellable */ + &local_error); + } + if (local_error != NULL) { + /* Make this non-fatal, since the correct accounts-service interface + * might not be installed, depending on which version of malcontent is installed. */ + g_warning ("Failed to mark user as parent: %s", local_error->message); + g_clear_error (&local_error); + } + + g_signal_emit (local, signals[PARENT_USER_CREATED], 0, parent_user, ""); + } + + /* Now create the main user. */ + main_user = act_user_manager_create_user (priv->act_client, username, fullname, priv->account_type, error); + if (main_user == NULL) + { + g_prefix_error (error, + _("Failed to create user '%s': "), + username); + /* FIXME: Could we delete the @parent_user at this point to reset the state + * and allow g-i-s to be run again after a reboot? */ + return FALSE; + } + + set_user_avatar (local, main_user); + + g_signal_emit (local, signals[MAIN_USER_CREATED], 0, main_user, ""); + + return TRUE; +} + +static void +gis_account_page_local_class_init (GisAccountPageLocalClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-account-page-local.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, avatar_button); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, avatar_image); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, header); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, fullname_entry); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, username_combo); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, username_explanation); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, enable_parental_controls_box); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, enable_parental_controls_check_button); + + object_class->constructed = gis_account_page_local_constructed; + object_class->dispose = gis_account_page_local_dispose; + + signals[VALIDATION_CHANGED] = g_signal_new ("validation-changed", GIS_TYPE_ACCOUNT_PAGE_LOCAL, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[MAIN_USER_CREATED] = g_signal_new ("main-user-created", GIS_TYPE_ACCOUNT_PAGE_LOCAL, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING); + + signals[PARENT_USER_CREATED] = g_signal_new ("parent-user-created", GIS_TYPE_ACCOUNT_PAGE_LOCAL, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING); + + signals[CONFIRM] = g_signal_new ("confirm", GIS_TYPE_ACCOUNT_PAGE_LOCAL, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +static void +gis_account_page_local_init (GisAccountPageLocal *page) +{ + g_type_ensure (GIS_TYPE_PAGE_HEADER); + gtk_widget_init_template (GTK_WIDGET (page)); +} + +gboolean +gis_account_page_local_validate (GisAccountPageLocal *page) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (page); + + return priv->valid_name && priv->valid_username; +} + +gboolean +gis_account_page_local_create_user (GisAccountPageLocal *local, + GisPage *page, + GError **error) +{ + return local_create_user (local, page, error); +} + +gboolean +gis_account_page_local_apply (GisAccountPageLocal *local, GisPage *page) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (local); + const gchar *username, *full_name; + gboolean parental_controls_enabled; + + username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (priv->username_combo)); + gis_driver_set_username (GIS_PAGE (page)->driver, username); + + full_name = gtk_entry_get_text (GTK_ENTRY (priv->fullname_entry)); + gis_driver_set_full_name (GIS_PAGE (page)->driver, full_name); + + if (priv->avatar_pixbuf != NULL) + { + gis_driver_set_avatar (GIS_PAGE (page)->driver, priv->avatar_pixbuf); + } + else if (priv->avatar_filename != NULL) + { + g_autoptr(GdkPixbuf) pixbuf = NULL; + + pixbuf = gdk_pixbuf_new_from_file_at_size (priv->avatar_filename, 96, 96, NULL); + gis_driver_set_avatar (GIS_PAGE (page)->driver, pixbuf); + } + +#ifdef HAVE_PARENTAL_CONTROLS + parental_controls_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->enable_parental_controls_check_button)); +#else + parental_controls_enabled = FALSE; +#endif + gis_driver_set_parental_controls_enabled (GIS_PAGE (page)->driver, parental_controls_enabled); + + return FALSE; +} + +void +gis_account_page_local_shown (GisAccountPageLocal *local) +{ + GisAccountPageLocalPrivate *priv = gis_account_page_local_get_instance_private (local); + gtk_widget_grab_focus (priv->fullname_entry); +} diff --git a/gnome-initial-setup/pages/account/gis-account-page-local.h b/gnome-initial-setup/pages/account/gis-account-page-local.h new file mode 100644 index 0000000..1674ad8 --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page-local.h @@ -0,0 +1,60 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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> + */ + +#ifndef __GIS_ACCOUNT_PAGE_LOCAL_H__ +#define __GIS_ACCOUNT_PAGE_LOCAL_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GIS_TYPE_ACCOUNT_PAGE_LOCAL (gis_account_page_local_get_type ()) +#define GIS_ACCOUNT_PAGE_LOCAL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_ACCOUNT_PAGE_LOCAL, GisAccountPageLocal)) +#define GIS_ACCOUNT_PAGE_LOCAL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_ACCOUNT_PAGE_LOCAL, GisAccountPageLocalClass)) +#define GIS_IS_ACCOUNT_PAGE_LOCAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_ACCOUNT_PAGE_LOCAL)) +#define GIS_IS_ACCOUNT_PAGE_LOCAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_ACCOUNT_PAGE_LOCAL)) +#define GIS_ACCOUNT_PAGE_LOCAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_ACCOUNT_PAGE_LOCAL, GisAccountPageLocalClass)) + +typedef struct _GisAccountPageLocal GisAccountPageLocal; +typedef struct _GisAccountPageLocalClass GisAccountPageLocalClass; + +struct _GisAccountPageLocal +{ + GtkBin parent; +}; + +struct _GisAccountPageLocalClass +{ + GtkBinClass parent_class; +}; + +GType gis_account_page_local_get_type (void); + +gboolean gis_account_page_local_validate (GisAccountPageLocal *local); +gboolean gis_account_page_local_apply (GisAccountPageLocal *local, GisPage *page); +gboolean gis_account_page_local_create_user (GisAccountPageLocal *local, + GisPage *page, + GError **error); +void gis_account_page_local_shown (GisAccountPageLocal *local); + +G_END_DECLS + +#endif /* __GIS_ACCOUNT_PAGE_LOCAL_H__ */ diff --git a/gnome-initial-setup/pages/account/gis-account-page-local.ui b/gnome-initial-setup/pages/account/gis-account-page-local.ui new file mode 100644 index 0000000..ccfd7e1 --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page-local.ui @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisAccountPageLocal" parent="GtkBin"> + <child> + <object class="GtkBox" id="area"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkToggleButton" id="avatar_button"> + <property name="visible">True</property> + <property name="margin_top">24</property> + <property name="halign">center</property> + <style> + <class name="avatar-button"/> + </style> + <child internal-child="accessible"> + <object class="AtkObject" id="avatar_button_accessible"> + <property name="accessible-name" translatable="yes">Avatar image</property> + </object> + </child> + <child> + <object class="GtkImage" id="avatar_image"> + <property name="visible">True</property> + <property name="pixel_size">96</property> + <property name="icon_name">avatar-default-symbolic</property> + <property name="icon_size">1</property> + </object> + </child> + </object> + </child> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin_top">18</property> + <property name="title" translatable="yes">About You</property> + <property name="subtitle" translatable="yes">Please provide a name and username. You can choose a picture too.</property> + </object> + </child> + <child> + <object class="GtkGrid" id="form"> + <property name="visible">True</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <property name="margin_top">42</property> + <child> + <object class="GtkLabel" id="fullname_label"> + <property name="visible">True</property> + <property name="halign">end</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Full Name</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">fullname_entry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="fullname_entry"> + <property name="max_length">255</property> + <property name="width-chars">25</property> + <property name="visible">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="username_label"> + <property name="visible">True</property> + <property name="halign">end</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Username</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">comboboxtext-entry</property> + <property name="margin_top">6</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="username_combo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="has_entry">True</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <property name="margin_top">6</property> + <child internal-child="entry"> + <object class="GtkEntry" id="comboboxtext-entry"> + <property name="can_focus">True</property> + <property name="width-chars">25</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="username_explanation"> + <property name="visible">True</property> + <property name="yalign">0</property> + <property name="xalign">0</property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="height-request">50</property> + <property name="wrap">True</property> + <property name="wrap_mode">word-char</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">5</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="enable_parental_controls_box"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="enable_parental_controls_check_button"> + <property name="visible">True</property> + <property name="label" translatable="yes">Set up _parental controls for this user</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">For use by a parent or supervisor, who must set up their own password.</property> + <property name="yalign">0</property> + <property name="xalign">0</property> + <property name="margin-left">24</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">6</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> + diff --git a/gnome-initial-setup/pages/account/gis-account-page-style.css b/gnome-initial-setup/pages/account/gis-account-page-style.css new file mode 100644 index 0000000..0d386ac --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page-style.css @@ -0,0 +1,6 @@ +.avatar-button { + background: transparent; + border: none; + box-shadow: none; + padding: 0; +} diff --git a/gnome-initial-setup/pages/account/gis-account-page.c b/gnome-initial-setup/pages/account/gis-account-page.c new file mode 100644 index 0000000..f319a26 --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page.c @@ -0,0 +1,318 @@ +/* -*- 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> + */ + +/* Account page {{{1 */ + +#define PAGE_ID "account" + +#include "config.h" +#include "account-resources.h" +#include "gis-account-page.h" +#include "gis-account-page-local.h" +#include "gis-account-page-enterprise.h" + +#include <glib/gi18n.h> +#include <gio/gio.h> + +struct _GisAccountPagePrivate +{ + GtkWidget *page_local; + GtkWidget *page_enterprise; + GtkWidget *stack; + + GtkWidget *page_toggle; + GtkWidget *offline_label; + GtkWidget *offline_stack; + + UmAccountMode mode; +}; +typedef struct _GisAccountPagePrivate GisAccountPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisAccountPage, gis_account_page, GIS_TYPE_PAGE); + +static void +enterprise_apply_complete (GisPage *dummy, + gboolean valid, + gpointer user_data) +{ + GisAccountPage *page = GIS_ACCOUNT_PAGE (user_data); + gis_driver_set_username (GIS_PAGE (page)->driver, NULL); + gis_page_apply_complete (GIS_PAGE (page), valid); +} + +static gboolean +page_validate (GisAccountPage *page) +{ + GisAccountPagePrivate *priv = gis_account_page_get_instance_private (page); + + switch (priv->mode) { + case UM_LOCAL: + return gis_account_page_local_validate (GIS_ACCOUNT_PAGE_LOCAL (priv->page_local)); + case UM_ENTERPRISE: + return gis_account_page_enterprise_validate (GIS_ACCOUNT_PAGE_ENTERPRISE (priv->page_enterprise)); + default: + g_assert_not_reached (); + } +} + +static void +update_page_validation (GisAccountPage *page) +{ + gis_page_set_complete (GIS_PAGE (page), page_validate (page)); +} + +static void +on_validation_changed (gpointer page_area, + GisAccountPage *page) +{ + update_page_validation (page); +} + +static void +set_mode (GisAccountPage *page, + UmAccountMode mode) +{ + GisAccountPagePrivate *priv = gis_account_page_get_instance_private (page); + + if (priv->mode == mode) + return; + + priv->mode = mode; + gis_driver_set_account_mode (GIS_PAGE (page)->driver, mode); + + switch (mode) + { + case UM_LOCAL: + gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->page_local); + gis_account_page_local_shown (GIS_ACCOUNT_PAGE_LOCAL (priv->page_local)); + break; + case UM_ENTERPRISE: + gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->page_enterprise); + gis_account_page_enterprise_shown (GIS_ACCOUNT_PAGE_ENTERPRISE (priv->page_enterprise)); + break; + default: + g_assert_not_reached (); + } + + update_page_validation (page); +} + +static void +toggle_mode (GtkToggleButton *button, + gpointer user_data) +{ + set_mode (GIS_ACCOUNT_PAGE (user_data), + gtk_toggle_button_get_active (button) ? UM_ENTERPRISE : UM_LOCAL); +} + +static gboolean +gis_account_page_apply (GisPage *gis_page, + GCancellable *cancellable) +{ + GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page); + GisAccountPagePrivate *priv = gis_account_page_get_instance_private (page); + + switch (priv->mode) { + case UM_LOCAL: + return gis_account_page_local_apply (GIS_ACCOUNT_PAGE_LOCAL (priv->page_local), gis_page); + case UM_ENTERPRISE: + return gis_account_page_enterprise_apply (GIS_ACCOUNT_PAGE_ENTERPRISE (priv->page_enterprise), cancellable, + enterprise_apply_complete, page); + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +gis_account_page_save_data (GisPage *gis_page, + GError **error) +{ + GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page); + GisAccountPagePrivate *priv = gis_account_page_get_instance_private (page); + + switch (priv->mode) { + case UM_LOCAL: + return gis_account_page_local_create_user (GIS_ACCOUNT_PAGE_LOCAL (priv->page_local), gis_page, error); + case UM_ENTERPRISE: + /* Nothing to do. */ + return TRUE; + default: + g_assert_not_reached (); + return FALSE; + } +} + +static void +gis_account_page_shown (GisPage *gis_page) +{ + GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page); + GisAccountPagePrivate *priv = gis_account_page_get_instance_private (page); + + gis_account_page_local_shown (GIS_ACCOUNT_PAGE_LOCAL (priv->page_local)); +} + +static void +on_local_main_user_created (GtkWidget *page_local, + ActUser *user, + const gchar *password, + GisAccountPage *page) +{ + const gchar *language; + + language = gis_driver_get_user_language (GIS_PAGE (page)->driver); + if (language) + act_user_set_language (user, language); + + gis_driver_set_user_permissions (GIS_PAGE (page)->driver, user, password); +} + +static void +on_local_parent_user_created (GtkWidget *page_local, + ActUser *user, + const gchar *password, + GisAccountPage *page) +{ + const gchar *language; + + language = gis_driver_get_user_language (GIS_PAGE (page)->driver); + if (language) + act_user_set_language (user, language); + + gis_driver_set_parent_permissions (GIS_PAGE (page)->driver, user, password); +} + +static void +on_local_page_confirmed (GisAccountPageLocal *local, + GisAccountPage *page) +{ + gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (page)->driver)); +} + +static void +on_local_user_cached (GtkWidget *page_local, + ActUser *user, + char *password, + GisAccountPage *page) +{ + const gchar *language; + + language = gis_driver_get_user_language (GIS_PAGE (page)->driver); + if (language) + act_user_set_language (user, language); + + gis_driver_set_user_permissions (GIS_PAGE (page)->driver, user, password); +} + +static void +on_network_changed (GNetworkMonitor *monitor, + gboolean available, + GisAccountPage *page) +{ + GisAccountPagePrivate *priv = gis_account_page_get_instance_private (page); + + if (!available && priv->mode != UM_ENTERPRISE) + gtk_stack_set_visible_child (GTK_STACK (priv->offline_stack), priv->offline_label); + else + gtk_stack_set_visible_child (GTK_STACK (priv->offline_stack), priv->page_toggle); +} + +static void +gis_account_page_constructed (GObject *object) +{ + GisAccountPage *page = GIS_ACCOUNT_PAGE (object); + GisAccountPagePrivate *priv = gis_account_page_get_instance_private (page); + GNetworkMonitor *monitor; + gboolean available; + + G_OBJECT_CLASS (gis_account_page_parent_class)->constructed (object); + + g_signal_connect (priv->page_local, "validation-changed", + G_CALLBACK (on_validation_changed), page); + g_signal_connect (priv->page_local, "main-user-created", + G_CALLBACK (on_local_main_user_created), page); + g_signal_connect (priv->page_local, "parent-user-created", + G_CALLBACK (on_local_parent_user_created), page); + g_signal_connect (priv->page_local, "confirm", + G_CALLBACK (on_local_page_confirmed), page); + + g_signal_connect (priv->page_enterprise, "validation-changed", + G_CALLBACK (on_validation_changed), page); + g_signal_connect (priv->page_enterprise, "user-cached", + G_CALLBACK (on_local_user_cached), page); + + update_page_validation (page); + + g_signal_connect (priv->page_toggle, "toggled", G_CALLBACK (toggle_mode), page); + g_object_bind_property (page, "applying", priv->page_toggle, "sensitive", G_BINDING_INVERT_BOOLEAN); + g_object_bind_property (priv->page_enterprise, "visible", priv->offline_stack, "visible", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + /* force a refresh by setting to an invalid value */ + priv->mode = NUM_MODES; + set_mode (page, UM_LOCAL); + + monitor = g_network_monitor_get_default (); + available = g_network_monitor_get_network_available (monitor); + on_network_changed (monitor, available, page); + g_signal_connect_object (monitor, "network-changed", G_CALLBACK (on_network_changed), page, 0); + + gtk_widget_show (GTK_WIDGET (page)); +} + +static void +gis_account_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("About You")); +} + +static void +gis_account_page_class_init (GisAccountPageClass *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-account-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPage, page_local); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPage, page_enterprise); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPage, stack); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPage, page_toggle); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPage, offline_label); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisAccountPage, offline_stack); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_account_page_locale_changed; + page_class->apply = gis_account_page_apply; + page_class->save_data = gis_account_page_save_data; + page_class->shown = gis_account_page_shown; + object_class->constructed = gis_account_page_constructed; +} + +static void +gis_account_page_init (GisAccountPage *page) +{ + g_resources_register (account_get_resource ()); + g_type_ensure (GIS_TYPE_ACCOUNT_PAGE_LOCAL); + g_type_ensure (GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE); + + gtk_widget_init_template (GTK_WIDGET (page)); +} diff --git a/gnome-initial-setup/pages/account/gis-account-page.h b/gnome-initial-setup/pages/account/gis-account-page.h new file mode 100644 index 0000000..7629e1a --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page.h @@ -0,0 +1,55 @@ +/* -*- 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> + */ + +#ifndef __GIS_ACCOUNT_PAGE_H__ +#define __GIS_ACCOUNT_PAGE_H__ + +#include <glib-object.h> + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_ACCOUNT_PAGE (gis_account_page_get_type ()) +#define GIS_ACCOUNT_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_ACCOUNT_PAGE, GisAccountPage)) +#define GIS_ACCOUNT_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_ACCOUNT_PAGE, GisAccountPageClass)) +#define GIS_IS_ACCOUNT_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_ACCOUNT_PAGE)) +#define GIS_IS_ACCOUNT_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_ACCOUNT_PAGE)) +#define GIS_ACCOUNT_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_ACCOUNT_PAGE, GisAccountPageClass)) + +typedef struct _GisAccountPage GisAccountPage; +typedef struct _GisAccountPageClass GisAccountPageClass; + +struct _GisAccountPage +{ + GisPage parent; +}; + +struct _GisAccountPageClass +{ + GisPageClass parent_class; +}; + +GType gis_account_page_get_type (void); + +G_END_DECLS + +#endif /* __GIS_ACCOUNT_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/account/gis-account-page.ui b/gnome-initial-setup/pages/account/gis-account-page.ui new file mode 100644 index 0000000..f2cd51f --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-page.ui @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisAccountPage" parent="GisPage"> + <child> + <object class="GtkBox" id="page"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="valign">start</property> + <property name="vexpand">True</property> + <child> + <object class="GisAccountPageLocal" id="page_local"> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GisAccountPageEnterprise" id="page_enterprise"> + <property name="visible">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkStack" id="offline_stack"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">end</property> + <property name="margin_bottom">18</property> + <child> + <object class="GtkToggleButton" id="page_toggle"> + <property name="visible">True</property> + <property name="use_underline">True</property> + <property name="label" translatable="yes">_Enterprise Login</property> + <property name="halign">center</property> + <property name="valign">center</property> + </object> + </child> + <child> + <object class="GtkLabel" id="offline_label"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Go online to set up Enterprise Login.</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/account/gis-account-pages.c b/gnome-initial-setup/pages/account/gis-account-pages.c new file mode 100644 index 0000000..d9cc8d9 --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-pages.c @@ -0,0 +1,32 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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 "gis-account-pages.h" +#include "gis-account-page.h" + +GisPage * +gis_prepare_account_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_ACCOUNT_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/account/gis-account-pages.h b/gnome-initial-setup/pages/account/gis-account-pages.h new file mode 100644 index 0000000..394421b --- /dev/null +++ b/gnome-initial-setup/pages/account/gis-account-pages.h @@ -0,0 +1,36 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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> + */ + +#ifndef __GIS_ACCOUNT_PAGES_H__ +#define __GIS_ACCOUNT_PAGES_H__ + +#include <glib-object.h> + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +GisPage *gis_prepare_account_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_ACCOUNT_PAGES_H__ */ + diff --git a/gnome-initial-setup/pages/account/meson.build b/gnome-initial-setup/pages/account/meson.build new file mode 100644 index 0000000..1130465 --- /dev/null +++ b/gnome-initial-setup/pages/account/meson.build @@ -0,0 +1,33 @@ +realmd_namespace = 'org.freedesktop.realmd' +sources += gnome.gdbus_codegen( + 'um-realm-generated', + realmd_namespace + '.xml', + interface_prefix: realmd_namespace + '.', + namespace: 'UmRealm', + object_manager: true, + annotations: ['org.freedesktop.realmd.Realm', 'org.gtk.GDBus.C.Name', 'Common'] +) + +sources += gnome.compile_resources( + 'account-resources', + files('account.gresource.xml'), + c_name: 'account' +) + +sources += files( + 'gis-account-page.c', + 'gis-account-page.h', + 'gis-account-pages.c', + 'gis-account-pages.h', + 'gis-account-page-local.c', + 'gis-account-page-local.h', + 'gis-account-page-enterprise.c', + 'gis-account-page-enterprise.h', + 'um-realm-manager.c', + 'um-realm-manager.h', + 'um-utils.c', + 'um-photo-dialog.c', + 'um-photo-dialog.h' +) + +account_sources_dir = meson.current_source_dir() diff --git a/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml b/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml new file mode 100644 index 0000000..316213a --- /dev/null +++ b/gnome-initial-setup/pages/account/org.freedesktop.realmd.xml @@ -0,0 +1,666 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/"> + + <!-- + org.freedesktop.realmd.Provider: + @short_description: a realm provider + + Various realm providers represent different software implementations + that provide access to realms or domains. + + This interface is implemented by individual providers, but is + aggregated globally at the system bus name + <literal>org.freedesktop.realmd</literal> + with the object path <literal>/org/freedesktop/realmd</literal> + --> + <interface name="org.freedesktop.realmd.Provider"> + + <!-- + Name: the name of the provider + + The name of the provider. This is not normally displayed + to the user, but may be useful for diagnostics or debugging. + --> + <property name="Name" type="s" access="read"/> + + <!-- + Version: the version of the provider + + The version of the provider. This is not normally used in + logic, but may be useful for diagnostics or debugging. + --> + <property name="Version" type="s" access="read"/> + + <!-- + Realms: a list of realms + + A list of known, enrolled or discovered realms. All realms + that this provider knows about are listed here. As realms + are discovered they are added to this list. + + Each realm is represented by the DBus object path of the + realm object. + --> + <property name="Realms" type="ao" access="read"/> + + <!-- + Discover: + @string: an input string to discover realms for + @options: options for the discovery operation + @relevance: the relevance of the returned results + @realm: a list of realms discovered + + Discover realms for the given string. The input @string is + usually a domain or realm name, perhaps typed by a user. If + an empty string is provided the realm provider should try to + discover a default realm if possible (eg: from DHCP). + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + The @relevance returned can be used to rank results from + different discover calls to different providers. Implementors + should return a positive number if the provider highly + recommends that the realms be handled by this provider, + or a zero if it can possibly handle the realms. Negative + should be returned if no realms are found. + + This method does not return an error when no realms are + discovered. It simply returns an @realm list. + + To see diagnostic information about the discovery process + connect to the org.freedesktop.realmd.Service::Diagnostics + signal. + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.discover-realm</literal>. + + In addition to common DBus error results, this method may + return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the discovery could not be run for some reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to perform a discovery + operation.</para></listitem> + </itemizedlist> + --> + <method name="Discover"> + <arg name="string" type="s" direction="in"/> + <arg name="options" type="a{sv}" direction="in"/> + <arg name="relevance" type="i" direction="out"/> + <arg name="realm" type="ao" direction="out"/> + </method> + + </interface> + + <!-- + org.freedesktop.realmd.Service: + @short_description: the realmd service + + Global calls for managing the realmd service. Usually you'll want + to use #org.freedesktop.realmd.Provider instead. + + This interface is implemented by the realmd service, and is always + available at the object path <literal>/org/freedesktop/realmd</literal> + + The service also implements the + <literal>org.freedesktop.DBus.ObjectManager</literal> interface which + makes it easy to retrieve all realmd objects and properties in one go. + --> + <interface name="org.freedesktop.realmd.Service"> + + <!-- + Cancel: + @operation: the operation to cancel + + Cancel a realmd operation. To be able to cancel an operation + pass a uniquely chosen <literal>operation</literal> string + identifier as an option in the methods <literal>options</literal> + argument. + + These operation string identifiers should be unique per client + calling the realmd service. + + It is not guaranteed that the service can or will cancel the + operation. For example the operation may have already completed + by the time this method is handled. The caller of the operation + method will receive a + <literal>org.freedesktop.realmd.Error.Cancelled</literal> + if the operation was cancelled. + --> + <method name="Cancel"> + <arg name="operation" type="s" direction="in"/> + </method> + + <!-- + SetLocale: + @locale: the locale for the client + + Set the language @locale for the client. This locale is used + for error messages. The locale is used until the next time + this method is called, the client disconnects, or the client + calls #org.freedesktop.realmd.Service.Release(). + --> + <method name="SetLocale"> + <arg name="locale" type="s" direction="in"/> + </method> + + <!-- + Diagnostics: + @data: diagnostic data + @operation: the operation this data resulted from + + This signal is fired when diagnostics result from an operation + in the provider or one of its realms. + + It is not guaranteed that this signal is emitted once per line. + More than one line may be contained in @data, or a partial + line. New line characters are embedded in @data. + + This signal is sent explicitly to the client which invoked + operation method. In order to tell which operation this + diagnostic data results from, pass a unique + <literal>operation</literal> string identifier in the + <literal>options</literal> argument of the operation method. + That same identifier will be passed back via the @operation + argument of this signal. + --> + <signal name="Diagnostics"> + <arg name="data" type="s"/> + <arg name="operation" type="s"/> + </signal> + + <!-- + Release: + + Normally realmd waits until all clients have disconnected + before exiting itself, sometime later. For long lived clients + they can call this method to allow the realmd service to quit. + This is an optimization. The daemon will not exit immediately. + It is safe to call this multiple times. + --> + <method name="Release"> + <!-- no arguments --> + </method> + + </interface> + + <!-- + org.freedesktop.realmd.Realm: + @short_description: a realm + + Represents one realm. + + Contains generic information about a realm, and useful properties for + introspecting what kind of realm this is and how to work with + the realm. + + Use #org.freedesktop.realmd.Provider:Realms or + #org.freedesktop.realmd.Provider.Discover() to get access to some + kerberos realm objects. + + Realms will always implement additional interfaces, such as + #org.freedesktop.realmd.Kerberos. Do not assume that all realms + implement that kerberos interface. Use the + #org.freedesktop.realmd.Realm:SupportedInterfaces property to see + which interfaces are set. + + Different realms support various ways to configure them on the + system. Use the #org.freedesktop.realmd.Realm:Configured property + to determine if a realm is configured. If it is configured the + property will be set to the interface of the mechanism that was + used to configure it. + + To configure a realm, look in the + #org.freedesktop.realmd.Realm:SupportedInterfaces property for a + recognized purpose specific interface that can be used for + configuration, such as the + #org.freedesktop.realmd.KerberosMembership interface and its + #org.freedesktop.realmd.KerberosMembership.Join() method. + + To deconfigure a realm from the current system, you can use the + #org.freedesktop.realmd.Realm.Deconfigure() method. In additon some + of the configuration specific interfaces provide methods to + deconfigure a realm in a specific way, such as + #org.freedesktop.realmd.KerberosMembership.Leave() method. + + The various properties are guaranteed to have been updated before + the operation methods return, if they change state. + --> + <interface name="org.freedesktop.realmd.Realm"> + + <!-- + Name: the realm name + + This is the name of the realm, appropriate for display to + end users where necessary. + --> + <property name="Name" type="s" access="read"/> + + <!-- + Configured: whether this domain is configured and how + + If this property is an empty string, then the realm is not + configured. Otherwise the realm is configured, and contains + a string which is the interface that represents how it was + configured, for example #org.freedesktop.realmd.KerberosMembership. + --> + <property name="Configured" type="s" access="read"/> + + <!-- + Deconfigure: deconfigure this realm + + Deconfigure this realm from the local machine with standard + default behavior. + + The behavior of this method depends on the which configuration + interface is present in the + #org.freedesktop.realmd.Realm.Configured property. It does not + always delete membership accounts in the realm, but just + reconfigures the local machine so it no longer is configured + for the given realm. In some cases the implementation may try + to update membership accounts, but this is not guaranteed. + + Various configuration interfaces may support more specific ways + to deconfigure a realm in a specific way, such as the + #org.freedesktop.realmd.KerberosMembership.Leave() method. + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.deconfigure-realm</literal>. + + In addition to common DBus error results, this method may return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the deconfigure failed for a generic reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to deconfigure a + realm.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>: + returned if this realm is not configured on the machine.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>: + returned if the service is currently performing another operation like + join or leave.</para></listitem> + </itemizedlist> + --> + <method name="Deconfigure"> + <arg name="options" type="a{sv}" direction="in"/> + </method> + + <!-- + SupportedInterfaces: + + Additional supported interfaces of this realm. This includes + interfaces that contain more information about the realm, + such as #org.freedesktop.realmd.Kerberos and interfaces + which contain methods for configuring a realm, such as + #org.freedesktop.realmd.KerberosMembership. + --> + <property name="SupportedInterfaces" type="as" access="read"/> + + <!-- + Details: informational details about the realm + + Informational details about the realm. The following values + should be present: + <itemizedlist> + <listitem><para><literal>server-software</literal>: + identifier of the software running on the server (eg: + <literal>active-directory</literal>).</para></listitem> + <listitem><para><literal>client-software</literal>: + identifier of the software running on the client (eg: + <literal>sssd</literal>).</para></listitem> + </itemizedlist> + --> + <property name="Details" type="a(ss)" access="read"/> + + <!-- + LoginFormats: supported formats for login names + + Supported formats for login to this realm. This is only + relevant once the realm has been enrolled. The formats + will contain a <literal>%U</literal> in the string, which + indicate where the user name should be placed. The formats + may contain a <literal>%D</literal> in the string which + indicate where a domain name should be placed. + + The first format in the list is the preferred format for + login names. + --> + <property name="LoginFormats" type="as" access="read"/> + + <!-- + LoginPolicy: the policy for logins using this realm + + The policy for logging into this computer using this realm. + + The policy can be changed using the + #org.freedesktop.realmd.Realm.ChangeLoginPolicy() method. + + The following policies are predefined. Not all providers + support all these policies and there may be provider specific + policies or multiple policies represented in the string: + <itemizedlist> + <listitem><para><literal>allow-any-login</literal>: allow + login by any authenticated user present in this + realm.</para></listitem> + <listitem><para><literal>allow-permitted-logins</literal>: + only allow the logins permitted in the + #org.freedesktop.realmd.Realm:PermittedLogins + property.</para></listitem> + <listitem><para><literal>deny-any-login</literal>: + don't allow any logins via authenticated users of this + realm.</para></listitem> + </itemizedlist> + --> + <property name="LoginPolicy" type="s" access="read"/> + + <!-- + PermittedLogins: the permitted login names + + The list of permitted authenticated users allowed to login + into this computer. This is only relevant if the + #org.freedesktop.realmd.Realm:LoginPolicy property + contains the <literal>allow-permitted-logins</literal> + string. + --> + <property name="PermittedLogins" type="as" access="read"/> + + <!-- + ChangeLoginPolicy: + @login_policy: the new login policy, or an empty string + @permitted_add: a list of logins to permit + @permitted_remove: a list of logins to not permit + @options: options for this operation + + Change the login policy and/or permitted logins for this realm. + + Not all realms support the all the various login policies. An + error will be returned if the new login policy is not supported. + You may specify an empty string for the @login_policy argument + which will cause no change in the policy itself. If the policy + is changed, it will be reflected in the + #org.freedesktop.realmd.Realm:LoginPolicy property. + + The @permitted_add and @permitted_remove arguments represent + lists of login names that should be added and removed from + the #org.freedesktop.realmd.Kerberos:PermittedLogins property. + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.login-policy</literal>. + + In addition to common DBus error results, this method may return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the policy change failed for a generic reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to change login policy + operation.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>: + returned if the realm is not configured.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>: + returned if the service is currently performing another operation like + join or leave.</para></listitem> + </itemizedlist> + --> + <method name="ChangeLoginPolicy"> + <arg name="login_policy" type="s" direction="in"/> + <arg name="permitted_add" type="as" direction="in"/> + <arg name="permitted_remove" type="as" direction="in"/> + <arg name="options" type="a{sv}" direction="in"/> + </method> + + </interface> + + <!-- + org.freedesktop.realmd.Kerberos: + @short_description: a kerberos realm + + An interface that describes a kerberos realm in more detail. This + is always implemented on an DBus object path that also implements + the #org.freedesktop.realmd.Realm interface. + --> + <interface name="org.freedesktop.realmd.Kerberos"> + + <!-- + RealmName: the kerberos realm name + + The kerberos name for this realm. This is usually in upper + case. + --> + <property name="RealmName" type="s" access="read"/> + + <!-- + DomainName: the DNS domain name + + The DNS domain name for this realm. + --> + <property name="DomainName" type="s" access="read"/> + + </interface> + + <!-- + org.freedesktop.realmd.KerberosMembership: + + An interface used to configure this machine by joining a realm. + + It sets up a computer/host account in the realm for this machine + and a keytab to track the credentials for that account. + + The various properties are guaranteed to have been updated before + the operation methods return, if they change state. + --> + <interface name="org.freedesktop.realmd.KerberosMembership"> + + <!-- + SuggestedAdministrator: common administrator name + + The common administrator name for this type of realm. This + can be used by clients as a hint when prompting the user for + administrative authentication. + --> + <property name="SuggestedAdministrator" type="s" access="read"/> + + <!-- + SupportedJoinCredentials: credentials supported for joining + + Various kinds of credentials that are supported when calling the + #org.freedesktop.realmd.Kerberos.Join() method. + + Each credential is represented by a type, and an owner. The type + denotes which kind of credential is passed to the method. The + owner indicates to the client how to prompt the user or obtain + the credential, and to the service how to use the credential. + + The various types are: + <itemizedlist> + <listitem><para><literal>ccache</literal>: + the credentials should contain an array of bytes as a + <literal>ay</literal> containing the data from a kerberos + credential cache file.</para></listitem> + <listitem><para><literal>password</literal>: + the credentials should contain a pair of strings as a + <literal>(ss)</literal> representing a name and + password. The name may contain a realm in the standard + kerberos format. If missing, it will default to this + realm. The name may be empty for a computer or one time + password.</para></listitem> + <listitem><para><literal>automatic</literal>: + the credentials should contain an empty string as a + <literal>s</literal>. Using <literal>automatic</literal> + indicates that default or system credentials are to be + used.</para></listitem> + </itemizedlist> + + The various owners are: + <itemizedlist> + <listitem><para><literal>administrator</literal>: + the credentials belong to a kerberos user principal. + The caller may use this as a hint to prompt the user + for administrative credentials.</para></listitem> + <listitem><para><literal>user</literal>: + the credentials belong to a kerberos user principal. + The caller may use this as a hint to prompt the user + for his (possibly non-administrative) + credentials.</para></listitem> + <listitem><para><literal>computer</literal>: + the credentials belong to the computer realmd is + being run on.</para></listitem> + <listitem><para><literal>secret</literal>: + the credentials are a one time password or other secret + used to join or leave the computer.</para></listitem> + </itemizedlist> + --> + <property name="SupportedJoinCredentials" type="a(ss)" access="read"/> + + <!-- + SupportedLeaveCredentials: credentials supported for leaving + + Various kinds of credentials that are supported when calling the + #org.freedesktop.realmd.Kerberos.Leave() method. + + See #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials for + a discussion of what the values represent. + --> + <property name="SupportedLeaveCredentials" type="a(ss)" access="read"/> + + <!-- + Join: + + Join this machine to the realm and enroll the machine. + + If this method returns successfully then the machine will be + joined to the realm. It is not necessary to restart services or the + machine afterward. Relevant properties on the realm will be updated + before the method returns. + + The @credentials should be set according to one of the + supported credentials returned by + #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials. + The first string in the tuple is the type, the second string + is the owner, and the variant contains the credential contents + See the discussion at + #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials + for more information. + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.configure-realm</literal>. + + In addition to common DBus error results, this method may return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the join failed for a generic reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to perform an join + operation.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>: + returned if the credentials passed did not authenticate against the realm + correctly. It is appropriate to prompt the user again.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.AlreadyEnrolled</literal>: + returned if already enrolled in this realm, or another realm and enrolling + in multiple realms is not supported.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>: + returned if the service is currently performing another operation like + join or leave.</para></listitem> + </itemizedlist> + --> + <method name="Join"> + <arg name="credentials" type="(ssv)" direction="in"/> + <arg name="options" type="a{sv}" direction="in"/> + </method> + + <!-- + Leave: + + Leave the realm and unenroll the machine. + + If this method returns successfully then the machine will have + left the domain and been unenrolled. It is not necessary to restart + services or the machine afterward. Relevant properties on the realm + will be updated before the method returns. + + The @credentials should be set according to one of the + supported credentials returned by + #org.freedesktop.realmd.Kerberos:SupportedUnenrollCredentials. + The first string in the tuple is the type, the second string + is the owner, and the variant contains the credential contents + See the discussion at + #org.freedesktop.realmd.Kerberos:SupportedEnrollCredentials + for more information. + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.deconfigure-realm</literal>. + + In addition to common DBus error results, this method may return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the unenroll failed for a generic reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to perform an unenroll + operation.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>: + returned if the credentials passed did not authenticate against the realm + correctly. It is appropriate to prompt the user again.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotEnrolled</literal>: + returned if not enrolled in this realm.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>: + returned if the service is currently performing another operation like + enroll or unenroll.</para></listitem> + </itemizedlist> + --> + <method name="Leave"> + <arg name="credentials" type="(ssv)" direction="in"/> + <arg name="options" type="a{sv}" direction="in"/> + </method> + + </interface> + +</node> diff --git a/gnome-initial-setup/pages/account/um-photo-dialog.c b/gnome-initial-setup/pages/account/um-photo-dialog.c new file mode 100644 index 0000000..32f90c1 --- /dev/null +++ b/gnome-initial-setup/pages/account/um-photo-dialog.c @@ -0,0 +1,475 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 Red Hat, Inc, + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#ifdef HAVE_CHEESE +#include <cheese-avatar-chooser.h> +#include <cheese-camera-device.h> +#include <cheese-camera-device-monitor.h> +#endif /* HAVE_CHEESE */ + +#include "um-photo-dialog.h" +#include "um-utils.h" + +#define ROW_SPAN 5 +#define AVATAR_PIXEL_SIZE 72 + +struct _UmPhotoDialog { + GtkPopover parent; + + GtkWidget *popup_button; + GtkWidget *take_picture_button; + GtkWidget *flowbox; + GtkWidget *recent_pictures; + +#ifdef HAVE_CHEESE + CheeseCameraDeviceMonitor *monitor; + GCancellable *cancellable; + guint num_cameras; +#endif /* HAVE_CHEESE */ + + GListStore *recent_faces; + GListStore *faces; + GFile *generated_avatar; + gboolean custom_avatar_was_chosen; + + SelectAvatarCallback *callback; + gpointer data; +}; + +G_DEFINE_TYPE (UmPhotoDialog, um_photo_dialog, GTK_TYPE_POPOVER) + +#ifdef HAVE_CHEESE +static gboolean +destroy_chooser (GtkWidget *chooser) +{ + gtk_widget_destroy (chooser); + return FALSE; +} + +static void +webcam_response_cb (GtkDialog *dialog, + int response, + UmPhotoDialog *um) +{ + if (response == GTK_RESPONSE_ACCEPT) { + GdkPixbuf *pb, *pb2; + + g_object_get (G_OBJECT (dialog), "pixbuf", &pb, NULL); + pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR); + + um->callback (pb2, NULL, um->data); + um->custom_avatar_was_chosen = TRUE; + + g_object_unref (pb2); + g_object_unref (pb); + } + if (response != GTK_RESPONSE_DELETE_EVENT && + response != GTK_RESPONSE_NONE) + g_idle_add ((GSourceFunc) destroy_chooser, dialog); +} + +static void +webcam_icon_selected (UmPhotoDialog *um) +{ + GtkWidget *window; + + window = cheese_avatar_chooser_new (); + gtk_window_set_transient_for (GTK_WINDOW (window), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (um)))); + gtk_window_set_modal (GTK_WINDOW (window), TRUE); + g_signal_connect (G_OBJECT (window), "response", + G_CALLBACK (webcam_response_cb), um); + gtk_widget_show (window); + + gtk_popover_popdown (GTK_POPOVER (um)); +} + +static void +update_photo_menu_status (UmPhotoDialog *um) +{ + gtk_widget_set_visible (um->take_picture_button, um->num_cameras != 0); +} + +static void +device_added (CheeseCameraDeviceMonitor *monitor, + CheeseCameraDevice *device, + UmPhotoDialog *um) +{ + um->num_cameras++; + update_photo_menu_status (um); +} + +static void +device_removed (CheeseCameraDeviceMonitor *monitor, + const char *id, + UmPhotoDialog *um) +{ + um->num_cameras--; + update_photo_menu_status (um); +} + +static void +setup_cheese_camera_device_monitor (UmPhotoDialog *um) +{ + g_signal_connect (G_OBJECT (um->monitor), "added", G_CALLBACK (device_added), um); + g_signal_connect (G_OBJECT (um->monitor), "removed", G_CALLBACK (device_removed), um); + cheese_camera_device_monitor_coldplug (um->monitor); +} + +static void +cheese_camera_device_monitor_new_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + UmPhotoDialog *um = user_data; + GObject *ret; + + ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, NULL); + if (ret == NULL) + return; + + um->monitor = CHEESE_CAMERA_DEVICE_MONITOR (ret); + setup_cheese_camera_device_monitor (um); +} +#else /* ! HAVE_CHEESE */ +static void +webcam_icon_selected (UmPhotoDialog *um) +{ + g_warning ("Webcam icon selected, but compiled without Cheese support"); +} +#endif /* HAVE_CHEESE */ + +static void +face_widget_activated (GtkFlowBox *flowbox, + GtkFlowBoxChild *child, + UmPhotoDialog *um) +{ + const char *filename; + GtkWidget *image; + + image = gtk_bin_get_child (GTK_BIN (child)); + filename = g_object_get_data (G_OBJECT (image), "filename"); + + um->callback (NULL, filename, um->data); + um->custom_avatar_was_chosen = TRUE; + + gtk_popover_popdown (GTK_POPOVER (um)); +} + +static void +generated_avatar_activated (GtkFlowBox *flowbox, + GtkFlowBoxChild *child, + UmPhotoDialog *um) +{ + face_widget_activated (flowbox, child, um); + um->custom_avatar_was_chosen = FALSE; +} + +static GtkWidget * +create_face_widget (gpointer item, + gpointer user_data) +{ + g_autoptr(GdkPixbuf) pixbuf = NULL; + GtkWidget *image; + g_autofree gchar *path = g_file_get_path (G_FILE (item)); + + pixbuf = gdk_pixbuf_new_from_file_at_size (path, + AVATAR_PIXEL_SIZE, + AVATAR_PIXEL_SIZE, + NULL); + + if (pixbuf != NULL) + image = gtk_image_new_from_pixbuf (round_image (pixbuf)); + else + image = gtk_image_new (); + + gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_PIXEL_SIZE); + + gtk_widget_show (image); + + g_object_set_data_full (G_OBJECT (image), + "filename", g_steal_pointer (&path), + (GDestroyNotify) g_free); + + return image; +} + +static GStrv +get_settings_facesdirs (void) +{ + g_autoptr(GSettingsSchema) schema = NULL; + g_autoptr(GPtrArray) facesdirs = g_ptr_array_new (); + g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface"); + g_auto(GStrv) settings_dirs = g_settings_get_strv (settings, "avatar-directories"); + + if (settings_dirs != NULL) { + int i; + for (i = 0; settings_dirs[i] != NULL; i++) { + char *path = settings_dirs[i]; + if (path != NULL && g_strcmp0 (path, "") != 0) + g_ptr_array_add (facesdirs, g_strdup (path)); + } + } + + // NULL terminated array + g_ptr_array_add (facesdirs, NULL); + return (GStrv) g_steal_pointer (&facesdirs->pdata); +} + +static GStrv +get_system_facesdirs (void) +{ + g_autoptr(GPtrArray) facesdirs = NULL; + const char * const * data_dirs; + int i; + + facesdirs = g_ptr_array_new (); + + data_dirs = g_get_system_data_dirs (); + for (i = 0; data_dirs[i] != NULL; i++) { + char *path = g_build_filename (data_dirs[i], "pixmaps", "faces", NULL); + g_ptr_array_add (facesdirs, path); + } + + // NULL terminated array + g_ptr_array_add (facesdirs, NULL); + return (GStrv) g_steal_pointer (&facesdirs->pdata); +} + +static gboolean +add_faces_from_dirs (GListStore *faces, GStrv facesdirs, gboolean add_all) +{ + gboolean added_faces = FALSE; + const gchar *target; + int i; + GFileType type; + + for (i = 0; facesdirs[i] != NULL; i++) { + g_autoptr(GFileEnumerator) enumerator = NULL; + g_autoptr(GFile) dir = NULL; + const char *path = facesdirs[i]; + gpointer infoptr; + + dir = g_file_new_for_path (path); + enumerator = g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," + G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (enumerator == NULL) + continue; + + while ((infoptr = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) { + g_autoptr (GFileInfo) info = infoptr; + g_autoptr (GFile) face_file = NULL; + + type = g_file_info_get_file_type (info); + if (type != G_FILE_TYPE_REGULAR && type != G_FILE_TYPE_SYMBOLIC_LINK) + continue; + + target = g_file_info_get_symlink_target (info); + if (target != NULL && g_str_has_prefix (target , "legacy/")) + continue; + + face_file = g_file_get_child (dir, g_file_info_get_name (info)); + g_list_store_append (faces, face_file); + added_faces = TRUE; + } + + g_file_enumerator_close (enumerator, NULL, NULL); + + if (added_faces && !add_all) + break; + } + return added_faces; +} + +static void +setup_photo_popup (UmPhotoDialog *um) +{ + g_auto(GStrv) facesdirs; + gboolean added_faces = FALSE; + + um->faces = g_list_store_new (G_TYPE_FILE); + gtk_flow_box_bind_model (GTK_FLOW_BOX (um->flowbox), + G_LIST_MODEL (um->faces), + create_face_widget, + um, + NULL); + + g_signal_connect (um->flowbox, "child-activated", + G_CALLBACK (face_widget_activated), um); + + um->recent_faces = g_list_store_new (G_TYPE_FILE); + gtk_flow_box_bind_model (GTK_FLOW_BOX (um->recent_pictures), + G_LIST_MODEL (um->recent_faces), + create_face_widget, + um, + NULL); + g_signal_connect (um->recent_pictures, "child-activated", + G_CALLBACK (generated_avatar_activated), um); + um->custom_avatar_was_chosen = FALSE; + + facesdirs = get_settings_facesdirs (); + added_faces = add_faces_from_dirs (um->faces, facesdirs, TRUE); + + if (!added_faces) { + facesdirs = get_system_facesdirs (); + add_faces_from_dirs (um->faces, facesdirs, FALSE); + } + +#ifdef HAVE_CHEESE + um->cancellable = g_cancellable_new (); + g_async_initable_new_async (CHEESE_TYPE_CAMERA_DEVICE_MONITOR, + G_PRIORITY_DEFAULT, + um->cancellable, + cheese_camera_device_monitor_new_cb, + um, + NULL); +#endif /* HAVE_CHEESE */ +} + +static void +popup_icon_menu (GtkToggleButton *button, UmPhotoDialog *um) +{ + gtk_popover_popup (GTK_POPOVER (um)); +} + +static gboolean +on_popup_button_button_pressed (GtkToggleButton *button, + GdkEventButton *event, + UmPhotoDialog *um) +{ + if (event->button == 1) { + if (!gtk_widget_get_visible (GTK_WIDGET (um))) { + popup_icon_menu (button, um); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + } else { + gtk_popover_popdown (GTK_POPOVER (um)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE); + } + + return TRUE; + } + + return FALSE; +} + +void +um_photo_dialog_generate_avatar (UmPhotoDialog *um, + const gchar *name) +{ + cairo_surface_t *surface; + gchar *filename; + + surface = generate_user_picture (name); + + /* Save into a tmp file that later gets copied by AccountsService */ + filename = g_build_filename (g_get_user_runtime_dir (), "avatar.png", NULL); + um->generated_avatar = g_file_new_for_path (filename); + cairo_surface_write_to_png (surface, g_file_get_path (um->generated_avatar)); + g_free (filename); + + /* Overwrite the first item */ + if (g_list_model_get_item (G_LIST_MODEL (um->recent_faces), 0) != NULL) + g_list_store_remove (um->recent_faces, 0); + + g_list_store_insert (um->recent_faces, 0, + um->generated_avatar); + gtk_widget_show_all (um->recent_pictures); + + if (!um->custom_avatar_was_chosen) { + um->callback (NULL, g_file_get_path (um->generated_avatar), um->data); + } +} + +UmPhotoDialog * +um_photo_dialog_new (GtkWidget *button, + SelectAvatarCallback callback, + gpointer data) +{ + UmPhotoDialog *um; + + um = g_object_new (UM_TYPE_PHOTO_DIALOG, + "relative-to", button, + NULL); + + /* Set up the popup */ + um->popup_button = button; + setup_photo_popup (um); + g_signal_connect (button, "toggled", + G_CALLBACK (popup_icon_menu), um); + g_signal_connect (button, "button-press-event", + G_CALLBACK (on_popup_button_button_pressed), um); + + um->callback = callback; + um->data = data; + + return um; +} + +void +um_photo_dialog_dispose (GObject *object) +{ +#ifdef HAVE_CHEESE + UmPhotoDialog *um = UM_PHOTO_DIALOG (object); + + g_cancellable_cancel (um->cancellable); + g_clear_object (&um->cancellable); + g_clear_object (&um->monitor); +#endif + + G_OBJECT_CLASS (um_photo_dialog_parent_class)->dispose (object); +} + +static void +um_photo_dialog_init (UmPhotoDialog *um) +{ + gtk_widget_init_template (GTK_WIDGET (um)); +} + +static void +um_photo_dialog_class_init (UmPhotoDialogClass *klass) +{ + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass); + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/initial-setup/gis-account-avatar-chooser.ui"); + + gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, flowbox); + gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, recent_pictures); + gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, take_picture_button); + gtk_widget_class_bind_template_callback (wclass, webcam_icon_selected); + + oclass->dispose = um_photo_dialog_dispose; +} diff --git a/gnome-initial-setup/pages/account/um-photo-dialog.h b/gnome-initial-setup/pages/account/um-photo-dialog.h new file mode 100644 index 0000000..20a1e7d --- /dev/null +++ b/gnome-initial-setup/pages/account/um-photo-dialog.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 Red Hat, Inc, + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#ifndef __UM_PHOTO_DIALOG_H__ +#define __UM_PHOTO_DIALOG_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define UM_TYPE_PHOTO_DIALOG (um_photo_dialog_get_type()) + +G_DECLARE_FINAL_TYPE (UmPhotoDialog, um_photo_dialog, UM, PHOTO_DIALOG, GtkPopover) + +typedef struct _UmPhotoDialog UmPhotoDialog; +typedef void (SelectAvatarCallback) (GdkPixbuf *pixbuf, + const gchar *filename, + gpointer data); + +UmPhotoDialog *um_photo_dialog_new (GtkWidget *button, + SelectAvatarCallback callback, + gpointer data); +void um_photo_dialog_free (UmPhotoDialog *dialog); + +void um_photo_dialog_generate_avatar (UmPhotoDialog *dialog, + const gchar *name); + +G_END_DECLS + +#endif diff --git a/gnome-initial-setup/pages/account/um-realm-manager.c b/gnome-initial-setup/pages/account/um-realm-manager.c new file mode 100644 index 0000000..bc4fd33 --- /dev/null +++ b/gnome-initial-setup/pages/account/um-realm-manager.c @@ -0,0 +1,878 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2012 Red Hat, Inc. + * + * 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: Matthias Clasen <mclasen@redhat.com> + * Stef Walter <stefw@gnome.org> + */ + +#include "config.h" + +#include "um-realm-manager.h" + +#include <krb5/krb5.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> + + +struct _UmRealmManager { + UmRealmObjectManagerClient parent; + UmRealmProvider *provider; +}; + +typedef struct { + UmRealmProviderProxyClass parent_class; +} UmRealmManagerClass; + +enum { + REALM_ADDED, + NUM_SIGNALS, +}; + +static gint signals[NUM_SIGNALS] = { 0, }; + +G_DEFINE_TYPE (UmRealmManager, um_realm_manager, UM_REALM_TYPE_OBJECT_MANAGER_CLIENT); + +GQuark +um_realm_error_get_quark (void) +{ + static GQuark quark = 0; + if (quark == 0) + quark = g_quark_from_static_string ("um-realm-error"); + return quark; +} + +static gboolean +is_realm_with_kerberos_and_membership (gpointer object) +{ + GDBusInterface *interface; + + if (!G_IS_DBUS_OBJECT (object)) + return FALSE; + + interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.Kerberos"); + if (interface == NULL) + return FALSE; + g_object_unref (interface); + + interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.KerberosMembership"); + if (interface == NULL) + return FALSE; + g_object_unref (interface); + + return TRUE; +} + +static void +on_interface_added (GDBusObjectManager *manager, + GDBusObject *object, + GDBusInterface *interface) +{ + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (interface), G_MAXINT); +} + +static void +on_object_added (GDBusObjectManager *manager, + GDBusObject *object, + gpointer user_data) +{ + GList *interfaces, *l; + + interfaces = g_dbus_object_get_interfaces (object); + for (l = interfaces; l != NULL; l = g_list_next (l)) + on_interface_added (manager, object, l->data); + g_list_free_full (interfaces, g_object_unref); + + if (is_realm_with_kerberos_and_membership (object)) { + g_debug ("Saw realm: %s", g_dbus_object_get_object_path (object)); + g_signal_emit (user_data, signals[REALM_ADDED], 0, object); + } +} + +static void +um_realm_manager_init (UmRealmManager *self) +{ + g_signal_connect (self, "object-added", G_CALLBACK (on_object_added), self); + g_signal_connect (self, "interface-added", G_CALLBACK (on_interface_added), self); +} + +static void +um_realm_manager_dispose (GObject *obj) +{ + UmRealmManager *self = UM_REALM_MANAGER (obj); + + g_clear_object (&self->provider); + + G_OBJECT_CLASS (um_realm_manager_parent_class)->dispose (obj); +} + +static void +um_realm_manager_class_init (UmRealmManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = um_realm_manager_dispose; + + signals[REALM_ADDED] = g_signal_new ("realm-added", UM_TYPE_REALM_MANAGER, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 1, UM_REALM_TYPE_OBJECT); +} + +typedef struct { + GCancellable *cancellable; + UmRealmManager *manager; +} NewClosure; + +static void +new_closure_free (gpointer data) +{ + NewClosure *closure = data; + g_clear_object (&closure->cancellable); + g_clear_object (&closure->manager); + g_slice_free (NewClosure, closure); +} + +static void +on_provider_new (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data); + NewClosure *closure = g_simple_async_result_get_op_res_gpointer (async); + GError *error = NULL; + UmRealmProvider *provider; + + provider = um_realm_provider_proxy_new_finish (result, &error); + closure->manager->provider = provider; + + if (error == NULL) { + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (closure->manager->provider), -1); + g_debug ("Created realm manager"); + } else { + g_simple_async_result_take_error (async, error); + } + g_simple_async_result_complete (async); + + g_object_unref (async); +} + +static void +on_manager_new (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data); + NewClosure *closure = g_simple_async_result_get_op_res_gpointer (async); + GDBusConnection *connection; + GError *error = NULL; + GObject *object; + + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, &error); + if (error == NULL) { + closure->manager = UM_REALM_MANAGER (object); + connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object)); + + g_debug ("Connected to realmd"); + + um_realm_provider_proxy_new (connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + "org.freedesktop.realmd", + "/org/freedesktop/realmd", + closure->cancellable, + on_provider_new, g_object_ref (async)); + } else { + g_simple_async_result_take_error (async, error); + g_simple_async_result_complete (async); + } + + g_object_unref (async); +} + +void +um_realm_manager_new (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *async; + NewClosure *closure; + + g_debug ("Connecting to realmd..."); + + async = g_simple_async_result_new (NULL, callback, user_data, + um_realm_manager_new); + closure = g_slice_new (NewClosure); + closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL; + g_simple_async_result_set_op_res_gpointer (async, closure, new_closure_free); + + g_async_initable_new_async (UM_TYPE_REALM_MANAGER, G_PRIORITY_DEFAULT, + cancellable, on_manager_new, g_object_ref (async), + "flags", G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + "name", "org.freedesktop.realmd", + "bus-type", G_BUS_TYPE_SYSTEM, + "object-path", "/org/freedesktop/realmd", + "get-proxy-type-func", um_realm_object_manager_client_get_proxy_type, + NULL); + + g_object_unref (async); +} + +UmRealmManager * +um_realm_manager_new_finish (GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *async; + NewClosure *closure; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL, + um_realm_manager_new), NULL); + + async = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (async, error)) + return NULL; + + closure = g_simple_async_result_get_op_res_gpointer (async); + return g_object_ref (closure->manager); +} + +typedef struct { + UmRealmManager *manager; + GCancellable *cancellable; + GList *realms; +} DiscoverClosure; + +static void +discover_closure_free (gpointer data) +{ + DiscoverClosure *discover = data; + g_object_unref (discover->manager); + g_clear_object (&discover->cancellable); + g_list_free_full (discover->realms, g_object_unref); + g_slice_free (DiscoverClosure, discover); +} + +static void +on_provider_discover (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data); + DiscoverClosure *discover = g_simple_async_result_get_op_res_gpointer (async); + GDBusObject *object; + GError *error = NULL; + gboolean no_membership = FALSE; + gchar **realms; + gint relevance; + gint i; + + um_realm_provider_call_discover_finish (UM_REALM_PROVIDER (source), &relevance, + &realms, result, &error); + if (error == NULL) { + for (i = 0; realms[i]; i++) { + object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (discover->manager), realms[i]); + if (object == NULL) { + g_warning ("Realm is not in object manager: %s", realms[i]); + } else { + if (is_realm_with_kerberos_and_membership (object)) { + g_debug ("Discovered realm: %s", realms[i]); + discover->realms = g_list_prepend (discover->realms, object); + } else { + g_debug ("Realm does not support kerberos membership: %s", realms[i]); + no_membership = TRUE; + g_object_unref (object); + } + } + } + g_strfreev (realms); + + if (!discover->realms && no_membership) { + g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC, + _("Cannot automatically join this type of domain")); + } + } else { + g_simple_async_result_take_error (async, error); + } + + g_simple_async_result_complete (async); + g_object_unref (async); +} + +void +um_realm_manager_discover (UmRealmManager *self, + const gchar *input, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + DiscoverClosure *discover; + GVariant *options; + + g_return_if_fail (UM_IS_REALM_MANAGER (self)); + g_return_if_fail (input != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + g_debug ("Discovering realms for: %s", input); + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + um_realm_manager_discover); + discover = g_slice_new0 (DiscoverClosure); + discover->manager = g_object_ref (self); + discover->cancellable = cancellable ? g_object_ref (cancellable) : NULL; + g_simple_async_result_set_op_res_gpointer (res, discover, discover_closure_free); + + options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0); + + um_realm_provider_call_discover (self->provider, input, options, cancellable, + on_provider_discover, g_object_ref (res)); + + g_object_unref (res); +} + +GList * +um_realm_manager_discover_finish (UmRealmManager *self, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *async; + DiscoverClosure *discover; + GList *realms; + + g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL); + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), + um_realm_manager_discover), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + async = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (async, error)) + return NULL; + + discover = g_simple_async_result_get_op_res_gpointer (async); + if (!discover->realms) { + g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC, + _("No such domain or realm found")); + return NULL; + } + + realms = g_list_reverse (discover->realms); + discover->realms = NULL; + return realms; +} + +GList * +um_realm_manager_get_realms (UmRealmManager *self) +{ + GList *objects; + GList *realms = NULL; + GList *l; + + g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL); + + objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self)); + for (l = objects; l != NULL; l = g_list_next (l)) { + if (is_realm_with_kerberos_and_membership (l->data)) + realms = g_list_prepend (realms, g_object_ref (l->data)); + } + + g_list_free_full (objects, g_object_unref); + return realms; +} + +static void +string_replace (GString *string, + const gchar *find, + const gchar *replace) +{ + const gchar *at; + gssize pos; + + at = strstr (string->str, find); + if (at != NULL) { + pos = at - string->str; + g_string_erase (string, pos, strlen (find)); + g_string_insert (string, pos, replace); + } +} + +gchar * +um_realm_calculate_login (UmRealmCommon *realm, + const gchar *username) +{ + GString *string; + const gchar *const *formats; + gchar *login = NULL; + + formats = um_realm_common_get_login_formats (realm); + if (formats[0] != NULL) { + string = g_string_new (formats[0]); + string_replace (string, "%U", username); + string_replace (string, "%D", um_realm_common_get_name (realm)); + login = g_string_free (string, FALSE); + } + + return login; + +} + +gboolean +um_realm_is_configured (UmRealmObject *realm) +{ + UmRealmCommon *common; + const gchar *configured; + gboolean is = FALSE; + + common = um_realm_object_get_common (realm); + if (common) { + configured = um_realm_common_get_configured (common); + is = configured != NULL && !g_str_equal (configured, ""); + g_object_unref (common); + } + + return is; +} + +static const gchar * +find_supported_credentials (UmRealmKerberosMembership *membership, + const gchar *owner) +{ + const gchar *cred_owner; + const gchar *cred_type; + GVariant *supported; + GVariantIter iter; + + supported = um_realm_kerberos_membership_get_supported_join_credentials (membership); + g_return_val_if_fail (supported != NULL, NULL); + + g_variant_iter_init (&iter, supported); + while (g_variant_iter_loop (&iter, "(&s&s)", &cred_type, &cred_owner)) { + if (g_str_equal (owner, cred_owner)) { + if (g_str_equal (cred_type, "ccache") || + g_str_equal (cred_type, "password")) { + return g_intern_string (cred_type); + } + } + } + + return NULL; +} + +static void +on_realm_join_complete (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data); + + g_debug ("Completed Join() method call"); + + g_simple_async_result_set_op_res_gpointer (async, g_object_ref (result), g_object_unref); + g_simple_async_result_complete_in_idle (async); + g_object_unref (async); +} + +static gboolean +realm_join_as_owner (UmRealmObject *realm, + const gchar *owner, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UmRealmKerberosMembership *membership; + GSimpleAsyncResult *async; + GVariant *contents; + GVariant *options; + GVariant *option; + GVariant *creds; + const gchar *type; + + membership = um_realm_object_get_kerberos_membership (realm); + g_return_val_if_fail (membership != NULL, FALSE); + + type = find_supported_credentials (membership, owner); + if (type == NULL) { + g_debug ("Couldn't find supported credential type for owner: %s", owner); + g_object_unref (membership); + return FALSE; + } + + async = g_simple_async_result_new (G_OBJECT (realm), callback, user_data, + realm_join_as_owner); + + if (g_str_equal (type, "ccache")) { + g_debug ("Using a kerberos credential cache to join the realm"); + contents = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), + g_bytes_get_data (credentials, NULL), + g_bytes_get_size (credentials), + TRUE, (GDestroyNotify)g_bytes_unref, credentials); + + } else if (g_str_equal (type, "password")) { + g_debug ("Using a user/password to join the realm"); + contents = g_variant_new ("(ss)", login, password); + + } else { + g_assert_not_reached (); + } + + creds = g_variant_new ("(ssv)", type, owner, contents); + option = g_variant_new ("{sv}", "manage-system", g_variant_new_boolean (FALSE)); + options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), &option, 1); + + g_debug ("Calling the Join() method with %s credentials", owner); + + um_realm_kerberos_membership_call_join (membership, creds, options, + cancellable, on_realm_join_complete, + g_object_ref (async)); + + g_object_unref (async); + g_object_unref (membership); + + return TRUE; +} + +gboolean +um_realm_join_as_user (UmRealmObject *realm, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (login != NULL, FALSE); + g_return_val_if_fail (password != NULL, FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + + return realm_join_as_owner (realm, "user", login, password, + credentials, cancellable, callback, user_data); +} + +gboolean +um_realm_join_as_admin (UmRealmObject *realm, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (login != NULL, FALSE); + g_return_val_if_fail (password != NULL, FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + + return realm_join_as_owner (realm, "administrator", login, password, credentials, + cancellable, callback, user_data); +} + +gboolean +um_realm_join_finish (UmRealmObject *realm, + GAsyncResult *result, + GError **error) +{ + UmRealmKerberosMembership *membership; + GError *call_error = NULL; + gchar *dbus_error; + GAsyncResult *async; + + g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + membership = um_realm_object_get_kerberos_membership (realm); + g_return_val_if_fail (membership != NULL, FALSE); + + async = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); + um_realm_kerberos_membership_call_join_finish (membership, async, &call_error); + g_object_unref (membership); + + if (call_error == NULL) + return TRUE; + + dbus_error = g_dbus_error_get_remote_error (call_error); + if (dbus_error == NULL) { + g_debug ("Join() failed because of %s", call_error->message); + g_propagate_error (error, call_error); + return FALSE; + } + + g_dbus_error_strip_remote_error (call_error); + + if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.AuthenticationFailed")) { + g_debug ("Join() failed because of invalid/insufficient credentials"); + g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN, + "%s", call_error->message); + g_error_free (call_error); + } else if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.BadHostname")) { + g_debug ("Join() failed because of invalid/conflicting host name"); + g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME, + "%s", call_error->message); + g_error_free (call_error); + } else { + g_debug ("Join() failed because of %s", call_error->message); + g_propagate_error (error, call_error); + } + + g_free (dbus_error); + return FALSE; +} + +typedef struct { + gchar *domain; + gchar *realm; + gchar *user; + gchar *password; + GBytes *credentials; +} LoginClosure; + +static void +login_closure_free (gpointer data) +{ + LoginClosure *login = data; + g_free (login->domain); + g_free (login->realm); + g_free (login->user); + g_free (login->password); + g_bytes_unref (login->credentials); + g_slice_free (LoginClosure, login); +} + +static krb5_error_code +login_perform_kinit (krb5_context k5, + const gchar *realm, + const gchar *login, + const gchar *password, + const gchar *filename) +{ + krb5_get_init_creds_opt *opts; + krb5_error_code code; + krb5_principal principal; + krb5_ccache ccache; + krb5_creds creds; + gchar *name; + + name = g_strdup_printf ("%s@%s", login, realm); + code = krb5_parse_name (k5, name, &principal); + + if (code != 0) { + g_debug ("Couldn't parse principal name: %s: %s", + name, krb5_get_error_message (k5, code)); + g_free (name); + return code; + } + + g_debug ("Using principal name to kinit: %s", name); + g_free (name); + + if (filename == NULL) + code = krb5_cc_default (k5, &ccache); + else + code = krb5_cc_resolve (k5, filename, &ccache); + + if (code != 0) { + krb5_free_principal (k5, principal); + g_debug ("Couldn't open credential cache: %s: %s", + filename ? filename : "<default>", + krb5_get_error_message (k5, code)); + return code; + } + + code = krb5_get_init_creds_opt_alloc (k5, &opts); + g_return_val_if_fail (code == 0, code); + + code = krb5_get_init_creds_opt_set_out_ccache (k5, opts, ccache); + g_return_val_if_fail (code == 0, code); + + code = krb5_get_init_creds_password (k5, &creds, principal, + (char *)password, + NULL, 0, 0, NULL, opts); + + krb5_get_init_creds_opt_free (k5, opts); + krb5_cc_close (k5, ccache); + krb5_free_principal (k5, principal); + + if (code == 0) { + g_debug ("kinit succeeded"); + krb5_free_cred_contents (k5, &creds); + } else { + g_debug ("kinit failed: %s", krb5_get_error_message (k5, code)); + } + + return code; +} + +static void +kinit_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + LoginClosure *login = task_data; + krb5_context k5 = NULL; + krb5_error_code code; + GError *error = NULL; + gchar *filename = NULL; + gchar *contents; + gsize length; + gint temp_fd; + + filename = g_build_filename (g_get_user_runtime_dir (), + "um-krb5-creds.XXXXXX", NULL); + temp_fd = g_mkstemp_full (filename, O_RDWR, S_IRUSR | S_IWUSR); + if (temp_fd == -1) { + g_warning ("Couldn't create credential cache file: %s: %s", + filename, g_strerror (errno)); + g_free (filename); + filename = NULL; + } else { + close (temp_fd); + } + + code = krb5_init_context (&k5); + if (code == 0) { + code = login_perform_kinit (k5, login->realm, login->user, + login->password, filename); + } + + switch (code) { + case 0: + if (filename != NULL) { + g_file_get_contents (filename, &contents, &length, &error); + if (error == NULL) { + login->credentials = g_bytes_new_take (contents, length); + g_debug ("Read in credential cache: %s", filename); + } else { + g_warning ("Couldn't read credential cache: %s: %s", + filename, error->message); + g_error_free (error); + } + } + g_task_return_boolean (task, TRUE); + break; + + case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: + case KRB5KDC_ERR_POLICY: + g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN, + _("Cannot log in as %s at the %s domain"), + login->user, login->domain); + break; + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD, + _("Invalid password, please try again")); + break; + case KRB5_PREAUTH_FAILED: + case KRB5KDC_ERR_KEY_EXP: + case KRB5KDC_ERR_CLIENT_REVOKED: + case KRB5KDC_ERR_ETYPE_NOSUPP: + case KRB5_PROG_ETYPE_NOSUPP: + g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_CANNOT_AUTH, + _("Cannot log in as %s at the %s domain"), + login->user, login->domain); + break; + default: + g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC, + _("Couldn’t connect to the %s domain: %s"), + login->domain, krb5_get_error_message (k5, code)); + break; + } + + if (filename) { + g_unlink (filename); + g_debug ("Deleted credential cache: %s", filename); + g_free (filename); + } + + if (k5) + krb5_free_context (k5); +} + +void +um_realm_login (UmRealmObject *realm, + const gchar *user, + const gchar *password, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LoginClosure *login; + UmRealmKerberos *kerberos; + + g_return_if_fail (UM_REALM_IS_OBJECT (realm)); + g_return_if_fail (user != NULL); + g_return_if_fail (password != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + kerberos = um_realm_object_get_kerberos (realm); + g_return_if_fail (kerberos != NULL); + + task = g_task_new (realm, cancellable, callback, user_data); + login = g_slice_new0 (LoginClosure); + login->domain = g_strdup (um_realm_kerberos_get_domain_name (kerberos)); + login->realm = g_strdup (um_realm_kerberos_get_realm_name (kerberos)); + login->user = g_strdup (user); + login->password = g_strdup (password); + g_task_set_task_data (task, login, login_closure_free); + + g_task_set_check_cancellable (task, TRUE); + g_task_set_return_on_cancel (task, TRUE); + + g_task_run_in_thread (task, kinit_thread_func); + + g_object_unref (task); + g_object_unref (kerberos); +} + +gboolean +um_realm_login_finish (UmRealmObject *realm, + GAsyncResult *result, + GBytes **credentials, + GError **error) +{ + GTask *task; + LoginClosure *login; + + g_return_val_if_fail (g_task_is_valid (result, realm), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + task = G_TASK (result); + if (!g_task_propagate_boolean (task, error)) + return FALSE; + + login = g_task_get_task_data (task); + if (credentials) { + if (login->credentials) + *credentials = g_bytes_ref (login->credentials); + else + *credentials = NULL; + } + + return TRUE; +} diff --git a/gnome-initial-setup/pages/account/um-realm-manager.h b/gnome-initial-setup/pages/account/um-realm-manager.h new file mode 100644 index 0000000..952bd2f --- /dev/null +++ b/gnome-initial-setup/pages/account/um-realm-manager.h @@ -0,0 +1,108 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 Red Hat, Inc. + * + * 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: Stef Walter <stefw@gnome.org> + */ + +#ifndef __UM_REALM_MANAGER_H__ +#define __UM_REALM_MANAGER_H__ + +#include "um-realm-generated.h" + +G_BEGIN_DECLS + +typedef enum { + UM_REALM_ERROR_BAD_LOGIN, + UM_REALM_ERROR_BAD_PASSWORD, + UM_REALM_ERROR_CANNOT_AUTH, + UM_REALM_ERROR_BAD_HOSTNAME, + UM_REALM_ERROR_GENERIC, +} UmRealmErrors; + +#define UM_REALM_ERROR (um_realm_error_get_quark ()) + +GQuark um_realm_error_get_quark (void) G_GNUC_CONST; + +#define UM_TYPE_REALM_MANAGER (um_realm_manager_get_type ()) +#define UM_REALM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_REALM_MANAGER, UmRealmManager)) +#define UM_IS_REALM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_REALM_MANAGER)) + +typedef struct _UmRealmManager UmRealmManager; + +GType um_realm_manager_get_type (void) G_GNUC_CONST; + +void um_realm_manager_new (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +UmRealmManager * um_realm_manager_new_finish (GAsyncResult *result, + GError **error); + +void um_realm_manager_discover (UmRealmManager *self, + const gchar *input, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GList * um_realm_manager_discover_finish (UmRealmManager *self, + GAsyncResult *result, + GError **error); + +GList * um_realm_manager_get_realms (UmRealmManager *self); + +void um_realm_login (UmRealmObject *realm, + const gchar *login, + const gchar *password, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean um_realm_login_finish (UmRealmObject *realm, + GAsyncResult *result, + GBytes **credentials, + GError **error); + +gboolean um_realm_join_as_user (UmRealmObject *realm, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) + G_GNUC_WARN_UNUSED_RESULT; + +gboolean um_realm_join_as_admin (UmRealmObject *realm, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) + G_GNUC_WARN_UNUSED_RESULT; + +gboolean um_realm_join_finish (UmRealmObject *realm, + GAsyncResult *result, + GError **error); + +gboolean um_realm_is_configured (UmRealmObject *realm); + +gchar * um_realm_calculate_login (UmRealmCommon *realm, + const gchar *username); + +G_END_DECLS + +#endif /* __UM_REALM_H__ */ diff --git a/gnome-initial-setup/pages/account/um-utils.c b/gnome-initial-setup/pages/account/um-utils.c new file mode 100644 index 0000000..a120382 --- /dev/null +++ b/gnome-initial-setup/pages/account/um-utils.c @@ -0,0 +1,648 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 Red Hat, Inc, + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <math.h> +#include <stdlib.h> +#include <sys/types.h> +#include <pwd.h> +#include <utmp.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include "um-utils.h" + +void +set_entry_validation_checkmark (GtkEntry *entry) +{ + g_object_set (entry, "caps-lock-warning", FALSE, NULL); + gtk_entry_set_icon_from_icon_name (entry, + GTK_ENTRY_ICON_SECONDARY, + "object-select-symbolic"); +} + +void +set_entry_validation_error (GtkEntry *entry, + const gchar *text) +{ + g_object_set (entry, "caps-lock-warning", FALSE, NULL); + gtk_entry_set_icon_from_stock (entry, + GTK_ENTRY_ICON_SECONDARY, + GTK_STOCK_CAPS_LOCK_WARNING); + gtk_entry_set_icon_tooltip_text (entry, + GTK_ENTRY_ICON_SECONDARY, + text); +} + +void +clear_entry_validation_error (GtkEntry *entry) +{ + gboolean warning; + + g_object_get (entry, "caps-lock-warning", &warning, NULL); + + if (warning) + return; + + gtk_entry_set_icon_from_pixbuf (entry, + GTK_ENTRY_ICON_SECONDARY, + NULL); + g_object_set (entry, "caps-lock-warning", TRUE, NULL); +} + +void +popup_menu_below_button (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + GtkWidget *button) +{ + GtkRequisition menu_req; + GtkTextDirection direction; + GtkAllocation allocation; + + gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &menu_req); + + direction = gtk_widget_get_direction (button); + + gdk_window_get_origin (gtk_widget_get_window (button), x, y); + gtk_widget_get_allocation (button, &allocation); + *x += allocation.x; + *y += allocation.y + allocation.height; + + if (direction == GTK_TEXT_DIR_LTR) + *x += MAX (allocation.width - menu_req.width, 0); + else if (menu_req.width > allocation.width) + *x -= menu_req.width - allocation.width; + + *push_in = TRUE; +} + +void +down_arrow (GtkStyleContext *context, + cairo_t *cr, + gint x, + gint y, + gint width, + gint height) +{ + GtkStateFlags flags; + GdkRGBA fg_color; + GdkRGBA outline_color; + gdouble vertical_overshoot; + gint diameter; + gdouble radius; + gdouble x_double, y_double; + gdouble angle; + gint line_width; + + flags = gtk_style_context_get_state (context); + + gtk_style_context_get_color (context, flags, &fg_color); + gtk_style_context_get_border_color (context, flags, &outline_color); + + line_width = 1; + angle = G_PI / 2; + vertical_overshoot = line_width / 2.0 * (1. / tan (G_PI / 8)); + if (line_width % 2 == 1) + vertical_overshoot = ceil (0.5 + vertical_overshoot) - 0.5; + else + vertical_overshoot = ceil (vertical_overshoot); + diameter = (gint) MAX (3, width - 2 * vertical_overshoot); + diameter -= (1 - (diameter + line_width) % 2); + radius = diameter / 2.; + x_double = floor ((x + width / 2) - (radius + line_width) / 2.) + (radius + line_width) / 2.; + + y_double = (y + height / 2) - 0.5; + + cairo_save (cr); + + cairo_translate (cr, x_double, y_double); + cairo_rotate (cr, angle); + + cairo_move_to (cr, - radius / 2., - radius); + cairo_line_to (cr, radius / 2., 0); + cairo_line_to (cr, - radius / 2., radius); + + cairo_close_path (cr); + + cairo_set_line_width (cr, line_width); + + gdk_cairo_set_source_rgba (cr, &fg_color); + + cairo_fill_preserve (cr); + + gdk_cairo_set_source_rgba (cr, &outline_color); + cairo_stroke (cr); + + cairo_restore (cr); +} + +#define MAXNAMELEN (UT_NAMESIZE - 1) + +static gboolean +is_username_used (const gchar *username) +{ + struct passwd *pwent; + + if (username == NULL || username[0] == '\0') { + return FALSE; + } + + pwent = getpwnam (username); + + return pwent != NULL; +} + +gboolean +is_valid_name (const gchar *name) +{ + gboolean is_empty = TRUE; + const gchar *c; + + /* Valid names must contain: + * 1) at least one character. + * 2) at least one non-"space" character. + */ + for (c = name; *c; c++) { + gunichar unichar; + + unichar = g_utf8_get_char_validated (c, -1); + + /* Partial UTF-8 sequence or end of string */ + if (unichar == (gunichar) -1 || unichar == (gunichar) -2) + break; + + /* Check for non-space character */ + if (!g_unichar_isspace (unichar)) { + is_empty = FALSE; + break; + } + } + + return !is_empty; +} + +gboolean +is_valid_username (const gchar *username, gboolean parental_controls_enabled, gchar **tip) +{ + gboolean empty; + gboolean in_use; + gboolean too_long; + gboolean valid; + gboolean parental_controls_conflict; + const gchar *c; + + if (username == NULL || username[0] == '\0') { + empty = TRUE; + in_use = FALSE; + too_long = FALSE; + } else { + empty = FALSE; + in_use = is_username_used (username); + too_long = strlen (username) > MAXNAMELEN; + } + valid = TRUE; + + if (!in_use && !empty && !too_long) { + /* First char must be a letter, and it must only composed + * of ASCII letters, digits, and a '.', '-', '_' + */ + for (c = username; *c; c++) { + if (! ((*c >= 'a' && *c <= 'z') || + (*c >= 'A' && *c <= 'Z') || + (*c >= '0' && *c <= '9') || + (*c == '_') || (*c == '.') || + (*c == '-' && c != username))) + valid = FALSE; + } + } + + parental_controls_conflict = (parental_controls_enabled && g_strcmp0 (username, "administrator") == 0); + + valid = !empty && !in_use && !too_long && !parental_controls_conflict && valid; + + if (!empty && (in_use || too_long || parental_controls_conflict || !valid)) { + if (in_use) { + *tip = g_strdup (_("Sorry, that user name isn’t available. Please try another.")); + } + else if (too_long) { + *tip = g_strdup_printf (_("The username is too long.")); + } + else if (username[0] == '-') { + *tip = g_strdup (_("The username cannot start with a “-”.")); + } + else if (parental_controls_conflict) { + *tip = g_strdup (_("That username isn’t available. Please try another.")); + } + else { + *tip = g_strdup (_("The username should only consist of upper and lower case letters from a-z, digits and the following characters: . - _")); + } + } + else { + *tip = g_strdup (_("This will be used to name your home folder and can’t be changed.")); + } + + return valid; +} + +void +generate_username_choices (const gchar *name, + GtkListStore *store) +{ + gboolean in_use, same_as_initial; + char *lc_name, *ascii_name, *stripped_name; + char **words1; + char **words2 = NULL; + char **w1, **w2; + char *c; + char *unicode_fallback = "?"; + GString *first_word, *last_word; + GString *item0, *item1, *item2, *item3, *item4; + int len; + int nwords1, nwords2, i; + GHashTable *items; + GtkTreeIter iter; + + gtk_list_store_clear (store); + + ascii_name = g_convert_with_fallback (name, -1, "ASCII//TRANSLIT", "UTF-8", + unicode_fallback, NULL, NULL, NULL); + + lc_name = g_ascii_strdown (ascii_name, -1); + + /* Remove all non ASCII alphanumeric chars from the name, + * apart from the few allowed symbols. + * + * We do remove '.', even though it is usually allowed, + * since it often comes in via an abbreviated middle name, + * and the dot looks just wrong in the proposals then. + */ + stripped_name = g_strnfill (strlen (lc_name) + 1, '\0'); + i = 0; + for (c = lc_name; *c; c++) { + if (!(g_ascii_isdigit (*c) || g_ascii_islower (*c) || + *c == ' ' || *c == '-' || *c == '_' || + /* used to track invalid words, removed below */ + *c == '?') ) + continue; + + stripped_name[i] = *c; + i++; + } + + if (strlen (stripped_name) == 0) { + g_free (ascii_name); + g_free (lc_name); + g_free (stripped_name); + return; + } + + /* we split name on spaces, and then on dashes, so that we can treat + * words linked with dashes the same way, i.e. both fully shown, or + * both abbreviated + */ + words1 = g_strsplit_set (stripped_name, " ", -1); + len = g_strv_length (words1); + + /* The default item is a concatenation of all words without ? */ + item0 = g_string_sized_new (strlen (stripped_name)); + + g_free (ascii_name); + g_free (lc_name); + g_free (stripped_name); + + /* Concatenate the whole first word with the first letter of each + * word (item1), and the last word with the first letter of each + * word (item2). item3 and item4 are symmetrical respectively to + * item1 and item2. + * + * Constant 5 is the max reasonable number of words we may get when + * splitting on dashes, since we can't guess it at this point, + * and reallocating would be too bad. + */ + item1 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5); + item3 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5); + + item2 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5); + item4 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5); + + /* again, guess at the max size of names */ + first_word = g_string_sized_new (20); + last_word = g_string_sized_new (20); + + nwords1 = 0; + nwords2 = 0; + for (w1 = words1; *w1; w1++) { + if (strlen (*w1) == 0) + continue; + + /* skip words with string '?', most likely resulting + * from failed transliteration to ASCII + */ + if (strstr (*w1, unicode_fallback) != NULL) + continue; + + nwords1++; /* count real words, excluding empty string */ + + item0 = g_string_append (item0, *w1); + + words2 = g_strsplit_set (*w1, "-", -1); + /* reset last word if a new non-empty word has been found */ + if (strlen (*words2) > 0) + last_word = g_string_set_size (last_word, 0); + + for (w2 = words2; *w2; w2++) { + if (strlen (*w2) == 0) + continue; + + nwords2++; + + /* part of the first "toplevel" real word */ + if (nwords1 == 1) { + item1 = g_string_append (item1, *w2); + first_word = g_string_append (first_word, *w2); + } + else { + item1 = g_string_append_unichar (item1, + g_utf8_get_char (*w2)); + item3 = g_string_append_unichar (item3, + g_utf8_get_char (*w2)); + } + + /* not part of the last "toplevel" word */ + if (w1 != words1 + len - 1) { + item2 = g_string_append_unichar (item2, + g_utf8_get_char (*w2)); + item4 = g_string_append_unichar (item4, + g_utf8_get_char (*w2)); + } + + /* always save current word so that we have it if last one reveals empty */ + last_word = g_string_append (last_word, *w2); + } + + g_strfreev (words2); + } + item2 = g_string_append (item2, last_word->str); + item3 = g_string_append (item3, first_word->str); + item4 = g_string_prepend (item4, last_word->str); + + items = g_hash_table_new (g_str_hash, g_str_equal); + + in_use = is_username_used (item0->str); + if (!in_use && !g_ascii_isdigit (item0->str[0])) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item0->str, -1); + g_hash_table_insert (items, item0->str, item0->str); + } + + in_use = is_username_used (item1->str); + same_as_initial = (g_strcmp0 (item0->str, item1->str) == 0); + if (!same_as_initial && nwords2 > 0 && !in_use && !g_ascii_isdigit (item1->str[0])) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item1->str, -1); + g_hash_table_insert (items, item1->str, item1->str); + } + + /* if there's only one word, would be the same as item1 */ + if (nwords2 > 1) { + /* add other items */ + in_use = is_username_used (item2->str); + if (!in_use && !g_ascii_isdigit (item2->str[0]) && + !g_hash_table_lookup (items, item2->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item2->str, -1); + g_hash_table_insert (items, item2->str, item2->str); + } + + in_use = is_username_used (item3->str); + if (!in_use && !g_ascii_isdigit (item3->str[0]) && + !g_hash_table_lookup (items, item3->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item3->str, -1); + g_hash_table_insert (items, item3->str, item3->str); + } + + in_use = is_username_used (item4->str); + if (!in_use && !g_ascii_isdigit (item4->str[0]) && + !g_hash_table_lookup (items, item4->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item4->str, -1); + g_hash_table_insert (items, item4->str, item4->str); + } + + /* add the last word */ + in_use = is_username_used (last_word->str); + if (!in_use && !g_ascii_isdigit (last_word->str[0]) && + !g_hash_table_lookup (items, last_word->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, last_word->str, -1); + g_hash_table_insert (items, last_word->str, last_word->str); + } + + /* ...and the first one */ + in_use = is_username_used (first_word->str); + if (!in_use && !g_ascii_isdigit (first_word->str[0]) && + !g_hash_table_lookup (items, first_word->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, first_word->str, -1); + g_hash_table_insert (items, first_word->str, first_word->str); + } + } + + g_hash_table_destroy (items); + g_strfreev (words1); + g_string_free (first_word, TRUE); + g_string_free (last_word, TRUE); + g_string_free (item0, TRUE); + g_string_free (item1, TRUE); + g_string_free (item2, TRUE); + g_string_free (item3, TRUE); + g_string_free (item4, TRUE); +} + +#define IMAGE_SIZE 512 + +/* U+1F464 "bust in silhouette" + * U+FE0E Variant Selector 15 to force text style (monochrome) emoji + */ +#define PLACEHOLDER "\U0001F464\U0000FE0E" + +static gchar * +extract_initials_from_name (const gchar *name) +{ + GString *initials = g_string_new (""); + gchar *p; + gchar *normalized; + gunichar unichar; + + if (name == NULL || name[0] == '\0') { + g_string_free (initials, TRUE); + return g_strdup (PLACEHOLDER); + } + + p = g_utf8_strup (name, -1); + normalized = g_utf8_normalize (g_strstrip (p), -1, G_NORMALIZE_DEFAULT_COMPOSE); + g_clear_pointer (&p, g_free); + if (normalized == NULL) { + g_free (normalized); + g_string_free (initials, TRUE); + return g_strdup (PLACEHOLDER); + } + + unichar = g_utf8_get_char (normalized); + g_string_append_unichar (initials, unichar); + + p = g_utf8_strrchr (normalized, -1, ' '); + if (p != NULL && g_utf8_next_char (p) != NULL) { + p = g_utf8_next_char (p); + + unichar = g_utf8_get_char (p); + g_string_append_unichar (initials, unichar); + } + + g_free (normalized); + + return g_string_free (initials, FALSE); +} + +GdkRGBA +get_color_for_name (const gchar *name) +{ + // https://gitlab.gnome.org/Community/Design/HIG-app-icons/blob/master/GNOME%20HIG.gpl + static gdouble gnome_color_palette[][3] = { + { 98, 160, 234 }, + { 53, 132, 228 }, + { 28, 113, 216 }, + { 26, 95, 180 }, + { 87, 227, 137 }, + { 51, 209, 122 }, + { 46, 194, 126 }, + { 38, 162, 105 }, + { 248, 228, 92 }, + { 246, 211, 45 }, + { 245, 194, 17 }, + { 229, 165, 10 }, + { 255, 163, 72 }, + { 255, 120, 0 }, + { 230, 97, 0 }, + { 198, 70, 0 }, + { 237, 51, 59 }, + { 224, 27, 36 }, + { 192, 28, 40 }, + { 165, 29, 45 }, + { 192, 97, 203 }, + { 163, 71, 186 }, + { 129, 61, 156 }, + { 97, 53, 131 }, + { 181, 131, 90 }, + { 152, 106, 68 }, + { 134, 94, 60 }, + { 99, 69, 44 } + }; + + GdkRGBA color = { 255, 255, 255, 1.0 }; + guint hash; + gint number_of_colors; + gint idx; + + if (name == NULL || name[0] == '\0') { + idx = 5; + } else { + hash = g_str_hash (name); + number_of_colors = G_N_ELEMENTS (gnome_color_palette); + idx = hash % number_of_colors; + } + + color.red = gnome_color_palette[idx][0]; + color.green = gnome_color_palette[idx][1]; + color.blue = gnome_color_palette[idx][2]; + + return color; +} + +cairo_surface_t * +generate_user_picture (const gchar *name) { + PangoFontDescription *font_desc; + g_autofree gchar *initials = extract_initials_from_name (name); + g_autofree gchar *font = g_strdup_printf ("Sans %d", (int)ceil (IMAGE_SIZE / 2.5)); + PangoLayout *layout; + GdkRGBA color = get_color_for_name (name); + cairo_surface_t *surface; + gint width, height; + cairo_t *cr; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + IMAGE_SIZE, + IMAGE_SIZE); + cr = cairo_create (surface); + + cairo_arc (cr, IMAGE_SIZE/2, IMAGE_SIZE/2, IMAGE_SIZE/2, 0, 2 * G_PI); + cairo_set_source_rgb (cr, color.red/255.0, color.green/255.0, color.blue/255.0); + cairo_fill (cr); + + /* Draw the initials on top */ + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + layout = pango_cairo_create_layout (cr); + pango_layout_set_text (layout, initials, -1); + font_desc = pango_font_description_from_string (font); + pango_layout_set_font_description (layout, font_desc); + pango_font_description_free (font_desc); + + pango_layout_get_size (layout, &width, &height); + cairo_translate (cr, IMAGE_SIZE/2, IMAGE_SIZE/2); + cairo_move_to (cr, - ((double)width / PANGO_SCALE)/2, - ((double)height/PANGO_SCALE)/2); + pango_cairo_show_layout (cr, layout); + cairo_destroy (cr); + + return surface; +} + +GdkPixbuf * +round_image (GdkPixbuf *image) +{ + GdkPixbuf *dest = NULL; + cairo_surface_t *surface; + cairo_t *cr; + gint size; + + size = gdk_pixbuf_get_width (image); + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size); + cr = cairo_create (surface); + + /* Clip a circle */ + cairo_arc (cr, size/2, size/2, size/2, 0, 2 * G_PI); + cairo_clip (cr); + cairo_new_path (cr); + + gdk_cairo_set_source_pixbuf (cr, image, 0, 0); + cairo_paint (cr); + + dest = gdk_pixbuf_get_from_surface (surface, 0, 0, size, size); + cairo_surface_destroy (surface); + cairo_destroy (cr); + + return dest; +} diff --git a/gnome-initial-setup/pages/account/um-utils.h b/gnome-initial-setup/pages/account/um-utils.h new file mode 100644 index 0000000..b0bb905 --- /dev/null +++ b/gnome-initial-setup/pages/account/um-utils.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 Red Hat, Inc, + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#ifndef __UM_UTILS_H__ +#define __UM_UTILS_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +void set_entry_validation_error (GtkEntry *entry, + const gchar *text); +void set_entry_validation_checkmark (GtkEntry *entry); +void clear_entry_validation_error (GtkEntry *entry); + +void popup_menu_below_button (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + GtkWidget *button); + +void down_arrow (GtkStyleContext *context, + cairo_t *cr, + gint x, + gint y, + gint width, + gint height); + +gboolean is_valid_name (const gchar *name); +gboolean is_valid_username (const gchar *name, + gboolean parental_controls_enabled, + gchar **tip); + +void generate_username_choices (const gchar *name, + GtkListStore *store); + +cairo_surface_t *generate_user_picture (const gchar *name); + +GdkPixbuf *round_image (GdkPixbuf *image); + + +G_END_DECLS + +#endif diff --git a/gnome-initial-setup/pages/goa/gis-goa-page.c b/gnome-initial-setup/pages/goa/gis-goa-page.c new file mode 100644 index 0000000..87341c4 --- /dev/null +++ b/gnome-initial-setup/pages/goa/gis-goa-page.c @@ -0,0 +1,391 @@ +/* -*- 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> + */ + +/* Online accounts page {{{1 */ + +#define PAGE_ID "goa" + +#include "config.h" +#include "gis-goa-page.h" +#include "goa-resources.h" + +#define GOA_API_IS_SUBJECT_TO_CHANGE +#include <goa/goa.h> +#define GOA_BACKEND_API_IS_SUBJECT_TO_CHANGE +#include <goabackend/goabackend.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "gis-page-header.h" + +#define VENDOR_GOA_GROUP "goa" +#define VENDOR_PROVIDERS_KEY "providers" + +struct _GisGoaPagePrivate { + GtkWidget *accounts_list; + + GoaClient *goa_client; + GHashTable *providers; + gboolean accounts_exist; +}; +typedef struct _GisGoaPagePrivate GisGoaPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisGoaPage, gis_goa_page, GIS_TYPE_PAGE); + +struct _ProviderWidget { + GisGoaPage *page; + GoaProvider *provider; + GoaAccount *displayed_account; + + GtkWidget *row; + GtkWidget *checkmark; + GtkWidget *account_label; +}; +typedef struct _ProviderWidget ProviderWidget; + +static void +sync_provider_widget (ProviderWidget *provider_widget) +{ + gboolean has_account = (provider_widget->displayed_account != NULL); + + gtk_widget_set_visible (provider_widget->checkmark, has_account); + gtk_widget_set_visible (provider_widget->account_label, has_account); + gtk_widget_set_sensitive (provider_widget->row, !has_account); + + if (has_account) { + char *markup; + markup = g_strdup_printf ("<small><span foreground=\"#555555\">%s</span></small>", + goa_account_get_presentation_identity (provider_widget->displayed_account)); + gtk_label_set_markup (GTK_LABEL (provider_widget->account_label), markup); + g_free (markup); + } +} + +static void +add_account_to_provider (ProviderWidget *provider_widget) +{ + GisGoaPage *page = provider_widget->page; + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GtkWindow *parent = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))); + GError *error = NULL; + GtkWidget *dialog; + + dialog = gtk_dialog_new_with_buttons (_("Add Account"), + parent, + GTK_DIALOG_MODAL + | GTK_DIALOG_DESTROY_WITH_PARENT + | GTK_DIALOG_USE_HEADER_BAR, + NULL, NULL); + + goa_provider_add_account (provider_widget->provider, + priv->goa_client, + GTK_DIALOG (dialog), + GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + &error); + + /* this will fire the `account-added` signal, which will do + * the syncing of displayed_account on its own */ + + if (error) { + if (!g_error_matches (error, GOA_ERROR, GOA_ERROR_DIALOG_DISMISSED)) + g_warning ("fart %s", error->message); + goto out; + } + + out: + gtk_widget_destroy (dialog); +} + +static void +add_provider_to_list (GisGoaPage *page, const char *provider_type) +{ + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GtkWidget *row; + GtkWidget *box; + GtkWidget *image; + GtkWidget *label; + GtkWidget *checkmark; + GtkWidget *account_label; + GIcon *icon; + gchar *markup, *provider_name; + GoaProvider *provider; + ProviderWidget *provider_widget; + + provider = goa_provider_get_for_provider_type (provider_type); + if (provider == NULL) + return; + + row = gtk_list_box_row_new (); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + g_object_set (box, "margin", 4, NULL); + gtk_widget_set_hexpand (box, TRUE); + + icon = goa_provider_get_provider_icon (provider, NULL); + image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG); + g_object_unref (icon); + + provider_name = goa_provider_get_provider_name (provider, NULL); + markup = g_strdup_printf ("<b>%s</b>", provider_name); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (markup); + g_free (provider_name); + + checkmark = gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU); + + account_label = gtk_label_new (NULL); + + gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_box_pack_end (GTK_BOX (box), checkmark, FALSE, FALSE, 8); + gtk_box_pack_end (GTK_BOX (box), account_label, FALSE, FALSE, 0); + + gtk_container_add (GTK_CONTAINER (row), box); + + gtk_widget_show (label); + gtk_widget_show (image); + gtk_widget_show (box); + gtk_widget_show (row); + + provider_widget = g_new0 (ProviderWidget, 1); + provider_widget->page = page; + provider_widget->provider = provider; + provider_widget->row = row; + provider_widget->checkmark = checkmark; + provider_widget->account_label = account_label; + + g_object_set_data_full (G_OBJECT (row), "widget", provider_widget, g_free); + + g_hash_table_insert (priv->providers, (char *) provider_type, provider_widget); + + gtk_container_add (GTK_CONTAINER (priv->accounts_list), row); +} + +static void +populate_provider_list (GisGoaPage *page) +{ + g_auto(GStrv) conf_providers = + gis_driver_conf_get_string_list (GIS_PAGE (page)->driver, VENDOR_GOA_GROUP, VENDOR_PROVIDERS_KEY, NULL); + GStrv providers = conf_providers ? conf_providers : + (gchar *[]) { "google", "owncloud", "windows_live", "facebook", NULL }; + + /* This code will read the keyfile containing vendor customization options and + * look for options under the "goa" group, and supports the following keys: + * - providers (optional): list of online account providers to offer + * + * This is how this file might look on a vendor image: + * + * [goa] + * providers=owncloud;imap_smtp + */ + + for (guint i = 0; providers[i]; i++) + add_provider_to_list (page, providers[i]); +} + +static void +sync_visibility (GisGoaPage *page) +{ + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GisAssistant *assistant = gis_driver_get_assistant (GIS_PAGE (page)->driver); + GNetworkMonitor *network_monitor = g_network_monitor_get_default (); + gboolean visible; + + if (gis_assistant_get_current_page (assistant) == GIS_PAGE (page)) + return; + + visible = (priv->accounts_exist || g_network_monitor_get_network_available (network_monitor)); + gtk_widget_set_visible (GTK_WIDGET (page), visible); +} + +static void +sync_accounts (GisGoaPage *page) +{ + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GList *accounts, *l; + + accounts = goa_client_get_accounts (priv->goa_client); + + for (l = accounts; l != NULL; l = l->next) { + GoaObject *object = GOA_OBJECT (l->data); + GoaAccount *account = goa_object_get_account (object); + const char *account_type = goa_account_get_provider_type (account); + ProviderWidget *provider_widget; + + provider_widget = g_hash_table_lookup (priv->providers, account_type); + if (!provider_widget) + continue; + + priv->accounts_exist = TRUE; + + if (provider_widget->displayed_account) + continue; + + provider_widget->displayed_account = account; + sync_provider_widget (provider_widget); + } + + g_list_free_full (accounts, (GDestroyNotify) g_object_unref); + + sync_visibility (page); + gis_page_set_skippable (GIS_PAGE (page), !priv->accounts_exist); + gis_page_set_complete (GIS_PAGE (page), priv->accounts_exist); +} + +static void +accounts_changed (GoaClient *client, GoaObject *object, gpointer user_data) +{ + GisGoaPage *page = GIS_GOA_PAGE (user_data); + sync_accounts (page); +} + +static void +network_status_changed (GNetworkMonitor *monitor, + gboolean available, + gpointer user_data) +{ + GisGoaPage *page = GIS_GOA_PAGE (user_data); + sync_visibility (page); +} + +static void +update_header_func (GtkListBoxRow *child, + GtkListBoxRow *before, + gpointer user_data) +{ + GtkWidget *header; + + if (before == NULL) + return; + + header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header (child, header); + gtk_widget_show (header); +} + +static void +row_activated (GtkListBox *box, + GtkListBoxRow *row, + GisGoaPage *page) +{ + ProviderWidget *provider_widget; + + if (row == NULL) + return; + + provider_widget = g_object_get_data (G_OBJECT (row), "widget"); + g_assert (provider_widget != NULL); + g_assert (provider_widget->displayed_account == NULL); + add_account_to_provider (provider_widget); +} + +static void +gis_goa_page_constructed (GObject *object) +{ + GisGoaPage *page = GIS_GOA_PAGE (object); + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GError *error = NULL; + GNetworkMonitor *network_monitor = g_network_monitor_get_default (); + + G_OBJECT_CLASS (gis_goa_page_parent_class)->constructed (object); + + gis_page_set_skippable (GIS_PAGE (page), TRUE); + + priv->providers = g_hash_table_new (g_str_hash, g_str_equal); + + priv->goa_client = goa_client_new_sync (NULL, &error); + + if (priv->goa_client == NULL) { + g_warning ("Failed to get a GoaClient: %s", error->message); + g_error_free (error); + return; + } + + g_signal_connect (priv->goa_client, "account-added", + G_CALLBACK (accounts_changed), page); + g_signal_connect (priv->goa_client, "account-removed", + G_CALLBACK (accounts_changed), page); + g_signal_connect (network_monitor, "network-changed", + G_CALLBACK (network_status_changed), page); + + gtk_list_box_set_header_func (GTK_LIST_BOX (priv->accounts_list), + update_header_func, + NULL, NULL); + g_signal_connect (priv->accounts_list, "row-activated", + G_CALLBACK (row_activated), page); + + populate_provider_list (page); + sync_accounts (page); +} + +static void +gis_goa_page_dispose (GObject *object) +{ + GisGoaPage *page = GIS_GOA_PAGE (object); + GisGoaPagePrivate *priv = gis_goa_page_get_instance_private (page); + GNetworkMonitor *network_monitor = g_network_monitor_get_default (); + + g_clear_object (&priv->goa_client); + + g_signal_handlers_disconnect_by_func (network_monitor, G_CALLBACK (network_status_changed), page); + + G_OBJECT_CLASS (gis_goa_page_parent_class)->dispose (object); +} + +static void +gis_goa_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("Online Accounts")); +} + +static void +gis_goa_page_class_init (GisGoaPageClass *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-goa-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisGoaPage, accounts_list); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_goa_page_locale_changed; + object_class->constructed = gis_goa_page_constructed; + object_class->dispose = gis_goa_page_dispose; +} + +static void +gis_goa_page_init (GisGoaPage *page) +{ + g_resources_register (goa_get_resource ()); + g_type_ensure (GIS_TYPE_PAGE_HEADER); + + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_goa_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_GOA_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/goa/gis-goa-page.h b/gnome-initial-setup/pages/goa/gis-goa-page.h new file mode 100644 index 0000000..31918bf --- /dev/null +++ b/gnome-initial-setup/pages/goa/gis-goa-page.h @@ -0,0 +1,55 @@ +/* -*- 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> + */ + +#ifndef __GIS_GOA_PAGE_H__ +#define __GIS_GOA_PAGE_H__ + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_GOA_PAGE (gis_goa_page_get_type ()) +#define GIS_GOA_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_GOA_PAGE, GisGoaPage)) +#define GIS_GOA_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_GOA_PAGE, GisGoaPageClass)) +#define GIS_IS_GOA_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_GOA_PAGE)) +#define GIS_IS_GOA_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_GOA_PAGE)) +#define GIS_GOA_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_GOA_PAGE, GisGoaPageClass)) + +typedef struct _GisGoaPage GisGoaPage; +typedef struct _GisGoaPageClass GisGoaPageClass; + +struct _GisGoaPage +{ + GisPage parent; +}; + +struct _GisGoaPageClass +{ + GisPageClass parent_class; +}; + +GType gis_goa_page_get_type (void); + +GisPage *gis_prepare_goa_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_GOA_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/goa/gis-goa-page.ui b/gnome-initial-setup/pages/goa/gis-goa-page.ui new file mode 100644 index 0000000..9ef1d95 --- /dev/null +++ b/gnome-initial-setup/pages/goa/gis-goa-page.ui @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.16.0 on Wed Oct 23 11:13:34 2013 --> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisGoaPage" parent="GisPage"> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <property name="orientation">vertical</property> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin_top">24</property> + <property name="title" translatable="yes">Connect Your Online Accounts</property> + <property name="subtitle" translatable="yes">Connect your accounts to easily access your email, online calendar, contacts, documents and photos.</property> + <property name="icon_name">goa-panel-symbolic</property> + <property name="show_icon" bind-source="GisGoaPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + </object> + </child> + <child> + <object class="GtkFrame" id="frame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label_xalign">0</property> + <property name="shadow_type">in</property> + <property name="margin_top">18</property> + <child> + <object class="GtkListBox" id="accounts_list"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="selection_mode">none</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="footer_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Accounts can be added and removed at any time from the Settings application.</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <property name="margin_bottom">18</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">4</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/goa/goa.gresource.xml b/gnome-initial-setup/pages/goa/goa.gresource.xml new file mode 100644 index 0000000..ad1cfdf --- /dev/null +++ b/gnome-initial-setup/pages/goa/goa.gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks" alias="gis-goa-page.ui">gis-goa-page.ui</file> + </gresource> +</gresources> + diff --git a/gnome-initial-setup/pages/goa/meson.build b/gnome-initial-setup/pages/goa/meson.build new file mode 100644 index 0000000..c7dcaaa --- /dev/null +++ b/gnome-initial-setup/pages/goa/meson.build @@ -0,0 +1,10 @@ +sources += gnome.compile_resources( + 'goa-resources', + files('goa.gresource.xml'), + c_name: 'goa' +) + +sources += files( + 'gis-goa-page.c', + 'gis-goa-page.h' +) diff --git a/gnome-initial-setup/pages/keyboard/cc-ibus-utils.c b/gnome-initial-setup/pages/keyboard/cc-ibus-utils.c new file mode 100644 index 0000000..424c69e --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/cc-ibus-utils.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 Red Hat, Inc + * + * 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/>. + */ + +#include <config.h> + +#ifdef HAVE_IBUS +#include "cc-ibus-utils.h" + +gchar * +engine_get_display_name (IBusEngineDesc *engine_desc) +{ + const gchar *name; + const gchar *language_code; + const gchar *language; + const gchar *textdomain; + gchar *display_name; + + name = ibus_engine_desc_get_longname (engine_desc); + language_code = ibus_engine_desc_get_language (engine_desc); + language = ibus_get_language_name (language_code); + textdomain = ibus_engine_desc_get_textdomain (engine_desc); + if (*textdomain != '\0' && *name != '\0') + name = g_dgettext (textdomain, name); + display_name = g_strdup_printf ("%s (%s)", language, name); + + return display_name; +} + +#endif /* HAVE_IBUS */ diff --git a/gnome-initial-setup/pages/keyboard/cc-ibus-utils.h b/gnome-initial-setup/pages/keyboard/cc-ibus-utils.h new file mode 100644 index 0000000..da3d996 --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/cc-ibus-utils.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2013 Red Hat, Inc + * + * 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_IBUS_UTILS_H__ +#define __GIS_IBUS_UTILS_H__ + +#include <ibus.h> + +G_BEGIN_DECLS + +gchar *engine_get_display_name (IBusEngineDesc *engine_desc); + +G_END_DECLS + +#endif /* __GIS_IBUS_UTILS_H__ */ diff --git a/gnome-initial-setup/pages/keyboard/cc-input-chooser.c b/gnome-initial-setup/pages/keyboard/cc-input-chooser.c new file mode 100644 index 0000000..196abf6 --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/cc-input-chooser.c @@ -0,0 +1,910 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * 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> + * Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" +#include "cc-input-chooser.h" + +#include <locale.h> +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include <gtk/gtk.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> +#include <libgnome-desktop/gnome-xkb-info.h> + +#ifdef HAVE_IBUS +#include <ibus.h> +#include "cc-ibus-utils.h" +#endif + +#include "cc-common-language.h" + +#include <glib-object.h> + +#define INPUT_SOURCE_TYPE_XKB "xkb" +#define INPUT_SOURCE_TYPE_IBUS "ibus" + +#define MIN_ROWS 5 + +struct _CcInputChooserPrivate +{ + GtkWidget *filter_entry; + GtkWidget *input_list; + GHashTable *inputs; + + GtkWidget *scrolled_window; + GtkWidget *no_results; + GtkWidget *more_item; + + gboolean showing_extra; + gchar *locale; + gchar *id; + gchar *type; + GnomeXkbInfo *xkb_info; +#ifdef HAVE_IBUS + IBusBus *ibus; + GHashTable *ibus_engines; + GCancellable *ibus_cancellable; +#endif +}; +typedef struct _CcInputChooserPrivate CcInputChooserPrivate; +G_DEFINE_TYPE_WITH_PRIVATE (CcInputChooser, cc_input_chooser, GTK_TYPE_BOX); + +enum { + PROP_0, + PROP_SHOWING_EXTRA, + PROP_LAST +}; + +static GParamSpec *obj_props[PROP_LAST]; + +enum { + CHANGED, + CONFIRM, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +typedef struct { + GtkWidget *box; + GtkWidget *label; + GtkWidget *checkmark; + + gchar *id; + gchar *type; + gchar *name; + gboolean is_extra; +} InputWidget; + +static InputWidget * +get_input_widget (GtkWidget *widget) +{ + return g_object_get_data (G_OBJECT (widget), "input-widget"); +} + +static GtkWidget * +padded_label_new (char *text) +{ + GtkWidget *widget; + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); + gtk_widget_set_margin_top (widget, 10); + gtk_widget_set_margin_bottom (widget, 10); + gtk_box_pack_start (GTK_BOX (widget), gtk_label_new (text), FALSE, FALSE, 0); + return widget; +} + +static void +input_widget_free (gpointer data) +{ + InputWidget *widget = data; + + g_free (widget->id); + g_free (widget->type); + g_free (widget->name); + g_free (widget); +} + +static gboolean +get_layout (CcInputChooser *chooser, + const gchar *type, + const gchar *id, + const gchar **layout, + const gchar **variant) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + + if (g_strcmp0 (type, INPUT_SOURCE_TYPE_XKB) == 0) { + gnome_xkb_info_get_layout_info (priv->xkb_info, + id, NULL, NULL, + layout, variant); + return TRUE; + } +#ifdef HAVE_IBUS + if (g_strcmp0 (type, INPUT_SOURCE_TYPE_IBUS) == 0) { + IBusEngineDesc *engine_desc = NULL; + + if (priv->ibus_engines) + engine_desc = g_hash_table_lookup (priv->ibus_engines, id); + + if (!engine_desc) + return FALSE; + + *layout = ibus_engine_desc_get_layout (engine_desc); + *variant = ""; + return TRUE; + } +#endif + return FALSE; +} + +static gboolean +preview_cb (GtkLabel *label, + const gchar *uri, + CcInputChooser *chooser) +{ + GtkWidget *row; + InputWidget *widget; + const gchar *layout; + const gchar *variant; + gchar *commandline; + + row = gtk_widget_get_parent (GTK_WIDGET (label)); + widget = get_input_widget (row); + + if (!get_layout (chooser, widget->type, widget->id, &layout, &variant)) + return TRUE; + + if (variant[0]) + commandline = g_strdup_printf ("gkbd-keyboard-display -l \"%s\t%s\"", layout, variant); + else + commandline = g_strdup_printf ("gkbd-keyboard-display -l %s", layout); + g_spawn_command_line_async (commandline, NULL); + g_free (commandline); + + return TRUE; +} + +static GtkWidget * +input_widget_new (CcInputChooser *chooser, + const char *type, + const char *id, + gboolean is_extra) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + GtkWidget *label; + InputWidget *widget = g_new0 (InputWidget, 1); + const gchar *name; + gchar *text; + + if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB)) { + gnome_xkb_info_get_layout_info (priv->xkb_info, id, &name, NULL, NULL, NULL); + } +#ifdef HAVE_IBUS + else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS)) { + if (priv->ibus_engines) + name = engine_get_display_name (g_hash_table_lookup (priv->ibus_engines, id)); + else + name = id; + } +#endif + else { + name = "ERROR"; + } + + widget->id = g_strdup (id); + widget->type = g_strdup (type); + widget->name = g_strdup (name); + widget->is_extra = is_extra; + + widget->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + gtk_widget_set_halign (widget->box, GTK_ALIGN_FILL); + gtk_widget_set_margin_top (widget->box, 10); + gtk_widget_set_margin_bottom (widget->box, 10); + gtk_widget_set_margin_start (widget->box, 10); + gtk_widget_set_margin_end (widget->box, 10); + widget->label = gtk_label_new (name); + gtk_label_set_xalign (GTK_LABEL (widget->label), 0); + gtk_label_set_yalign (GTK_LABEL (widget->label), 0.5); + gtk_label_set_ellipsize (GTK_LABEL (widget->label), PANGO_ELLIPSIZE_END); + gtk_label_set_max_width_chars (GTK_LABEL (widget->label), 40); + gtk_label_set_width_chars (GTK_LABEL (widget->label), 40); + gtk_box_pack_start (GTK_BOX (widget->box), widget->label, FALSE, FALSE, 0); + widget->checkmark = gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU); + gtk_box_pack_start (GTK_BOX (widget->box), widget->checkmark, TRUE, TRUE, 0); + gtk_widget_set_margin_start (widget->checkmark, 10); + gtk_widget_set_margin_end (widget->checkmark, 10); + gtk_widget_set_halign (widget->box, GTK_ALIGN_START); + + text = g_strdup_printf ("<a href='preview'>%s</a>", _("Preview")); + label = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (label), text); + g_free (text); + g_signal_connect (label, "activate-link", + G_CALLBACK (preview_cb), chooser); + gtk_box_pack_start (GTK_BOX (widget->box), label, TRUE, TRUE, 0); + + gtk_widget_show_all (widget->box); + + g_object_set_data_full (G_OBJECT (widget->box), "input-widget", widget, + input_widget_free); + + return widget->box; +} + +static void +sync_checkmark (GtkWidget *row, + gpointer user_data) +{ + CcInputChooser *chooser = user_data; + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + GtkWidget *child; + InputWidget *widget; + gboolean should_be_visible; + + child = gtk_bin_get_child (GTK_BIN (row)); + widget = get_input_widget (child); + + if (widget == NULL) + return; + + if (priv->id == NULL || priv->type == NULL) + should_be_visible = FALSE; + else + should_be_visible = g_strcmp0 (widget->id, priv->id) == 0 && g_strcmp0 (widget->type, priv->type) == 0; + gtk_widget_set_opacity (widget->checkmark, should_be_visible ? 1.0 : 0.0); + + if (widget->is_extra && should_be_visible) + widget->is_extra = FALSE; +} + +static void +sync_all_checkmarks (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + + gtk_container_foreach (GTK_CONTAINER (priv->input_list), + sync_checkmark, chooser); + gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list)); +} + +static GtkWidget * +more_widget_new (void) +{ + GtkWidget *widget; + GtkWidget *arrow; + + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + gtk_widget_set_tooltip_text (widget, _("More…")); + + arrow = gtk_image_new_from_icon_name ("view-more-symbolic", GTK_ICON_SIZE_MENU); + gtk_style_context_add_class (gtk_widget_get_style_context (arrow), "dim-label"); + gtk_widget_set_margin_top (widget, 10); + gtk_widget_set_margin_bottom (widget, 10); + gtk_widget_set_halign (arrow, GTK_ALIGN_CENTER); + gtk_widget_set_valign (arrow, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (widget), arrow, TRUE, TRUE, 0); + gtk_widget_show_all (widget); + + return widget; +} + +static GtkWidget * +no_results_widget_new (void) +{ + GtkWidget *widget; + + /* Translators: a search for input methods or keyboard layouts + * did not yield any results + */ + widget = padded_label_new (_("No inputs found")); + gtk_widget_set_sensitive (widget, FALSE); + gtk_widget_show_all (widget); + return widget; +} + +static void +choose_non_extras_foreach (GtkWidget *row, + gpointer user_data) +{ + GtkWidget *child; + InputWidget *widget; + guint *count = user_data; + + *count += 1; + if (*count > MIN_ROWS) + return; + + child = gtk_bin_get_child (GTK_BIN (row)); + widget = get_input_widget (child); + if (widget == NULL) + return; + + widget->is_extra = FALSE; +} + +static void +choose_non_extras (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + guint count = 0; + + gtk_container_foreach (GTK_CONTAINER (priv->input_list), + choose_non_extras_foreach, &count); +} + +static void +add_rows_to_list (CcInputChooser *chooser, + GList *list, + const gchar *type, + const gchar *default_id) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + const gchar *id; + GtkWidget *widget; + gchar *key; + + for (; list; list = list->next) { + id = (const gchar *) list->data; + + if (g_strcmp0 (id, default_id) == 0) + continue; + + key = g_strdup_printf ("%s::%s", type, id); + if (g_hash_table_contains (priv->inputs, key)) { + g_free (key); + continue; + } + g_hash_table_add (priv->inputs, key); + + widget = input_widget_new (chooser, type, id, TRUE); + gtk_container_add (GTK_CONTAINER (priv->input_list), widget); + } +} + +static void +add_row_to_list (CcInputChooser *chooser, + const gchar *type, + const gchar *id) +{ + GList tmp = { 0 }; + tmp.data = (gpointer)id; + add_rows_to_list (chooser, &tmp, type, NULL); +} + +static void +get_locale_infos (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + const gchar *type = NULL; + const gchar *id = NULL; + gchar *lang, *country; + GList *list; + + if (gnome_get_input_source_from_locale (priv->locale, &type, &id)) { + add_row_to_list (chooser, type, id); + if (!priv->id) { + priv->id = g_strdup (id); + priv->type = g_strdup (type); + } + } + + if (!gnome_parse_locale (priv->locale, &lang, &country, NULL, NULL)) + goto out; + + list = gnome_xkb_info_get_layouts_for_language (priv->xkb_info, lang); + add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id); + g_list_free (list); + + if (country != NULL) { + list = gnome_xkb_info_get_layouts_for_country (priv->xkb_info, country); + add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id); + g_list_free (list); + } + + choose_non_extras (chooser); + + list = gnome_xkb_info_get_all_layouts (priv->xkb_info); + add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id); + g_list_free (list); + + gtk_widget_show_all (priv->input_list); + +out: + g_free (lang); + g_free (country); +} + +static gboolean +input_visible (GtkListBoxRow *row, + gpointer user_data) +{ + CcInputChooser *chooser = user_data; + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + InputWidget *widget; + gboolean visible; + GtkWidget *child; + const char *search_term; + + child = gtk_bin_get_child (GTK_BIN (row)); + if (child == priv->more_item) + return !priv->showing_extra && g_hash_table_size (priv->inputs) > MIN_ROWS; + + widget = get_input_widget (child); + + if (!priv->showing_extra && widget->is_extra) + return FALSE; + + search_term = gtk_entry_get_text (GTK_ENTRY (priv->filter_entry)); + if (!search_term || !*search_term) + return TRUE; + + visible = g_str_match_string (search_term, widget->name, TRUE); + return visible; +} + +static gint +sort_inputs (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer data) +{ + InputWidget *la, *lb; + + la = get_input_widget (gtk_bin_get_child (GTK_BIN (a))); + lb = get_input_widget (gtk_bin_get_child (GTK_BIN (b))); + + if (la == NULL) + return 1; + + if (lb == NULL) + return -1; + + if (la->is_extra && !lb->is_extra) + return 1; + + if (!la->is_extra && lb->is_extra) + return -1; + + return strcmp (la->name, lb->name); +} + +static void +filter_changed (GtkEntry *entry, + CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list)); +} + +static void +show_more (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + + if (g_hash_table_size (priv->inputs) <= MIN_ROWS) + return; + + gtk_widget_show (priv->filter_entry); + gtk_widget_grab_focus (priv->filter_entry); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_widget_set_valign (GTK_WIDGET (chooser), GTK_ALIGN_FILL); + + priv->showing_extra = TRUE; + gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list)); + g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_SHOWING_EXTRA]); +} + +static void +set_input (CcInputChooser *chooser, + const gchar *id, + const gchar *type) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + + if (g_strcmp0 (priv->id, id) == 0 && + g_strcmp0 (priv->type, type) == 0) + return; + + g_free (priv->id); + g_free (priv->type); + priv->id = g_strdup (id); + priv->type = g_strdup (type); + + sync_all_checkmarks (chooser); + + g_signal_emit (chooser, signals[CHANGED], 0); +} + +static gboolean +confirm_choice (gpointer data) +{ + GtkWidget *widget = data; + + g_signal_emit (widget, signals[CONFIRM], 0); + + return G_SOURCE_REMOVE; +} + +static void +row_activated (GtkListBox *box, + GtkListBoxRow *row, + CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + GtkWidget *child; + InputWidget *widget; + + if (row == NULL) + return; + + child = gtk_bin_get_child (GTK_BIN (row)); + if (child == priv->more_item) { + show_more (chooser); + } else { + widget = get_input_widget (child); + if (widget == NULL) + return; + if (g_strcmp0 (priv->id, widget->id) == 0 && + g_strcmp0 (priv->type, widget->type) == 0) + confirm_choice (chooser); + else + set_input (chooser, widget->id, widget->type); + } +} + +static void +update_header_func (GtkListBoxRow *child, + GtkListBoxRow *before, + gpointer user_data) +{ + GtkWidget *header; + + if (before == NULL) { + gtk_list_box_row_set_header (child, NULL); + return; + } + + header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header (child, header); + gtk_widget_show (header); +} + +#ifdef HAVE_IBUS +static void +update_ibus_active_sources (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + GList *rows, *l; + InputWidget *row; + const gchar *type; + const gchar *id; + IBusEngineDesc *engine_desc; + gchar *name; + + rows = gtk_container_get_children (GTK_CONTAINER (priv->input_list)); + for (l = rows; l; l = l->next) { + row = get_input_widget (gtk_bin_get_child (GTK_BIN (l->data))); + if (row == NULL) + continue; + + type = row->type; + id = row->id; + if (g_strcmp0 (type, INPUT_SOURCE_TYPE_IBUS) != 0) + continue; + + engine_desc = g_hash_table_lookup (priv->ibus_engines, id); + if (engine_desc) { + name = engine_get_display_name (engine_desc); + gtk_label_set_text (GTK_LABEL (row->label), name); + g_free (name); + } + } + g_list_free (rows); +} + +static void +get_ibus_locale_infos (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + GHashTableIter iter; + const gchar *engine_id; + IBusEngineDesc *engine; + + if (!priv->ibus_engines) + return; + + g_hash_table_iter_init (&iter, priv->ibus_engines); + while (g_hash_table_iter_next (&iter, (gpointer *) &engine_id, (gpointer *) &engine)) + add_row_to_list (chooser, INPUT_SOURCE_TYPE_IBUS, engine_id); +} + +static void +fetch_ibus_engines_result (GObject *object, + GAsyncResult *result, + CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv; + GList *list, *l; + GError *error; + + error = NULL; + list = ibus_bus_list_engines_async_finish (IBUS_BUS (object), result, &error); + if (!list && error) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Couldn't finish IBus request: %s", error->message); + g_error_free (error); + return; + } + + priv = cc_input_chooser_get_instance_private (chooser); + g_clear_object (&priv->ibus_cancellable); + + /* Maps engine ids to engine description objects */ + priv->ibus_engines = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); + + for (l = list; l; l = l->next) { + IBusEngineDesc *engine = l->data; + const gchar *engine_id; + + engine_id = ibus_engine_desc_get_name (engine); + if (g_str_has_prefix (engine_id, "xkb:")) + g_object_unref (engine); + else + g_hash_table_replace (priv->ibus_engines, (gpointer)engine_id, engine); + } + g_list_free (list); + + update_ibus_active_sources (chooser); + get_ibus_locale_infos (chooser); + + sync_all_checkmarks (chooser); +} + +static void +fetch_ibus_engines (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + + priv->ibus_cancellable = g_cancellable_new (); + + ibus_bus_list_engines_async (priv->ibus, + -1, + priv->ibus_cancellable, + (GAsyncReadyCallback)fetch_ibus_engines_result, + chooser); + + /* We've got everything we needed, don't want to be called again. */ + g_signal_handlers_disconnect_by_func (priv->ibus, fetch_ibus_engines, chooser); +} + +static void +maybe_start_ibus (void) +{ + /* IBus doesn't export API in the session bus. The only thing + * we have there is a well known name which we can use as a + * sure-fire way to activate it. + */ + g_bus_unwatch_name (g_bus_watch_name (G_BUS_TYPE_SESSION, + IBUS_SERVICE_IBUS, + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + NULL, + NULL, + NULL, + NULL)); +} +#endif + +static void +cc_input_chooser_constructed (GObject *object) +{ + CcInputChooser *chooser = CC_INPUT_CHOOSER (object); + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + + G_OBJECT_CLASS (cc_input_chooser_parent_class)->constructed (object); + + priv->xkb_info = gnome_xkb_info_new (); + +#ifdef HAVE_IBUS + ibus_init (); + if (!priv->ibus) { + priv->ibus = ibus_bus_new_async (); + if (ibus_bus_is_connected (priv->ibus)) + fetch_ibus_engines (chooser); + else + g_signal_connect_swapped (priv->ibus, "connected", + G_CALLBACK (fetch_ibus_engines), chooser); + } + maybe_start_ibus (); +#endif + + priv->inputs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + priv->more_item = more_widget_new (); + priv->no_results = no_results_widget_new (); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->input_list), + sort_inputs, chooser, NULL); + gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->input_list), + input_visible, chooser, NULL); + gtk_list_box_set_header_func (GTK_LIST_BOX (priv->input_list), + update_header_func, chooser, NULL); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->input_list), + GTK_SELECTION_NONE); + + if (priv->locale == NULL) { + priv->locale = cc_common_language_get_current_language (); + } + + get_locale_infos (chooser); +#ifdef HAVE_IBUS + get_ibus_locale_infos (chooser); +#endif + + gtk_container_add (GTK_CONTAINER (priv->input_list), priv->more_item); + gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->input_list), priv->no_results); + + g_signal_connect (priv->filter_entry, "changed", + G_CALLBACK (filter_changed), + chooser); + + g_signal_connect (priv->input_list, "row-activated", + G_CALLBACK (row_activated), chooser); + + sync_all_checkmarks (chooser); +} + +static void +cc_input_chooser_finalize (GObject *object) +{ + CcInputChooser *chooser = CC_INPUT_CHOOSER (object); + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + + g_clear_object (&priv->xkb_info); + g_hash_table_unref (priv->inputs); +#ifdef HAVE_IBUS + g_clear_object (&priv->ibus); + if (priv->ibus_cancellable) + g_cancellable_cancel (priv->ibus_cancellable); + g_clear_object (&priv->ibus_cancellable); + g_clear_pointer (&priv->ibus_engines, g_hash_table_destroy); +#endif + + G_OBJECT_CLASS (cc_input_chooser_parent_class)->finalize (object); +} + +static void +cc_input_chooser_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcInputChooser *chooser = CC_INPUT_CHOOSER (object); + switch (prop_id) { + case PROP_SHOWING_EXTRA: + g_value_set_boolean (value, cc_input_chooser_get_showing_extra (chooser)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_input_chooser_class_init (CcInputChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/input-chooser.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcInputChooser, filter_entry); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcInputChooser, input_list); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcInputChooser, scrolled_window); + + object_class->finalize = cc_input_chooser_finalize; + object_class->get_property = cc_input_chooser_get_property; + object_class->constructed = cc_input_chooser_constructed; + + obj_props[PROP_SHOWING_EXTRA] = + g_param_spec_string ("showing-extra", "", "", "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[CONFIRM] = + g_signal_new ("confirm", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_class_install_properties (object_class, PROP_LAST, obj_props); +} + +static void +cc_input_chooser_init (CcInputChooser *chooser) +{ + gtk_widget_init_template (GTK_WIDGET (chooser)); +} + +void +cc_input_chooser_clear_filter (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + gtk_entry_set_text (GTK_ENTRY (priv->filter_entry), ""); +} + +const gchar * +cc_input_chooser_get_input_id (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + return priv->id; +} + +const gchar * +cc_input_chooser_get_input_type (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + return priv->type; +} + +void +cc_input_chooser_get_layout (CcInputChooser *chooser, + const gchar **layout, + const gchar **variant) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + + if (!get_layout (chooser, priv->type, priv->id, layout, variant)) { + if (layout != NULL) + *layout = NULL; + if (variant != NULL) + *variant = NULL; + } +} + +void +cc_input_chooser_set_input (CcInputChooser *chooser, + const gchar *id, + const gchar *type) +{ + set_input (chooser, id, type); +} + +gboolean +cc_input_chooser_get_showing_extra (CcInputChooser *chooser) +{ + CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser); + return priv->showing_extra; +} diff --git a/gnome-initial-setup/pages/keyboard/cc-input-chooser.h b/gnome-initial-setup/pages/keyboard/cc-input-chooser.h new file mode 100644 index 0000000..dfd6a28 --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/cc-input-chooser.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 Red Hat, Inc + * + * 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> + * Matthias Clasen <mclasen@redhat.com> + */ + +#ifndef __GIS_INPUT_CHOOSER_H__ +#define __GIS_INPUT_CHOOSER_H__ + +#include <gtk/gtk.h> +#include <glib-object.h> + +#define CC_TYPE_INPUT_CHOOSER (cc_input_chooser_get_type ()) +#define CC_INPUT_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_INPUT_CHOOSER, CcInputChooser)) +#define CC_INPUT_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TYPE_INPUT_CHOOSER, CcInputChooserClass)) +#define CC_IS_INPUT_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_INPUT_CHOOSER)) +#define CC_IS_INPUT_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TYPE_INPUT_CHOOSER)) +#define CC_INPUT_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_INPUT_CHOOSER, CcInputChooserClass)) + +G_BEGIN_DECLS + +typedef struct _CcInputChooser CcInputChooser; +typedef struct _CcInputChooserClass CcInputChooserClass; + +struct _CcInputChooser +{ + GtkBox parent; +}; + +struct _CcInputChooserClass +{ + GtkBoxClass parent_class; +}; + +GType cc_input_chooser_get_type (void); + +void cc_input_chooser_clear_filter (CcInputChooser *chooser); +const gchar * cc_input_chooser_get_input_id (CcInputChooser *chooser); +const gchar * cc_input_chooser_get_input_type (CcInputChooser *chooser); +void cc_input_chooser_set_input (CcInputChooser *chooser, + const gchar *id, + const gchar *type); +void cc_input_chooser_get_layout (CcInputChooser *chooser, + const gchar **layout, + const gchar **variant); +gboolean cc_input_chooser_get_showing_extra (CcInputChooser *chooser); + +G_END_DECLS + +#endif /* __GIS_INPUT_CHOOSER_H__ */ diff --git a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c new file mode 100644 index 0000000..3adfd66 --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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/>. + * + * Author: Sergey Udaltsov <svu@gnome.org> + * Michael Wood <michael.g.wood@intel.com> + * + * Based on gnome-control-center cc-region-panel.c + */ + +#define PAGE_ID "keyboard" + +#include "config.h" + +#include <locale.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gtk/gtk.h> +#include <polkit/polkit.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> + +#include "gis-keyboard-page.h" +#include "keyboard-resources.h" +#include "cc-input-chooser.h" + +#include "cc-common-language.h" + +#include "gis-page-header.h" + +#define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources" +#define KEY_CURRENT_INPUT_SOURCE "current" +#define KEY_INPUT_SOURCES "sources" + +struct _GisKeyboardPagePrivate { + GtkWidget *input_chooser; + + GDBusProxy *localed; + GCancellable *cancellable; + GPermission *permission; + GSettings *input_settings; + + GSList *system_sources; +}; +typedef struct _GisKeyboardPagePrivate GisKeyboardPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisKeyboardPage, gis_keyboard_page, GIS_TYPE_PAGE); + +static void +gis_keyboard_page_finalize (GObject *object) +{ + GisKeyboardPage *self = GIS_KEYBOARD_PAGE (object); + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + + if (priv->cancellable) + g_cancellable_cancel (priv->cancellable); + g_clear_object (&priv->cancellable); + + 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_OBJECT_CLASS (gis_keyboard_page_parent_class)->finalize (object); +} + +static void +set_input_settings (GisKeyboardPage *self) +{ + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + const gchar *type; + const gchar *id; + GVariantBuilder builder; + GSList *l; + gboolean is_xkb_source = FALSE; + + 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)); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)")); + + if (g_str_equal (type, "xkb")) { + g_variant_builder_add (&builder, "(ss)", type, id); + is_xkb_source = TRUE; + } + + 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; + + g_variant_builder_add (&builder, "(ss)", "xkb", sid); + } + + if (!is_xkb_source) + g_variant_builder_add (&builder, "(ss)", type, id); + + 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_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; + + 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 + + g_dbus_proxy_call (priv->localed, + "SetX11Keyboard", + g_variant_new ("(ssssbb)", layouts->str, "", variants->str, "", TRUE, TRUE), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); + g_string_free (layouts, TRUE); + g_string_free (variants, TRUE); +} + +static void +change_locale_permission_acquired (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GisKeyboardPage *page = GIS_KEYBOARD_PAGE (data); + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (page); + GError *error = NULL; + gboolean allowed; + + allowed = g_permission_acquire_finish (priv->permission, res, &error); + if (error) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to acquire permission: %s", error->message); + g_error_free (error); + return; + } + + if (allowed) + set_localed_input (GIS_KEYBOARD_PAGE (data)); +} + +static void +update_input (GisKeyboardPage *self) +{ + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + + set_input_settings (self); + + if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) { + if (g_permission_get_allowed (priv->permission)) { + set_localed_input (self); + } else if (g_permission_get_can_acquire (priv->permission)) { + g_permission_acquire_async (priv->permission, + NULL, + change_locale_permission_acquired, + self); + } + } +} + +static gboolean +gis_keyboard_page_apply (GisPage *page, + GCancellable *cancellable) +{ + update_input (GIS_KEYBOARD_PAGE (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) +{ + 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); +} + +static void +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); +} + +static void +preselect_input_source (GisKeyboardPage *self) +{ + const gchar *type; + const gchar *id; + gchar *language; + gboolean desktop_got_something; + gboolean desktop_got_input_method; + + 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); + + 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"); + } + + g_free (language); +} + +static void +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); + gis_page_set_complete (GIS_PAGE (self), complete); +} + +static void +localed_proxy_ready (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GisKeyboardPage *self = data; + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + 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; + } + + priv->localed = proxy; + + preselect_input_source (self); + update_page_complete (self); +} + +static void +input_confirmed (CcInputChooser *chooser, + GisKeyboardPage *self) +{ + gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (self)->driver)); +} + +static void +input_changed (CcInputChooser *chooser, + GisKeyboardPage *self) +{ + update_page_complete (self); +} + +static void +gis_keyboard_page_constructed (GObject *object) +{ + GisKeyboardPage *self = GIS_KEYBOARD_PAGE (object); + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + + g_type_ensure (CC_TYPE_INPUT_CHOOSER); + + G_OBJECT_CLASS (gis_keyboard_page_parent_class)->constructed (object); + + g_signal_connect (priv->input_chooser, "confirm", + G_CALLBACK (input_confirmed), self); + g_signal_connect (priv->input_chooser, "changed", + G_CALLBACK (input_changed), self); + + priv->input_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR); + g_settings_delay (priv->input_settings); + + priv->cancellable = g_cancellable_new (); + + 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) localed_proxy_ready, + 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); + + update_page_complete (self); + + gtk_widget_show (GTK_WIDGET (self)); +} + +static void +gis_keyboard_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("Typing")); +} + +static void +gis_keyboard_page_class_init (GisKeyboardPageClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GisPageClass * page_class = GIS_PAGE_CLASS (klass); + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-keyboard-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisKeyboardPage, input_chooser); + + page_class->page_id = PAGE_ID; + page_class->apply = gis_keyboard_page_apply; + page_class->skip = gis_keyboard_page_skip; + page_class->locale_changed = gis_keyboard_page_locale_changed; + object_class->constructed = gis_keyboard_page_constructed; + object_class->finalize = gis_keyboard_page_finalize; +} + +static void +gis_keyboard_page_init (GisKeyboardPage *self) +{ + g_resources_register (keyboard_get_resource ()); + g_type_ensure (GIS_TYPE_PAGE_HEADER); + g_type_ensure (CC_TYPE_INPUT_CHOOSER); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GisPage * +gis_prepare_keyboard_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_KEYBOARD_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.h b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.h new file mode 100644 index 0000000..d5710a0 --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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/>. + * + * Author: Sergey Udaltsov <svu@gnome.org> + * + */ + + +#ifndef _GIS_KEYBOARD_PAGE_H +#define _GIS_KEYBOARD_PAGE_H + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_KEYBOARD_PAGE gis_keyboard_page_get_type() + +#define GIS_KEYBOARD_PAGE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + GIS_TYPE_KEYBOARD_PAGE, GisKeyboardPage)) + +#define GIS_KEYBOARD_PAGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + GIS_TYPE_KEYBOARD_PAGE, GisKeyboardPageClass)) + +#define GIS_IS_KEYBOARD_PAGE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + GIS_TYPE_KEYBOARD_PAGE)) + +#define GIS_IS_KEYBOARD_PAGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GIS_TYPE_KEYBOARD_PAGE)) + +#define GIS_KEYBOARD_PAGE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + GIS_TYPE_KEYBOARD_PAGE, GisKeyboardPageClass)) + +typedef struct _GisKeyboardPage GisKeyboardPage; +typedef struct _GisKeyboardPageClass GisKeyboardPageClass; + +struct _GisKeyboardPage +{ + GisPage parent; +}; + +struct _GisKeyboardPageClass +{ + GisPageClass parent_class; +}; + +GType gis_keyboard_page_get_type (void) G_GNUC_CONST; + +GisPage *gis_prepare_keyboard_page (GisDriver *driver); + +G_END_DECLS + +#endif /* _GIS_KEYBOARD_PAGE_H */ diff --git a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.ui b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.ui new file mode 100644 index 0000000..8d022fb --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.ui @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="GisKeyboardPage" parent="GisPage"> + <child> + <object class="GtkBox" id="page"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <property name="orientation">vertical</property> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin_top">24</property> + <property name="title" translatable="yes">Typing</property> + <property name="subtitle" translatable="yes">Select your keyboard layout or an input method.</property> + <property name="icon_name">input-keyboard-symbolic</property> + <property name="show_icon" bind-source="GisKeyboardPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + </object> + </child> + <child> + <object class="CcInputChooser" id="input_chooser"> + <property name="margin_top">18</property> + <property name="margin_bottom">18</property> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">start</property> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/keyboard/input-chooser.ui b/gnome-initial-setup/pages/keyboard/input-chooser.ui new file mode 100644 index 0000000..f3d4fb9 --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/input-chooser.ui @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="3.0"/> + <template class="CcInputChooser" parent="GtkBox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">10</property> + <child> + <object class="GtkScrolledWindow" id="scrolled_window"> + <property name="visible">True</property> + <property name="hscrollbar-policy">never</property> + <property name="vscrollbar-policy">never</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkListBox" id="input_list"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="vexpand">True</property> + <property name="halign">fill</property> + <property name="valign">fill</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkSearchEntry" id="filter_entry"> + <property name="visible">False</property> + <property name="hexpand">True</property> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/keyboard/keyboard.gresource.xml b/gnome-initial-setup/pages/keyboard/keyboard.gresource.xml new file mode 100644 index 0000000..103b3f1 --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/keyboard.gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks">gis-keyboard-page.ui</file> + <file preprocess="xml-stripblanks">input-chooser.ui</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/keyboard/meson.build b/gnome-initial-setup/pages/keyboard/meson.build new file mode 100644 index 0000000..69d6de8 --- /dev/null +++ b/gnome-initial-setup/pages/keyboard/meson.build @@ -0,0 +1,14 @@ +sources += gnome.compile_resources( + 'keyboard-resources', + files('keyboard.gresource.xml'), + c_name: 'keyboard' +) + +sources += files( + 'cc-input-chooser.c', + 'cc-input-chooser.h', + 'cc-ibus-utils.c', + 'cc-ibus-utils.h', + 'gis-keyboard-page.c', + 'gis-keyboard-page.h' +) diff --git a/gnome-initial-setup/pages/language/cc-language-chooser.c b/gnome-initial-setup/pages/language/cc-language-chooser.c new file mode 100644 index 0000000..fa8531c --- /dev/null +++ b/gnome-initial-setup/pages/language/cc-language-chooser.c @@ -0,0 +1,624 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 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> + * Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" +#include "cc-language-chooser.h" + +#include <locale.h> +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include <gtk/gtk.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> + +#include "cc-common-language.h" +#include "cc-util.h" + +#include <glib-object.h> + +struct _CcLanguageChooserPrivate +{ + GtkWidget *filter_entry; + GtkWidget *language_list; + + GtkWidget *scrolled_window; + GtkWidget *no_results; + GtkWidget *more_item; + + gboolean showing_extra; + gchar *language; +}; +typedef struct _CcLanguageChooserPrivate CcLanguageChooserPrivate; +G_DEFINE_TYPE_WITH_PRIVATE (CcLanguageChooser, cc_language_chooser, GTK_TYPE_BOX); + +enum { + PROP_0, + PROP_LANGUAGE, + PROP_SHOWING_EXTRA, + PROP_LAST, +}; + +static GParamSpec *obj_props[PROP_LAST]; + +enum { + CONFIRM, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +typedef struct { + GtkWidget *box; + GtkWidget *checkmark; + + gchar *locale_id; + gchar *locale_name; + gchar *locale_current_name; + gchar *locale_untranslated_name; + gchar *sort_key; + gboolean is_extra; +} LanguageWidget; + +static LanguageWidget * +get_language_widget (GtkWidget *widget) +{ + return g_object_get_data (G_OBJECT (widget), "language-widget"); +} + +static GtkWidget * +padded_label_new (char *text) +{ + GtkWidget *widget; + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); + gtk_widget_set_margin_top (widget, 10); + gtk_widget_set_margin_bottom (widget, 10); + gtk_box_pack_start (GTK_BOX (widget), gtk_label_new (text), FALSE, FALSE, 0); + return widget; +} + +static void +language_widget_free (gpointer data) +{ + LanguageWidget *widget = data; + + /* This is called when the box is destroyed, + * so don't bother destroying the widget and + * children again. */ + g_free (widget->locale_id); + g_free (widget->locale_name); + g_free (widget->locale_current_name); + g_free (widget->locale_untranslated_name); + g_free (widget->sort_key); + g_free (widget); +} + +static GtkWidget * +language_widget_new (const char *locale_id, + gboolean is_extra) +{ + GtkWidget *label; + gchar *locale_name, *locale_current_name, *locale_untranslated_name; + gchar *language = NULL; + gchar *language_name; + gchar *country = NULL; + gchar *country_name = NULL; + LanguageWidget *widget = g_new0 (LanguageWidget, 1); + + if (!gnome_parse_locale (locale_id, &language, &country, NULL, NULL)) + return NULL; + + language_name = gnome_get_language_from_code (language, locale_id); + if (language_name == NULL) + language_name = gnome_get_language_from_code (language, NULL); + + if (country) { + country_name = gnome_get_country_from_code (country, locale_id); + if (country_name == NULL) + country_name = gnome_get_country_from_code (country, NULL); + } + + locale_name = gnome_get_language_from_locale (locale_id, locale_id); + locale_current_name = gnome_get_language_from_locale (locale_id, NULL); + locale_untranslated_name = gnome_get_language_from_locale (locale_id, "C"); + + widget->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + gtk_widget_set_margin_top (widget->box, 10); + gtk_widget_set_margin_bottom (widget->box, 10); + gtk_widget_set_margin_start (widget->box, 10); + gtk_widget_set_margin_end (widget->box, 10); + gtk_widget_set_halign (widget->box, GTK_ALIGN_FILL); + + label = gtk_label_new (language_name); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_label_set_max_width_chars (GTK_LABEL (label), 30); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_box_pack_start (GTK_BOX (widget->box), label, FALSE, FALSE, 0); + + widget->checkmark = gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU); + gtk_box_pack_start (GTK_BOX (widget->box), widget->checkmark, FALSE, FALSE, 0); + gtk_widget_show (widget->checkmark); + + if (country_name) { + label = gtk_label_new (country_name); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_label_set_max_width_chars (GTK_LABEL (label), 30); + gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label"); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_widget_set_halign (label, GTK_ALIGN_END); + gtk_box_pack_end (GTK_BOX (widget->box), label, FALSE, FALSE, 0); + } + + widget->locale_id = g_strdup (locale_id); + widget->locale_name = locale_name; + widget->locale_current_name = locale_current_name; + widget->locale_untranslated_name = locale_untranslated_name; + widget->is_extra = is_extra; + widget->sort_key = cc_util_normalize_casefold_and_unaccent (locale_name); + + g_object_set_data_full (G_OBJECT (widget->box), "language-widget", widget, + language_widget_free); + + g_free (language); + g_free (language_name); + g_free (country); + g_free (country_name); + + return widget->box; +} + +static void +sync_checkmark (GtkWidget *row, + gpointer user_data) +{ + GtkWidget *child; + LanguageWidget *widget; + gchar *locale_id; + gboolean should_be_visible; + + child = gtk_bin_get_child (GTK_BIN (row)); + widget = get_language_widget (child); + + if (widget == NULL) + return; + + locale_id = user_data; + should_be_visible = g_str_equal (widget->locale_id, locale_id); + gtk_widget_set_opacity (widget->checkmark, should_be_visible ? 1.0 : 0.0); +} + +static void +sync_all_checkmarks (CcLanguageChooser *chooser) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + + gtk_container_foreach (GTK_CONTAINER (priv->language_list), + sync_checkmark, priv->language); +} + +static GtkWidget * +more_widget_new (void) +{ + GtkWidget *widget; + GtkWidget *arrow; + + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + gtk_widget_set_tooltip_text (widget, _("More…")); + + arrow = gtk_image_new_from_icon_name ("view-more-symbolic", GTK_ICON_SIZE_MENU); + gtk_style_context_add_class (gtk_widget_get_style_context (arrow), "dim-label"); + gtk_widget_set_margin_top (widget, 10); + gtk_widget_set_margin_bottom (widget, 10); + gtk_box_pack_start (GTK_BOX (widget), arrow, TRUE, TRUE, 0); + + return widget; +} + +static GtkWidget * +no_results_widget_new (void) +{ + GtkWidget *widget; + + widget = padded_label_new (_("No languages found")); + gtk_widget_set_sensitive (widget, FALSE); + gtk_widget_show_all (widget); + return widget; +} + +static void +add_one_language (CcLanguageChooser *chooser, + const char *locale_id, + gboolean is_initial) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + GtkWidget *widget; + + if (!cc_common_language_has_font (locale_id)) { + return; + } + + widget = language_widget_new (locale_id, !is_initial); + if (widget) + gtk_container_add (GTK_CONTAINER (priv->language_list), widget); +} + +static void +add_languages (CcLanguageChooser *chooser, + char **locale_ids, + GHashTable *initial) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + GHashTableIter iter; + gchar *key; + + g_hash_table_iter_init (&iter, initial); + while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL)) { + add_one_language (chooser, key, TRUE); + } + + while (*locale_ids) { + const gchar *locale_id; + + locale_id = *locale_ids; + locale_ids ++; + + if (!g_hash_table_lookup (initial, locale_id)) + add_one_language (chooser, locale_id, FALSE); + } + + gtk_container_add (GTK_CONTAINER (priv->language_list), priv->more_item); + gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->language_list), priv->no_results); + + gtk_widget_show_all (priv->language_list); +} + +static void +add_all_languages (CcLanguageChooser *chooser) +{ + g_auto(GStrv) locale_ids = NULL; + g_autoptr(GHashTable) initial = NULL; + + locale_ids = gnome_get_all_locales (); + initial = cc_common_language_get_initial_languages (); + add_languages (chooser, locale_ids, initial); +} + +static gboolean +language_visible (GtkListBoxRow *row, + gpointer user_data) +{ + CcLanguageChooser *chooser = user_data; + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + LanguageWidget *widget; + gboolean visible; + GtkWidget *child; + const char *search_term; + + child = gtk_bin_get_child (GTK_BIN (row)); + if (child == priv->more_item) + return !priv->showing_extra; + + widget = get_language_widget (child); + + if (!priv->showing_extra && widget->is_extra) + return FALSE; + + search_term = gtk_entry_get_text (GTK_ENTRY (priv->filter_entry)); + if (!search_term || !*search_term) + return TRUE; + + visible = FALSE; + + visible = g_str_match_string (search_term, widget->locale_name, TRUE); + if (visible) + goto out; + + visible = g_str_match_string (search_term, widget->locale_current_name, TRUE); + if (visible) + goto out; + + visible = g_str_match_string (search_term, widget->locale_untranslated_name, TRUE); + if (visible) + goto out; + + out: + return visible; +} + +static gint +sort_languages (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer data) +{ + LanguageWidget *la, *lb; + int ret; + + la = get_language_widget (gtk_bin_get_child (GTK_BIN (a))); + lb = get_language_widget (gtk_bin_get_child (GTK_BIN (b))); + + if (la == NULL) + return 1; + + if (lb == NULL) + return -1; + + if (la->is_extra && !lb->is_extra) + return 1; + + if (!la->is_extra && lb->is_extra) + return -1; + + ret = g_strcmp0 (la->sort_key, lb->sort_key); + if (ret != 0) + return ret; + + return g_strcmp0 (la->locale_id, lb->locale_id); +} + +static void +filter_changed (GtkEntry *entry, + CcLanguageChooser *chooser) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list)); +} + +static void +show_more (CcLanguageChooser *chooser) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + + gtk_widget_show (priv->filter_entry); + gtk_widget_grab_focus (priv->filter_entry); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_widget_set_valign (GTK_WIDGET (chooser), GTK_ALIGN_FILL); + + priv->showing_extra = TRUE; + gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list)); + g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_SHOWING_EXTRA]); +} + +static void +set_locale_id (CcLanguageChooser *chooser, + const gchar *new_locale_id) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + + if (g_strcmp0 (priv->language, new_locale_id) == 0) + return; + + g_free (priv->language); + priv->language = g_strdup (new_locale_id); + + sync_all_checkmarks (chooser); + + g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_LANGUAGE]); +} + +static gboolean +confirm_choice (gpointer data) +{ + GtkWidget *widget = data; + + g_signal_emit (widget, signals[CONFIRM], 0); + + return G_SOURCE_REMOVE; +} + +static void +row_activated (GtkListBox *box, + GtkListBoxRow *row, + CcLanguageChooser *chooser) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + GtkWidget *child; + LanguageWidget *widget; + + if (row == NULL) + return; + + child = gtk_bin_get_child (GTK_BIN (row)); + if (child == priv->more_item) { + show_more (chooser); + } else { + widget = get_language_widget (child); + if (widget == NULL) + return; + if (g_strcmp0 (priv->language, widget->locale_id) == 0) + g_idle_add (confirm_choice, chooser); + else + set_locale_id (chooser, widget->locale_id); + } +} + +static void +update_header_func (GtkListBoxRow *child, + GtkListBoxRow *before, + gpointer user_data) +{ + GtkWidget *header; + + if (before == NULL) { + gtk_list_box_row_set_header (child, NULL); + return; + } + + header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header (child, header); + gtk_widget_show (header); +} + +static void +cc_language_chooser_constructed (GObject *object) +{ + CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object); + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + + G_OBJECT_CLASS (cc_language_chooser_parent_class)->constructed (object); + + priv->more_item = more_widget_new (); + priv->no_results = no_results_widget_new (); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->language_list), + sort_languages, chooser, NULL); + gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->language_list), + language_visible, chooser, NULL); + gtk_list_box_set_header_func (GTK_LIST_BOX (priv->language_list), + update_header_func, chooser, NULL); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->language_list), + GTK_SELECTION_NONE); + add_all_languages (chooser); + + g_signal_connect (priv->filter_entry, "changed", + G_CALLBACK (filter_changed), + chooser); + + g_signal_connect (priv->language_list, "row-activated", + G_CALLBACK (row_activated), chooser); + + if (priv->language == NULL) + priv->language = cc_common_language_get_current_language (); + + sync_all_checkmarks (chooser); +} + +static void +cc_language_chooser_finalize (GObject *object) +{ + CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object); + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + + g_free (priv->language); + + G_OBJECT_CLASS (cc_language_chooser_parent_class)->finalize (object); +} + +static void +cc_language_chooser_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object); + switch (prop_id) { + case PROP_LANGUAGE: + g_value_set_string (value, cc_language_chooser_get_language (chooser)); + break; + case PROP_SHOWING_EXTRA: + g_value_set_boolean (value, cc_language_chooser_get_showing_extra (chooser)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_language_chooser_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object); + switch (prop_id) { + case PROP_LANGUAGE: + cc_language_chooser_set_language (chooser, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_language_chooser_class_init (CcLanguageChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/control-center/language-chooser.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, filter_entry); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, language_list); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, scrolled_window); + + object_class->finalize = cc_language_chooser_finalize; + object_class->get_property = cc_language_chooser_get_property; + object_class->set_property = cc_language_chooser_set_property; + object_class->constructed = cc_language_chooser_constructed; + + signals[CONFIRM] = g_signal_new ("confirm", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (CcLanguageChooserClass, confirm), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + obj_props[PROP_LANGUAGE] = + g_param_spec_string ("language", "", "", "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_props[PROP_SHOWING_EXTRA] = + g_param_spec_string ("showing-extra", "", "", "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST, obj_props); +} + +static void +cc_language_chooser_init (CcLanguageChooser *chooser) +{ + gtk_widget_init_template (GTK_WIDGET (chooser)); +} + +void +cc_language_chooser_clear_filter (CcLanguageChooser *chooser) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + gtk_entry_set_text (GTK_ENTRY (priv->filter_entry), ""); +} + +const gchar * +cc_language_chooser_get_language (CcLanguageChooser *chooser) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + return priv->language; +} + +void +cc_language_chooser_set_language (CcLanguageChooser *chooser, + const gchar *language) +{ + set_locale_id (chooser, language); +} + +gboolean +cc_language_chooser_get_showing_extra (CcLanguageChooser *chooser) +{ + CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); + return priv->showing_extra; +} diff --git a/gnome-initial-setup/pages/language/cc-language-chooser.h b/gnome-initial-setup/pages/language/cc-language-chooser.h new file mode 100644 index 0000000..749af78 --- /dev/null +++ b/gnome-initial-setup/pages/language/cc-language-chooser.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 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> + * Matthias Clasen <mclasen@redhat.com> + */ + +#ifndef __CC_LANGUAGE_CHOOSER_H__ +#define __CC_LANGUAGE_CHOOSER_H__ + +#include <gtk/gtk.h> +#include <glib-object.h> + +#define CC_TYPE_LANGUAGE_CHOOSER (cc_language_chooser_get_type ()) +#define CC_LANGUAGE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_LANGUAGE_CHOOSER, CcLanguageChooser)) +#define CC_LANGUAGE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TYPE_LANGUAGE_CHOOSER, CcLanguageChooserClass)) +#define CC_IS_LANGUAGE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_LANGUAGE_CHOOSER)) +#define CC_IS_LANGUAGE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TYPE_LANGUAGE_CHOOSER)) +#define CC_LANGUAGE_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_LANGUAGE_CHOOSER, CcLanguageChooserClass)) + +G_BEGIN_DECLS + +typedef struct _CcLanguageChooser CcLanguageChooser; +typedef struct _CcLanguageChooserClass CcLanguageChooserClass; + +struct _CcLanguageChooser +{ + GtkBox parent; +}; + +struct _CcLanguageChooserClass +{ + GtkBoxClass parent_class; + + void (*confirm) (CcLanguageChooser *chooser); +}; + +GType cc_language_chooser_get_type (void); + +void cc_language_chooser_clear_filter (CcLanguageChooser *chooser); +const gchar * cc_language_chooser_get_language (CcLanguageChooser *chooser); +void cc_language_chooser_set_language (CcLanguageChooser *chooser, + const gchar *language); +gboolean cc_language_chooser_get_showing_extra (CcLanguageChooser *chooser); + +G_END_DECLS + +#endif /* __CC_LANGUAGE_CHOOSER_H__ */ diff --git a/gnome-initial-setup/pages/language/cc-util.c b/gnome-initial-setup/pages/language/cc-util.c new file mode 100644 index 0000000..e51a9d2 --- /dev/null +++ b/gnome-initial-setup/pages/language/cc-util.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * The Control Center 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. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "config.h" + +#include <string.h> + + +#include "cc-util.h" + +/* Combining diacritical mark? + * Basic range: [0x0300,0x036F] + * Supplement: [0x1DC0,0x1DFF] + * For Symbols: [0x20D0,0x20FF] + * Half marks: [0xFE20,0xFE2F] + */ +#define IS_CDM_UCS4(c) (((c) >= 0x0300 && (c) <= 0x036F) || \ + ((c) >= 0x1DC0 && (c) <= 0x1DFF) || \ + ((c) >= 0x20D0 && (c) <= 0x20FF) || \ + ((c) >= 0xFE20 && (c) <= 0xFE2F)) + +/* Copied from tracker/src/libtracker-fts/tracker-parser-glib.c under the GPL + * And then from gnome-shell/src/shell-util.c + * + * Originally written by Aleksander Morgado <aleksander@gnu.org> + */ +char * +cc_util_normalize_casefold_and_unaccent (const char *str) +{ + char *normalized, *tmp; + int i = 0, j = 0, ilen; + + if (str == NULL) + return NULL; + + normalized = g_utf8_normalize (str, -1, G_NORMALIZE_NFKD); + tmp = g_utf8_casefold (normalized, -1); + g_free (normalized); + + ilen = strlen (tmp); + + while (i < ilen) + { + gunichar unichar; + gchar *next_utf8; + gint utf8_len; + + /* Get next character of the word as UCS4 */ + unichar = g_utf8_get_char_validated (&tmp[i], -1); + + /* Invalid UTF-8 character or end of original string. */ + if (unichar == (gunichar) -1 || + unichar == (gunichar) -2) + { + break; + } + + /* Find next UTF-8 character */ + next_utf8 = g_utf8_next_char (&tmp[i]); + utf8_len = next_utf8 - &tmp[i]; + + if (IS_CDM_UCS4 ((guint32) unichar)) + { + /* If the given unichar is a combining diacritical mark, + * just update the original index, not the output one */ + i += utf8_len; + continue; + } + + /* If already found a previous combining + * diacritical mark, indexes are different so + * need to copy characters. As output and input + * buffers may overlap, need to use memmove + * instead of memcpy */ + if (i != j) + { + memmove (&tmp[j], &tmp[i], utf8_len); + } + + /* Update both indexes */ + i += utf8_len; + j += utf8_len; + } + + /* Force proper string end */ + tmp[j] = '\0'; + + return tmp; +} diff --git a/gnome-initial-setup/pages/language/cc-util.h b/gnome-initial-setup/pages/language/cc-util.h new file mode 100644 index 0000000..42b09ff --- /dev/null +++ b/gnome-initial-setup/pages/language/cc-util.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * The Control Center 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. + * + * The Control Center 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 the Control Center; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifndef _CC_UTIL_H +#define _CC_UTIL_H + +#include <glib.h> + +char *cc_util_normalize_casefold_and_unaccent (const char *str); + +#endif diff --git a/gnome-initial-setup/pages/language/gis-language-page.c b/gnome-initial-setup/pages/language/gis-language-page.c new file mode 100644 index 0000000..f62b2ad --- /dev/null +++ b/gnome-initial-setup/pages/language/gis-language-page.c @@ -0,0 +1,312 @@ +/* -*- 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> + * Michael Wood <michael.g.wood@intel.com> + * + * Based on gnome-control-center cc-region-panel.c + */ + +/* Language page {{{1 */ + +#define PAGE_ID "language" + +#define GNOME_SYSTEM_LOCALE_DIR "org.gnome.system.locale" +#define REGION_KEY "region" + +#include "config.h" +#include "language-resources.h" +#include "gis-welcome-widget.h" +#include "cc-language-chooser.h" +#include "gis-language-page.h" + +#include <act/act-user-manager.h> +#include <polkit/polkit.h> +#include <locale.h> +#include <gtk/gtk.h> + +struct _GisLanguagePagePrivate +{ + GtkWidget *logo; + GtkWidget *welcome_widget; + GtkWidget *language_chooser; + + GDBusProxy *localed; + GPermission *permission; + const gchar *new_locale_id; + + GCancellable *cancellable; +}; +typedef struct _GisLanguagePagePrivate GisLanguagePagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisLanguagePage, gis_language_page, GIS_TYPE_PAGE); + +static void +set_localed_locale (GisLanguagePage *self) +{ + GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (self); + GVariantBuilder *b; + gchar *s; + + b = g_variant_builder_new (G_VARIANT_TYPE ("as")); + s = g_strconcat ("LANG=", priv->new_locale_id, NULL); + g_variant_builder_add (b, "s", s); + g_free (s); + + g_dbus_proxy_call (priv->localed, + "SetLocale", + g_variant_new ("(asb)", b, TRUE), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); + g_variant_builder_unref (b); +} + +static void +change_locale_permission_acquired (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GisLanguagePage *page = GIS_LANGUAGE_PAGE (data); + GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page); + GError *error = NULL; + gboolean allowed; + + allowed = g_permission_acquire_finish (priv->permission, res, &error); + if (error) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to acquire permission: %s", error->message); + g_error_free (error); + return; + } + + if (allowed) + set_localed_locale (page); +} + +static void +user_loaded (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + gchar *new_locale_id = user_data; + + act_user_set_language (ACT_USER (object), new_locale_id); + + g_free (new_locale_id); +} + +static void +language_changed (CcLanguageChooser *chooser, + GParamSpec *pspec, + GisLanguagePage *page) +{ + GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page); + GisDriver *driver; + GSettings *region_settings; + ActUser *user; + + priv->new_locale_id = cc_language_chooser_get_language (chooser); + driver = GIS_PAGE (page)->driver; + + gis_driver_set_user_language (driver, priv->new_locale_id, TRUE); + gtk_widget_set_default_direction (gtk_get_locale_direction ()); + + if (gis_driver_get_mode (driver) == GIS_DRIVER_MODE_NEW_USER) { + if (g_permission_get_allowed (priv->permission)) { + set_localed_locale (page); + } + else if (g_permission_get_can_acquire (priv->permission)) { + g_permission_acquire_async (priv->permission, + NULL, + change_locale_permission_acquired, + page); + } + } + + /* Ensure we won't override the selected language for format strings */ + region_settings = g_settings_new (GNOME_SYSTEM_LOCALE_DIR); + g_settings_reset (region_settings, REGION_KEY); + g_object_unref (region_settings); + + user = act_user_manager_get_user (act_user_manager_get_default (), + g_get_user_name ()); + if (act_user_is_loaded (user)) + act_user_set_language (user, priv->new_locale_id); + else + g_signal_connect (user, + "notify::is-loaded", + G_CALLBACK (user_loaded), + g_strdup (priv->new_locale_id)); + + gis_welcome_widget_show_locale (GIS_WELCOME_WIDGET (priv->welcome_widget), + priv->new_locale_id); +} + +static void +localed_proxy_ready (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GisLanguagePage *self = data; + GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (self); + 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; + } + + priv->localed = proxy; +} + +static void +update_distro_logo (GisLanguagePage *page) +{ + GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page); + g_autofree char *id = g_get_os_info (G_OS_INFO_KEY_ID); + gsize i; + + static const struct { + const char *id; + const char *logo; + } id_to_logo[] = { + { "debian", "emblem-debian" }, + { "fedora", "fedora-logo-icon" }, + { "ubuntu", "ubuntu-logo-icon" }, + { "openSUSE Tumbleweed", "opensuse-logo-icon" }, + { "openSUSE Leap", "opensuse-logo-icon" }, + { "SLED", "suse-logo-icon" }, + { "SLES", "suse-logo-icon" }, + }; + + for (i = 0; i < G_N_ELEMENTS (id_to_logo); i++) + { + if (g_strcmp0 (id, id_to_logo[i].id) == 0) + { + g_object_set (priv->logo, "icon-name", id_to_logo[i].logo, NULL); + break; + } + } +} + +static void +language_confirmed (CcLanguageChooser *chooser, + GisLanguagePage *page) +{ + gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (page)->driver)); +} + +static void +gis_language_page_constructed (GObject *object) +{ + GisLanguagePage *page = GIS_LANGUAGE_PAGE (object); + GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page); + GDBusConnection *bus; + + g_type_ensure (CC_TYPE_LANGUAGE_CHOOSER); + + G_OBJECT_CLASS (gis_language_page_parent_class)->constructed (object); + + update_distro_logo (page); + + g_signal_connect (priv->language_chooser, "notify::language", + G_CALLBACK (language_changed), page); + 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) + { + priv->permission = polkit_permission_new_sync ("org.freedesktop.locale1.set-locale", NULL, NULL, NULL); + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); + g_dbus_proxy_new (bus, + G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, + NULL, + "org.freedesktop.locale1", + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + priv->cancellable, + (GAsyncReadyCallback) localed_proxy_ready, + object); + g_object_unref (bus); + } + + gis_page_set_complete (GIS_PAGE (page), TRUE); + gtk_widget_show (GTK_WIDGET (page)); +} + +static void +gis_language_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("Welcome")); +} + +static void +gis_language_page_dispose (GObject *object) +{ + GisLanguagePage *page = GIS_LANGUAGE_PAGE (object); + GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page); + + g_clear_object (&priv->permission); + g_clear_object (&priv->localed); + g_clear_object (&priv->cancellable); + + G_OBJECT_CLASS (gis_language_page_parent_class)->dispose (object); +} + +static void +gis_language_page_class_init (GisLanguagePageClass *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-language-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLanguagePage, welcome_widget); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLanguagePage, language_chooser); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLanguagePage, logo); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_language_page_locale_changed; + object_class->constructed = gis_language_page_constructed; + object_class->dispose = gis_language_page_dispose; +} + +static void +gis_language_page_init (GisLanguagePage *page) +{ + g_resources_register (language_get_resource ()); + g_type_ensure (GIS_TYPE_WELCOME_WIDGET); + g_type_ensure (CC_TYPE_LANGUAGE_CHOOSER); + + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_language_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_LANGUAGE_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/language/gis-language-page.h b/gnome-initial-setup/pages/language/gis-language-page.h new file mode 100644 index 0000000..7636021 --- /dev/null +++ b/gnome-initial-setup/pages/language/gis-language-page.h @@ -0,0 +1,57 @@ +/* -*- 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> + */ + +#ifndef __GIS_LANGUAGE_PAGE_H__ +#define __GIS_LANGUAGE_PAGE_H__ + +#include <glib-object.h> + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_LANGUAGE_PAGE (gis_language_page_get_type ()) +#define GIS_LANGUAGE_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_LANGUAGE_PAGE, GisLanguagePage)) +#define GIS_LANGUAGE_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_LANGUAGE_PAGE, GisLanguagePageClass)) +#define GIS_IS_LANGUAGE_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_LANGUAGE_PAGE)) +#define GIS_IS_LANGUAGE_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_LANGUAGE_PAGE)) +#define GIS_LANGUAGE_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_LANGUAGE_PAGE, GisLanguagePageClass)) + +typedef struct _GisLanguagePage GisLanguagePage; +typedef struct _GisLanguagePageClass GisLanguagePageClass; + +struct _GisLanguagePage +{ + GisPage parent; +}; + +struct _GisLanguagePageClass +{ + GisPageClass parent_class; +}; + +GType gis_language_page_get_type (void); + +GisPage *gis_prepare_language_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_LANGUAGE_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/language/gis-language-page.ui b/gnome-initial-setup/pages/language/gis-language-page.ui new file mode 100644 index 0000000..8f2a08c --- /dev/null +++ b/gnome-initial-setup/pages/language/gis-language-page.ui @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="3.0"/> + <template class="GisLanguagePage" parent="GisPage"> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="halign">center</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="expand" bind-source="language_chooser" bind-property="visible" bind-flags="invert-boolean|sync-create"/> + <child> + <object class="GtkImage" id="logo"> + <property name="visible" bind-source="GisLanguagePage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + <property name="can_focus">False</property> + <property name="margin_top">24</property> + <property name="pixel_size">96</property> + <property name="icon_name">start-here-symbolic</property> + </object> + </child> + <child> + <object class="GisWelcomeWidget" id="welcome_widget"> + <property name="visible">True</property> + <property name="margin_top">18</property> + <property name="margin_bottom">40</property> + </object> + </child> + </object> + </child> + <child> + <object class="CcLanguageChooser" id="language_chooser"> + <property name="margin_bottom">18</property> + <property name="width_request">400</property> + <property name="visible">True</property> + <property name="valign">start</property> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/language/gis-welcome-widget.c b/gnome-initial-setup/pages/language/gis-welcome-widget.c new file mode 100644 index 0000000..a1dafc8 --- /dev/null +++ b/gnome-initial-setup/pages/language/gis-welcome-widget.c @@ -0,0 +1,245 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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 "gis-welcome-widget.h" + +#include <errno.h> +#include <locale.h> +#include <glib/gi18n.h> + +#include "cc-common-language.h" + +struct _GisWelcomeWidgetPrivate +{ + GtkWidget *stack; + GHashTable *translation_widgets; /* (element-type owned utf8 unowned GtkWidget) (owned) */ + + guint timeout_id; +}; +typedef struct _GisWelcomeWidgetPrivate GisWelcomeWidgetPrivate; + +#define TIMEOUT 5 + +G_DEFINE_TYPE_WITH_PRIVATE (GisWelcomeWidget, gis_welcome_widget, GTK_TYPE_BIN); + +static gboolean +advance_stack (gpointer user_data) +{ + GisWelcomeWidget *widget = user_data; + GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget); + GList *children, *l; + + children = gtk_container_get_children (GTK_CONTAINER (priv->stack)); + if (children == NULL) + goto out; + + for (l = children; l != NULL; l = l->next) + { + if (l->data == gtk_stack_get_visible_child (GTK_STACK (priv->stack))) + break; + } + + /* wrap around */ + if (l->next) + l = l->next; + else + l = children; + + gtk_stack_set_visible_child (GTK_STACK (priv->stack), l->data); + + g_list_free (children); + + out: + return G_SOURCE_CONTINUE; +} + +static void +gis_welcome_widget_start (GisWelcomeWidget *widget) +{ + GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget); + + if (priv->timeout_id > 0) + return; + + priv->timeout_id = g_timeout_add_seconds (5, advance_stack, widget); +} + +static void +gis_welcome_widget_stop (GisWelcomeWidget *widget) +{ + GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget); + + if (priv->timeout_id == 0) + return; + + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; +} + +static void +gis_welcome_widget_map (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (gis_welcome_widget_parent_class)->map (widget); + gis_welcome_widget_start (GIS_WELCOME_WIDGET (widget)); +} + +static void +gis_welcome_widget_unmap (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (gis_welcome_widget_parent_class)->unmap (widget); + gis_welcome_widget_stop (GIS_WELCOME_WIDGET (widget)); +} + +static const char * +welcome (const char *locale_id) +{ + locale_t locale; + locale_t old_locale; + const char *welcome; + + locale = newlocale (LC_MESSAGES_MASK, locale_id, (locale_t) 0); + if (locale == (locale_t) 0) + { + if (errno == ENOENT) + g_debug ("Failed to create locale %s: %s", locale_id, g_strerror (errno)); + else + g_warning ("Failed to create locale %s: %s", locale_id, g_strerror (errno)); + + return "Welcome!"; + } + + old_locale = uselocale (locale); + + /* Translators: This is meant to be a warm, engaging welcome message, + * like greeting somebody at the door. If the exclamation mark is not + * suitable for this in your language you may replace it. + */ + welcome = _("Welcome!"); + + uselocale (old_locale); + freelocale (locale); + + return welcome; +} + +static GtkWidget * +big_label (const char *text) +{ + GtkWidget *label = gtk_label_new (text); + + gtk_style_context_add_class (gtk_widget_get_style_context (label), "title-1"); + + return label; +} + +static void +fill_stack (GisWelcomeWidget *widget) +{ + GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget); + g_autoptr(GHashTable) initial = cc_common_language_get_initial_languages (); + GHashTableIter iter; + gpointer key, value; + g_autoptr(GHashTable) added_translations = NULL; + + added_translations = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_iter_init (&iter, initial); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const char *locale_id = key; + const char *text; + GtkWidget *label; + + if (!cc_common_language_has_font (locale_id)) + continue; + + text = welcome (locale_id); + label = g_hash_table_lookup (added_translations, text); + if (label == NULL) { + label = big_label (text); + gtk_container_add (GTK_CONTAINER (priv->stack), label); + gtk_widget_show (label); + g_hash_table_insert (added_translations, (gpointer) text, label); + } + + g_hash_table_insert (priv->translation_widgets, g_strdup (locale_id), label); + } +} + +static void +gis_welcome_widget_constructed (GObject *object) +{ + fill_stack (GIS_WELCOME_WIDGET (object)); +} + +static void +gis_welcome_widget_dispose (GObject *object) +{ + GisWelcomeWidget *widget = GIS_WELCOME_WIDGET (object); + GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget); + + g_clear_pointer (&priv->translation_widgets, g_hash_table_unref); + + G_OBJECT_CLASS (gis_welcome_widget_parent_class)->dispose (object); +} + +static void +gis_welcome_widget_class_init (GisWelcomeWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/initial-setup/gis-welcome-widget.ui"); + + gtk_widget_class_bind_template_child_private (widget_class, GisWelcomeWidget, stack); + + object_class->constructed = gis_welcome_widget_constructed; + object_class->dispose = gis_welcome_widget_dispose; + widget_class->map = gis_welcome_widget_map; + widget_class->unmap = gis_welcome_widget_unmap; +} + +static void +gis_welcome_widget_init (GisWelcomeWidget *widget) +{ + GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget); + + priv->translation_widgets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + gtk_widget_init_template (GTK_WIDGET (widget)); +} + +void +gis_welcome_widget_show_locale (GisWelcomeWidget *widget, + const char *locale_id) +{ + GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget); + GtkWidget *label; + + /* Restart the widget to reset the timer. */ + gis_welcome_widget_stop (widget); + gis_welcome_widget_start (widget); + + label = g_hash_table_lookup (priv->translation_widgets, locale_id); + if (label) + gtk_stack_set_visible_child (GTK_STACK (priv->stack), label); +} diff --git a/gnome-initial-setup/pages/language/gis-welcome-widget.h b/gnome-initial-setup/pages/language/gis-welcome-widget.h new file mode 100644 index 0000000..33afe8b --- /dev/null +++ b/gnome-initial-setup/pages/language/gis-welcome-widget.h @@ -0,0 +1,56 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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> + */ + +#ifndef __GIS_WELCOME_WIDGET_H__ +#define __GIS_WELCOME_WIDGET_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GIS_TYPE_WELCOME_WIDGET (gis_welcome_widget_get_type ()) +#define GIS_WELCOME_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_WELCOME_WIDGET, GisWelcomeWidget)) +#define GIS_WELCOME_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_WELCOME_WIDGET, GisWelcomeWidgetClass)) +#define GIS_IS_WELCOME_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_WELCOME_WIDGET)) +#define GIS_IS_WELCOME_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_WELCOME_WIDGET)) +#define GIS_WELCOME_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_WELCOME_WIDGET, GisWelcomeWidgetClass)) + +typedef struct _GisWelcomeWidget GisWelcomeWidget; +typedef struct _GisWelcomeWidgetClass GisWelcomeWidgetClass; + +struct _GisWelcomeWidget +{ + GtkBin parent; +}; + +struct _GisWelcomeWidgetClass +{ + GtkBinClass parent_class; +}; + +GType gis_welcome_widget_get_type (void); + +void gis_welcome_widget_show_locale (GisWelcomeWidget *widget, + const char *locale_id); + +G_END_DECLS + +#endif /* __GIS_WELCOME_WIDGET_H__ */ diff --git a/gnome-initial-setup/pages/language/gis-welcome-widget.ui b/gnome-initial-setup/pages/language/gis-welcome-widget.ui new file mode 100644 index 0000000..251ad46 --- /dev/null +++ b/gnome-initial-setup/pages/language/gis-welcome-widget.ui @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="3.0"/> + <template class="GisWelcomeWidget" parent="GtkBin"> + <property name="margin-top">10</property> + <property name="margin-bottom">10</property> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="transition-type">slide-left</property> + <property name="transition-duration">1000</property> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/language/language-chooser.ui b/gnome-initial-setup/pages/language/language-chooser.ui new file mode 100644 index 0000000..fc61d59 --- /dev/null +++ b/gnome-initial-setup/pages/language/language-chooser.ui @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="3.0"/> + <template class="CcLanguageChooser" parent="GtkBox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">10</property> + <child> + <object class="GtkScrolledWindow" id="scrolled_window"> + <property name="visible">True</property> + <property name="hscrollbar-policy">never</property> + <property name="vscrollbar-policy">never</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkListBox" id="language_list"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="vexpand">True</property> + <property name="halign">fill</property> + <property name="valign">fill</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkSearchEntry" id="filter_entry"> + <property name="visible">False</property> + <property name="hexpand">True</property> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/language/language.gresource.xml b/gnome-initial-setup/pages/language/language.gresource.xml new file mode 100644 index 0000000..de0688d --- /dev/null +++ b/gnome-initial-setup/pages/language/language.gresource.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks" alias="gis-language-page.ui">gis-language-page.ui</file> + <file preprocess="xml-stripblanks" alias="gis-welcome-widget.ui">gis-welcome-widget.ui</file> + </gresource> + <gresource prefix="/org/gnome/control-center"> + <file preprocess="xml-stripblanks" alias="language-chooser.ui">language-chooser.ui</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/language/meson.build b/gnome-initial-setup/pages/language/meson.build new file mode 100644 index 0000000..ef6ba3f --- /dev/null +++ b/gnome-initial-setup/pages/language/meson.build @@ -0,0 +1,16 @@ +sources += gnome.compile_resources( + 'language-resources', + files('language.gresource.xml'), + c_name: 'language' +) + +sources += files( + 'cc-language-chooser.c', + 'cc-language-chooser.h', + 'cc-util.c', + 'cc-util.h', + 'gis-welcome-widget.c', + 'gis-welcome-widget.h', + 'gis-language-page.c', + 'gis-language-page.h', +) diff --git a/gnome-initial-setup/pages/meson.build b/gnome-initial-setup/pages/meson.build new file mode 100644 index 0000000..45f77d7 --- /dev/null +++ b/gnome-initial-setup/pages/meson.build @@ -0,0 +1,20 @@ +pages = [ + 'account', + 'language', + 'keyboard', + 'network', + 'timezone', + 'privacy', + 'goa', + 'password', + 'summary', + 'welcome', +] + +if libmalcontent_dep.found() and libmalcontent_ui_dep.found() + pages += 'parental-controls' +endif + +foreach page: pages + subdir (page) +endforeach diff --git a/gnome-initial-setup/pages/network/gis-network-page.c b/gnome-initial-setup/pages/network/gis-network-page.c new file mode 100644 index 0000000..331cd37 --- /dev/null +++ b/gnome-initial-setup/pages/network/gis-network-page.c @@ -0,0 +1,853 @@ +/* -*- 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> + */ + +/* Network page {{{1 */ + +#define PAGE_ID "network" + +#include "config.h" +#include "network-resources.h" +#include "gis-network-page.h" + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "network-dialogs.h" + +#include "gis-page-header.h" + +typedef enum { + NM_AP_SEC_UNKNOWN, + NM_AP_SEC_NONE, + NM_AP_SEC_WEP, + NM_AP_SEC_WPA, + NM_AP_SEC_WPA2 +} NMAccessPointSecurity; + +struct _GisNetworkPagePrivate { + GtkWidget *network_list; + GtkWidget *scrolled_window; + GtkWidget *no_network_label; + GtkWidget *no_network_spinner; + GtkWidget *turn_on_label; + GtkWidget *turn_on_switch; + + NMClient *nm_client; + NMDevice *nm_device; + gboolean refreshing; + GtkSizeGroup *icons; + + guint refresh_timeout_id; + + /* TRUE if the page has ever been shown to the user. */ + gboolean ever_shown; +}; +typedef struct _GisNetworkPagePrivate GisNetworkPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisNetworkPage, gis_network_page, GIS_TYPE_PAGE); + +static GPtrArray * +get_strongest_unique_aps (const GPtrArray *aps) +{ + GBytes *ssid; + GBytes *ssid_tmp; + GPtrArray *unique = NULL; + gboolean add_ap; + guint i; + guint j; + NMAccessPoint *ap; + NMAccessPoint *ap_tmp; + + /* we will have multiple entries for typical hotspots, + * just keep the one with the strongest signal + */ + unique = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + if (aps == NULL) + goto out; + + for (i = 0; i < aps->len; i++) { + ap = NM_ACCESS_POINT (g_ptr_array_index (aps, i)); + ssid = nm_access_point_get_ssid (ap); + add_ap = TRUE; + + if (!ssid) + continue; + + /* get already added list */ + for (j = 0; j < unique->len; j++) { + ap_tmp = NM_ACCESS_POINT (g_ptr_array_index (unique, j)); + ssid_tmp = nm_access_point_get_ssid (ap_tmp); + + /* is this the same type and data? */ + if (ssid_tmp && + nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), + g_bytes_get_data (ssid_tmp, NULL), g_bytes_get_size (ssid_tmp), TRUE)) { + /* the new access point is stronger */ + if (nm_access_point_get_strength (ap) > + nm_access_point_get_strength (ap_tmp)) { + g_ptr_array_remove (unique, ap_tmp); + add_ap = TRUE; + } else { + add_ap = FALSE; + } + break; + } + } + if (add_ap) { + g_ptr_array_add (unique, g_object_ref (ap)); + } + } + + out: + return unique; +} + +static guint +get_access_point_security (NMAccessPoint *ap) +{ + NM80211ApFlags flags; + NM80211ApSecurityFlags wpa_flags; + NM80211ApSecurityFlags rsn_flags; + guint type; + + flags = nm_access_point_get_flags (ap); + wpa_flags = nm_access_point_get_wpa_flags (ap); + rsn_flags = nm_access_point_get_rsn_flags (ap); + + if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) && + wpa_flags == NM_802_11_AP_SEC_NONE && + rsn_flags == NM_802_11_AP_SEC_NONE) + type = NM_AP_SEC_NONE; + else if ((flags & NM_802_11_AP_FLAGS_PRIVACY) && + wpa_flags == NM_802_11_AP_SEC_NONE && + rsn_flags == NM_802_11_AP_SEC_NONE) + type = NM_AP_SEC_WEP; + else if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) && + wpa_flags != NM_802_11_AP_SEC_NONE && + rsn_flags != NM_802_11_AP_SEC_NONE) + type = NM_AP_SEC_WPA; + else + type = NM_AP_SEC_WPA2; + + return type; +} + +static gint +ap_sort (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer data) +{ + GtkWidget *wa, *wb; + guint sa, sb; + + wa = gtk_bin_get_child (GTK_BIN (a)); + wb = gtk_bin_get_child (GTK_BIN (b)); + + sa = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (wa), "strength")); + sb = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (wb), "strength")); + if (sa > sb) return -1; + if (sb > sa) return 1; + + return 0; +} + +static void +update_header_func (GtkListBoxRow *child, + GtkListBoxRow *before, + gpointer user_data) +{ + GtkWidget *header; + + if (before == NULL) + return; + + header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header (child, header); + gtk_widget_show (header); +} + +static void +add_access_point (GisNetworkPage *page, NMAccessPoint *ap, NMAccessPoint *active) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + GBytes *ssid; + GBytes *ssid_active = NULL; + gchar *ssid_text; + const gchar *object_path; + gboolean activated, activating; + guint security; + guint strength; + const gchar *icon_name; + GtkWidget *row; + GtkWidget *widget; + GtkWidget *grid; + GtkWidget *state_widget = NULL; + + ssid = nm_access_point_get_ssid (ap); + object_path = nm_object_get_path (NM_OBJECT (ap)); + + if (ssid == NULL) + return; + ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); + + if (active) + ssid_active = nm_access_point_get_ssid (active); + if (ssid_active && nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), + g_bytes_get_data (ssid_active, NULL), g_bytes_get_size (ssid_active), TRUE)) { + switch (nm_device_get_state (priv->nm_device)) + { + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_SECONDARIES: + activated = FALSE; + activating = TRUE; + break; + case NM_DEVICE_STATE_ACTIVATED: + activated = TRUE; + activating = FALSE; + break; + default: + activated = FALSE; + activating = FALSE; + break; + } + } else { + activated = FALSE; + activating = FALSE; + } + + security = get_access_point_security (ap); + strength = nm_access_point_get_strength (ap); + + row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_set_margin_start (row, 12); + gtk_widget_set_margin_end (row, 12); + widget = gtk_label_new (ssid_text); + gtk_widget_set_margin_top (widget, 12); + gtk_widget_set_margin_bottom (widget, 12); + gtk_box_pack_start (GTK_BOX (row), widget, FALSE, FALSE, 0); + + if (activated) { + state_widget = gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU); + } else if (activating) { + state_widget = gtk_spinner_new (); + gtk_widget_show (state_widget); + gtk_spinner_start (GTK_SPINNER (state_widget)); + } + + if (state_widget) { + gtk_widget_set_halign (state_widget, GTK_ALIGN_CENTER); + gtk_widget_set_valign (state_widget, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (row), state_widget, FALSE, FALSE, 0); + } + + grid = gtk_grid_new (); + gtk_grid_set_column_spacing (GTK_GRID (grid), 6); + gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE); + gtk_widget_set_valign (grid, GTK_ALIGN_CENTER); + gtk_size_group_add_widget (priv->icons, grid); + gtk_box_pack_end (GTK_BOX (row), grid, FALSE, FALSE, 0); + + if (security != NM_AP_SEC_UNKNOWN && + security != NM_AP_SEC_NONE) { + widget = gtk_image_new_from_icon_name ("network-wireless-encrypted-symbolic", GTK_ICON_SIZE_MENU); + gtk_grid_attach (GTK_GRID (grid), widget, 0, 0, 1, 1); + } + + if (strength < 20) + icon_name = "network-wireless-signal-none-symbolic"; + else if (strength < 40) + icon_name = "network-wireless-signal-weak-symbolic"; + else if (strength < 50) + icon_name = "network-wireless-signal-ok-symbolic"; + else if (strength < 80) + icon_name = "network-wireless-signal-good-symbolic"; + else + icon_name = "network-wireless-signal-excellent-symbolic"; + widget = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU); + gtk_widget_set_halign (widget, GTK_ALIGN_END); + gtk_grid_attach (GTK_GRID (grid), widget, 1, 0, 1, 1); + + gtk_widget_show_all (row); + + /* if this connection is the active one or is being activated, then make sure + * it's sorted before all others */ + if (activating || activated) + strength = G_MAXUINT; + + g_object_set_data (G_OBJECT (row), "object-path", (gpointer) object_path); + g_object_set_data (G_OBJECT (row), "ssid", (gpointer) ssid); + g_object_set_data (G_OBJECT (row), "strength", GUINT_TO_POINTER (strength)); + + widget = gtk_list_box_row_new (); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_widget_show (widget); + gtk_container_add (GTK_CONTAINER (priv->network_list), widget); +} + +static void +add_access_point_other (GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + GtkWidget *row; + GtkWidget *widget; + + row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_set_margin_start (row, 12); + gtk_widget_set_margin_end (row, 12); + widget = gtk_label_new (C_("Wireless access point", "Other…")); + gtk_widget_set_margin_top (widget, 12); + gtk_widget_set_margin_bottom (widget, 12); + gtk_box_pack_start (GTK_BOX (row), widget, FALSE, FALSE, 0); + gtk_widget_show_all (row); + + g_object_set_data (G_OBJECT (row), "object-path", "ap-other..."); + g_object_set_data (G_OBJECT (row), "strength", GUINT_TO_POINTER (0)); + + gtk_container_add (GTK_CONTAINER (priv->network_list), row); +} + +static gboolean refresh_wireless_list (GisNetworkPage *page); + +static void +cancel_periodic_refresh (GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + + if (priv->refresh_timeout_id == 0) + return; + + g_debug ("Stopping periodic/scheduled Wi-Fi list refresh"); + + g_clear_handle_id (&priv->refresh_timeout_id, g_source_remove); +} + +static gboolean +refresh_again (gpointer user_data) +{ + GisNetworkPage *page = GIS_NETWORK_PAGE (user_data); + refresh_wireless_list (page); + return G_SOURCE_REMOVE; +} + +static void +start_periodic_refresh (GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + static const guint periodic_wifi_refresh_timeout_sec = 10; + + cancel_periodic_refresh (page); + + g_debug ("Starting periodic Wi-Fi list refresh (every %u secs)", + periodic_wifi_refresh_timeout_sec); + priv->refresh_timeout_id = g_timeout_add_seconds (periodic_wifi_refresh_timeout_sec, + refresh_again, page); +} + +static gboolean +refresh_wireless_list (GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + NMAccessPoint *active_ap = NULL; + NMAccessPoint *ap; + const GPtrArray *aps; + GPtrArray *unique_aps; + guint i; + GList *children, *l; + gboolean enabled; + + g_debug ("Refreshing Wi-Fi networks list"); + + priv->refreshing = TRUE; + + g_assert (NM_IS_DEVICE_WIFI (priv->nm_device)); + + cancel_periodic_refresh (page); + + active_ap = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (priv->nm_device)); + + children = gtk_container_get_children (GTK_CONTAINER (priv->network_list)); + for (l = children; l; l = l->next) + gtk_container_remove (GTK_CONTAINER (priv->network_list), l->data); + g_list_free (children); + + aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (priv->nm_device)); + enabled = nm_client_wireless_get_enabled (priv->nm_client); + + if (aps == NULL || aps->len == 0) { + gboolean hw_enabled; + + hw_enabled = nm_client_wireless_hardware_get_enabled (priv->nm_client); + + if (!enabled || !hw_enabled) { + gtk_label_set_text (GTK_LABEL (priv->no_network_label), _("Wireless networking is disabled")); + gtk_widget_show (priv->no_network_label); + gtk_widget_hide (priv->no_network_spinner); + + gtk_widget_set_visible (priv->turn_on_label, hw_enabled); + gtk_widget_set_visible (priv->turn_on_switch, hw_enabled); + } else { + gtk_label_set_text (GTK_LABEL (priv->no_network_label), _("Checking for available wireless networks")); + gtk_widget_show (priv->no_network_spinner); + gtk_widget_show (priv->no_network_label); + gtk_widget_hide (priv->turn_on_label); + gtk_widget_hide (priv->turn_on_switch); + } + + gtk_widget_hide (priv->scrolled_window); + goto out; + + } else { + gtk_widget_hide (priv->no_network_spinner); + gtk_widget_hide (priv->no_network_label); + gtk_widget_hide (priv->turn_on_label); + gtk_widget_hide (priv->turn_on_switch); + gtk_widget_show (priv->scrolled_window); + } + + unique_aps = get_strongest_unique_aps (aps); + for (i = 0; i < unique_aps->len; i++) { + ap = NM_ACCESS_POINT (g_ptr_array_index (unique_aps, i)); + add_access_point (page, ap, active_ap); + } + g_ptr_array_unref (unique_aps); + add_access_point_other (page); + + out: + + if (enabled) + start_periodic_refresh (page); + + priv->refreshing = FALSE; + + return G_SOURCE_REMOVE; +} + +/* Avoid repeated calls to refreshing the wireless list by making it refresh at + * most once per second */ +static void +schedule_refresh_wireless_list (GisNetworkPage *page) +{ + static const guint refresh_wireless_list_timeout_sec = 1; + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + + cancel_periodic_refresh (page); + + g_debug ("Delaying Wi-Fi list refresh (for %u sec)", + refresh_wireless_list_timeout_sec); + + priv->refresh_timeout_id = g_timeout_add_seconds (refresh_wireless_list_timeout_sec, + (GSourceFunc) refresh_wireless_list, + page); +} + +static void +connection_activate_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NMClient *client = NM_CLIENT (object); + NMActiveConnection *connection = NULL; + g_autoptr(GError) error = NULL; + + connection = nm_client_activate_connection_finish (client, result, &error); + if (connection != NULL) { + g_clear_object (&connection); + } else { + /* failed to activate */ + g_warning ("Failed to activate a connection: %s", error->message); + } +} + +static void +connection_add_activate_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NMClient *client = NM_CLIENT (object); + NMActiveConnection *connection = NULL; + g_autoptr(GError) error = NULL; + + connection = nm_client_add_and_activate_connection_finish (client, result, &error); + if (connection != NULL) { + g_clear_object (&connection); + } else { + /* failed to activate */ + g_warning ("Failed to add and activate a connection: %s", error->message); + } +} + +static void +connect_to_hidden_network (GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + cc_network_panel_connect_to_hidden_network (gtk_widget_get_toplevel (GTK_WIDGET (page)), + priv->nm_client); +} + +static void +row_activated (GtkListBox *box, + GtkListBoxRow *row, + GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + gchar *object_path; + const GPtrArray *list; + GPtrArray *filtered; + NMConnection *connection; + NMConnection *connection_to_activate; + NMSettingWireless *setting; + GBytes *ssid; + GBytes *ssid_target; + GtkWidget *child; + int i; + + if (priv->refreshing) + return; + + child = gtk_bin_get_child (GTK_BIN (row)); + object_path = g_object_get_data (G_OBJECT (child), "object-path"); + ssid_target = g_object_get_data (G_OBJECT (child), "ssid"); + + if (g_strcmp0 (object_path, "ap-other...") == 0) { + connect_to_hidden_network (page); + goto out; + } + + list = nm_client_get_connections (priv->nm_client); + filtered = nm_device_filter_connections (priv->nm_device, list); + + connection_to_activate = NULL; + + for (i = 0; i < filtered->len; i++) { + connection = NM_CONNECTION (filtered->pdata[i]); + setting = nm_connection_get_setting_wireless (connection); + if (!NM_IS_SETTING_WIRELESS (setting)) + continue; + + ssid = nm_setting_wireless_get_ssid (setting); + if (ssid == NULL) + continue; + + if (nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), + g_bytes_get_data (ssid_target, NULL), g_bytes_get_size (ssid_target), TRUE)) { + connection_to_activate = connection; + break; + } + } + g_ptr_array_unref (filtered); + + if (connection_to_activate != NULL) { + nm_client_activate_connection_async (priv->nm_client, + connection_to_activate, + priv->nm_device, NULL, + NULL, + connection_activate_cb, page); + return; + } + + nm_client_add_and_activate_connection_async (priv->nm_client, + NULL, + priv->nm_device, object_path, + NULL, + connection_add_activate_cb, page); + + out: + schedule_refresh_wireless_list (page); +} + +static void +connection_state_changed (NMActiveConnection *c, GParamSpec *pspec, GisNetworkPage *page) +{ + schedule_refresh_wireless_list (page); +} + +static void +active_connections_changed (NMClient *client, GParamSpec *pspec, GisNetworkPage *page) +{ + const GPtrArray *connections; + guint i; + + connections = nm_client_get_active_connections (client); + for (i = 0; connections && (i < connections->len); i++) { + NMActiveConnection *connection; + + connection = g_ptr_array_index (connections, i); + if (!g_object_get_data (G_OBJECT (connection), "has-state-changed-handler")) { + g_signal_connect (connection, "notify::state", + G_CALLBACK (connection_state_changed), page); + g_object_set_data (G_OBJECT (connection), "has-state-changed-handler", GINT_TO_POINTER (1)); + } + } +} + +static void +sync_complete (GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + gboolean has_device; + gboolean activated; + gboolean visible; + + has_device = priv->nm_device != NULL; + activated = priv->nm_device != NULL + && nm_device_get_state (priv->nm_device) == NM_DEVICE_STATE_ACTIVATED; + + if (priv->ever_shown) { + visible = TRUE; + } else if (!has_device) { + g_debug ("No network device found, hiding network page"); + visible = FALSE; + } else if (activated) { + g_debug ("Activated network device found, hiding network page"); + visible = FALSE; + } else { + visible = TRUE; + } + + gis_page_set_complete (GIS_PAGE (page), activated); + gtk_widget_set_visible (GTK_WIDGET (page), visible); + + if (has_device) + schedule_refresh_wireless_list (page); +} + +static void +device_state_changed (GObject *object, GParamSpec *param, GisNetworkPage *page) +{ + sync_complete (page); +} + +static void +find_best_device (GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + const GPtrArray *devices; + guint i; + + /* FIXME: deal with multiple devices and devices being removed */ + if (priv->nm_device != NULL) { + g_debug ("Already showing network device %s", + nm_device_get_description (priv->nm_device)); + return; + } + + devices = nm_client_get_devices (priv->nm_client); + g_return_if_fail (devices != NULL); + for (i = 0; i < devices->len; i++) { + NMDevice *device = g_ptr_array_index (devices, i); + + if (!nm_device_get_managed (device)) + continue; + + if (nm_device_get_device_type (device) == NM_DEVICE_TYPE_WIFI) { + /* FIXME deal with multiple, dynamic devices */ + priv->nm_device = g_object_ref (device); + g_debug ("Showing network device %s", + nm_device_get_description (priv->nm_device)); + + g_signal_connect (priv->nm_device, "notify::state", + G_CALLBACK (device_state_changed), page); + g_signal_connect (priv->nm_client, "notify::active-connections", + G_CALLBACK (active_connections_changed), page); + + break; + } + } + + sync_complete (page); +} +static void +device_notify_managed (NMDevice *device, + GParamSpec *param, + void *user_data) +{ + GisNetworkPage *page = GIS_NETWORK_PAGE (user_data); + + find_best_device (page); +} + +static void +client_device_added (NMClient *client, + NMDevice *device, + void *user_data) +{ + GisNetworkPage *page = GIS_NETWORK_PAGE (user_data); + + g_signal_connect_object (device, + "notify::managed", + G_CALLBACK (device_notify_managed), + G_OBJECT (page), + 0); + + find_best_device (page); +} + +static void +client_device_removed (NMClient *client, + NMDevice *device, + void *user_data) +{ + GisNetworkPage *page = GIS_NETWORK_PAGE (user_data); + + /* TODO: reset page if priv->nm_device == device */ + g_signal_handlers_disconnect_by_func (device, device_notify_managed, page); + + find_best_device (page); +} + +static void +monitor_network_devices (GisNetworkPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + const GPtrArray *devices; + guint i; + + g_assert (priv->nm_client != NULL); + devices = nm_client_get_devices (priv->nm_client); + g_return_if_fail (devices != NULL); + for (i = 0; devices != NULL && i < devices->len; i++) { + NMDevice *device = g_ptr_array_index (devices, i); + + g_signal_connect_object (device, + "notify::managed", + G_CALLBACK (device_notify_managed), + G_OBJECT (page), + 0); + } + + g_signal_connect_object (priv->nm_client, + "device-added", + G_CALLBACK (client_device_added), + G_OBJECT (page), + 0); + g_signal_connect_object (priv->nm_client, + "device-removed", + G_CALLBACK (client_device_removed), + G_OBJECT (page), + 0); + + find_best_device (page); +} + +static void +gis_network_page_constructed (GObject *object) +{ + GisNetworkPage *page = GIS_NETWORK_PAGE (object); + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + g_autoptr(GError) error = NULL; + + G_OBJECT_CLASS (gis_network_page_parent_class)->constructed (object); + + gis_page_set_skippable (GIS_PAGE (page), TRUE); + + priv->ever_shown = g_getenv ("GIS_ALWAYS_SHOW_NETWORK_PAGE") != NULL; + priv->icons = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->network_list), GTK_SELECTION_NONE); + gtk_list_box_set_header_func (GTK_LIST_BOX (priv->network_list), update_header_func, NULL, NULL); + gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->network_list), ap_sort, NULL, NULL); + g_signal_connect (priv->network_list, "row-activated", + G_CALLBACK (row_activated), page); + + priv->nm_client = nm_client_new (NULL, &error); + if (!priv->nm_client) { + g_warning ("Can't create NetworkManager client, hiding network page: %s", + error->message); + sync_complete (page); + return; + } + + g_object_bind_property (priv->nm_client, "wireless-enabled", + priv->turn_on_switch, "active", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + monitor_network_devices (page); +} + +static void +gis_network_page_dispose (GObject *object) +{ + GisNetworkPage *page = GIS_NETWORK_PAGE (object); + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); + + g_clear_object (&priv->nm_client); + g_clear_object (&priv->nm_device); + g_clear_object (&priv->icons); + + cancel_periodic_refresh (page); + + G_OBJECT_CLASS (gis_network_page_parent_class)->dispose (object); +} + +static void +gis_network_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("Network")); +} + +static void +gis_network_page_shown (GisPage *page) +{ + GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (GIS_NETWORK_PAGE (page)); + + priv->ever_shown = TRUE; +} + +static void +gis_network_page_class_init (GisNetworkPageClass *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-network-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, network_list); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, scrolled_window); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, no_network_label); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, no_network_spinner); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, turn_on_label); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, turn_on_switch); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_network_page_locale_changed; + page_class->shown = gis_network_page_shown; + object_class->constructed = gis_network_page_constructed; + object_class->dispose = gis_network_page_dispose; +} + +static void +gis_network_page_init (GisNetworkPage *page) +{ + g_resources_register (network_get_resource ()); + g_type_ensure (GIS_TYPE_PAGE_HEADER); + + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_network_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_NETWORK_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/network/gis-network-page.h b/gnome-initial-setup/pages/network/gis-network-page.h new file mode 100644 index 0000000..172b7d1 --- /dev/null +++ b/gnome-initial-setup/pages/network/gis-network-page.h @@ -0,0 +1,55 @@ +/* -*- 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> + */ + +#ifndef __GIS_NETWORK_PAGE_H__ +#define __GIS_NETWORK_PAGE_H__ + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_NETWORK_PAGE (gis_network_page_get_type ()) +#define GIS_NETWORK_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_NETWORK_PAGE, GisNetworkPage)) +#define GIS_NETWORK_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_NETWORK_PAGE, GisNetworkPageClass)) +#define GIS_IS_NETWORK_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_NETWORK_PAGE)) +#define GIS_IS_NETWORK_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_NETWORK_PAGE)) +#define GIS_NETWORK_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_NETWORK_PAGE, GisNetworkPageClass)) + +typedef struct _GisNetworkPage GisNetworkPage; +typedef struct _GisNetworkPageClass GisNetworkPageClass; + +struct _GisNetworkPage +{ + GisPage parent; +}; + +struct _GisNetworkPageClass +{ + GisPageClass parent_class; +}; + +GType gis_network_page_get_type (void); + +GisPage *gis_prepare_network_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_NETWORK_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/network/gis-network-page.ui b/gnome-initial-setup/pages/network/gis-network-page.ui new file mode 100644 index 0000000..cdad9a6 --- /dev/null +++ b/gnome-initial-setup/pages/network/gis-network-page.ui @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.16.0 on Wed Oct 16 17:37:48 2013 --> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisNetworkPage" parent="GisPage"> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <property name="margin_bottom">32</property> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin_top">24</property> + <property name="title" translatable="yes">Wi-Fi</property> + <property name="subtitle" translatable="yes">Connecting to the internet helps you get new apps, information, and other upgrades. It also helps set the time and your location automatically.</property> + <property name="icon_name">network-wireless-symbolic</property> + <property name="show_icon" bind-source="GisNetworkPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + </object> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolled_window"> + <property name="margin_top">18</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport" id="viewport1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkListBox" id="network_list"> + <property name="visible">True</property> + <property name="vexpand">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkGrid" id="no_network_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">18</property> + <property name="halign">center</property> + <property name="valign">start</property> + <property name="row-spacing">10</property> + <child> + <object class="GtkSpinner" id="no_network_spinner"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="margin_start">6</property> + <property name="margin_end">6</property> + <property name="active">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="no_network_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="label" translatable="yes">No wireless available</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="turn_on_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Turn On</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkSwitch" id="turn_on_switch"> + <property name="visible">True</property> + <property name="halign">end</property> + <property name="valign">center</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/network/meson.build b/gnome-initial-setup/pages/network/meson.build new file mode 100644 index 0000000..2c0f2a7 --- /dev/null +++ b/gnome-initial-setup/pages/network/meson.build @@ -0,0 +1,12 @@ +sources += gnome.compile_resources( + 'network-resources', + files('network.gresource.xml'), + c_name: 'network' +) + +sources += files( + 'network-dialogs.c', + 'network-dialogs.h', + 'gis-network-page.c', + 'gis-network-page.h' +) diff --git a/gnome-initial-setup/pages/network/network-dialogs.c b/gnome-initial-setup/pages/network/network-dialogs.c new file mode 100644 index 0000000..09490e6 --- /dev/null +++ b/gnome-initial-setup/pages/network/network-dialogs.c @@ -0,0 +1,517 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Portions of this code were taken from network-manager-applet. + * Copyright 2008 - 2011 Red Hat, Inc. + */ + +#include <gtk/gtk.h> +#include <NetworkManager.h> +#include <nma-wifi-dialog.h> +#include <nma-mobile-wizard.h> + +typedef struct { + NMClient *client; +} WirelessDialogClosure; + +typedef struct { + NMClient *client; + NMDevice *device; +} MobileDialogClosure; + +static void +wireless_dialog_closure_closure_notify (gpointer data, + GClosure *gclosure) +{ + WirelessDialogClosure *closure = data; + g_object_unref (closure->client); + + g_slice_free (WirelessDialogClosure, data); +} + +static void +mobile_dialog_closure_free (gpointer data) +{ + MobileDialogClosure *closure = data; + g_object_unref (closure->client); + g_object_unref (closure->device); + + g_slice_free (MobileDialogClosure, data); +} + +static gboolean +wifi_can_create_wifi_network (NMClient *client) +{ + NMClientPermissionResult perm; + + /* FIXME: check WIFI_SHARE_PROTECTED too, and make the wireless dialog + * handle the permissions as well so that admins can restrict open network + * creation separately from protected network creation. + */ + perm = nm_client_get_permission_result (client, NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN); + if (perm == NM_CLIENT_PERMISSION_RESULT_YES || perm == NM_CLIENT_PERMISSION_RESULT_AUTH) + return TRUE; + + return FALSE; +} + +static void +activate_existing_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NMClient *client = NM_CLIENT (object); + NMActiveConnection *connection; + GError *error = NULL; + + connection = nm_client_activate_connection_finish (client, result, &error); + if (connection) { + g_object_unref (connection); + } else { + g_warning ("Failed to activate connection: (%d) %s", error->code, error->message); + g_error_free (error); + } +} + +static void +activate_new_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NMClient *client = NM_CLIENT (object); + NMActiveConnection *connection; + GError *error = NULL; + + connection = nm_client_add_and_activate_connection_finish (client, result, &error); + if (connection) { + g_object_unref (connection); + } else { + g_warning ("Failed to add new connection: (%d) %s", error->code, error->message); + g_error_free (error); + } +} + +static void +wireless_dialog_response_cb (GtkDialog *foo, + gint response, + gpointer user_data) +{ + NMAWifiDialog *dialog = NMA_WIFI_DIALOG (foo); + WirelessDialogClosure *closure = user_data; + NMConnection *connection, *fuzzy_match = NULL; + NMDevice *device; + NMAccessPoint *ap; + const GPtrArray *all; + int i; + + if (response != GTK_RESPONSE_OK) + goto done; + + /* nma_wifi_dialog_get_connection() returns a connection with the + * refcount incremented, so the caller must remember to unref it. + */ + connection = nma_wifi_dialog_get_connection (dialog, &device, &ap); + g_assert (connection); + g_assert (device); + + /* Find a similar connection and use that instead */ + all = nm_client_get_connections (closure->client); + for (i = 0; i < all->len; i++) { + if (nm_connection_compare (connection, + NM_CONNECTION (all->pdata[i]), + (NM_SETTING_COMPARE_FLAG_FUZZY | NM_SETTING_COMPARE_FLAG_IGNORE_ID))) { + fuzzy_match = NM_CONNECTION (all->pdata[i]); + break; + } + } + + if (fuzzy_match) { + nm_client_activate_connection_async (closure->client, + fuzzy_match, + device, + ap ? nm_object_get_path (NM_OBJECT (ap)) : NULL, + NULL, + activate_existing_cb, + NULL); + } else { + NMSetting *s_con; + NMSettingWireless *s_wifi; + const char *mode = NULL; + + /* Entirely new connection */ + + /* Don't autoconnect adhoc networks by default for now */ + s_wifi = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS); + if (s_wifi) + mode = nm_setting_wireless_get_mode (s_wifi); + if (g_strcmp0 (mode, "adhoc") == 0) { + s_con = nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION); + if (!s_con) { + s_con = nm_setting_connection_new (); + nm_connection_add_setting (connection, s_con); + } + g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL); + } + + nm_client_add_and_activate_connection_async (closure->client, + connection, + device, + ap ? nm_object_get_path (NM_OBJECT (ap)) : NULL, + NULL, + activate_new_cb, + NULL); + } + + /* Balance nma_wifi_dialog_get_connection() */ + g_object_unref (connection); + +done: + gtk_widget_hide (GTK_WIDGET (dialog)); + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +show_wireless_dialog (GtkWidget *toplevel, + NMClient *client, + GtkWidget *dialog) +{ + WirelessDialogClosure *closure; + + g_debug ("About to parent and show a network dialog"); + + g_assert (gtk_widget_is_toplevel (toplevel)); + g_object_set (G_OBJECT (dialog), + "modal", TRUE, + "transient-for", toplevel, + NULL); + + closure = g_slice_new (WirelessDialogClosure); + closure->client = g_object_ref (client); + g_signal_connect_data (dialog, "response", + G_CALLBACK (wireless_dialog_response_cb), + closure, wireless_dialog_closure_closure_notify, 0); + + g_object_bind_property (G_OBJECT (toplevel), "visible", + G_OBJECT (dialog), "visible", + G_BINDING_SYNC_CREATE); +} + +void +cc_network_panel_create_wifi_network (GtkWidget *toplevel, + NMClient *client) +{ + if (wifi_can_create_wifi_network (client)) { + show_wireless_dialog (toplevel, client, + nma_wifi_dialog_new_for_create (client)); + } +} + +void +cc_network_panel_connect_to_hidden_network (GtkWidget *toplevel, + NMClient *client) +{ + g_debug ("connect to hidden wifi"); + show_wireless_dialog (toplevel, client, + nma_wifi_dialog_new_for_hidden (client)); +} + +void +cc_network_panel_connect_to_8021x_network (GtkWidget *toplevel, + NMClient *client, + NMDevice *device, + const gchar *arg_access_point) +{ + NMConnection *connection; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x *s_8021x; + NM80211ApSecurityFlags wpa_flags, rsn_flags; + GtkWidget *dialog; + char *uuid; + NMAccessPoint *ap; + + g_debug ("connect to 8021x wifi"); + ap = nm_device_wifi_get_access_point_by_path (NM_DEVICE_WIFI (device), arg_access_point); + if (ap == NULL) { + g_warning ("didn't find access point with path %s", arg_access_point); + return; + } + + /* If the AP is WPA[2]-Enterprise then we need to set up a minimal 802.1x + * setting and ask the user for more information. + */ + rsn_flags = nm_access_point_get_rsn_flags (ap); + wpa_flags = nm_access_point_get_wpa_flags (ap); + if (!(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + && !(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { + g_warning ("Network panel loaded with connect-8021x-wifi but the " + "access point does not support 802.1x"); + return; + } + + connection = nm_simple_connection_new (); + + /* Need a UUID for the "always ask" stuff in the Dialog of Doom */ + s_con = (NMSettingConnection *) nm_setting_connection_new (); + uuid = nm_utils_uuid_generate (); + g_object_set (s_con, NM_SETTING_CONNECTION_UUID, uuid, NULL); + g_free (uuid); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + g_object_set (s_wifi, + NM_SETTING_WIRELESS_SSID, nm_access_point_get_ssid (ap), + NULL); + + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL); + nm_connection_add_setting (connection, NM_SETTING (s_wsec)); + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_setting_802_1x_add_eap_method (s_8021x, "ttls"); + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", NULL); + nm_connection_add_setting (connection, NM_SETTING (s_8021x)); + + dialog = nma_wifi_dialog_new (client, connection, device, ap, FALSE); + show_wireless_dialog (toplevel, client, dialog); +} + +static void +connect_3g (NMConnection *connection, + gboolean canceled, + gpointer user_data) +{ + MobileDialogClosure *closure = user_data; + + if (canceled == FALSE) { + g_return_if_fail (connection != NULL); + + /* Ask NM to add the new connection and activate it; NM will fill in the + * missing details based on the specific object and the device. + */ + nm_client_add_and_activate_connection_async (closure->client, + connection, + closure->device, + "/", + NULL, + activate_new_cb, + NULL); + } + + mobile_dialog_closure_free (closure); +} + +static void +cdma_mobile_wizard_done (NMAMobileWizard *wizard, + gboolean canceled, + NMAMobileWizardAccessMethod *method, + gpointer user_data) +{ + NMConnection *connection = NULL; + + if (!canceled && method) { + NMSetting *setting; + char *uuid, *id; + + if (method->devtype != NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) { + g_warning ("Unexpected device type (not CDMA)."); + canceled = TRUE; + goto done; + } + + connection = nm_simple_connection_new (); + + setting = nm_setting_cdma_new (); + g_object_set (setting, + NM_SETTING_CDMA_NUMBER, "#777", + NM_SETTING_CDMA_USERNAME, method->username, + NM_SETTING_CDMA_PASSWORD, method->password, + NULL); + nm_connection_add_setting (connection, setting); + + /* Serial setting */ + setting = nm_setting_serial_new (); + g_object_set (setting, + NM_SETTING_SERIAL_BAUD, 115200, + NM_SETTING_SERIAL_BITS, 8, + NM_SETTING_SERIAL_PARITY, 'n', + NM_SETTING_SERIAL_STOPBITS, 1, + NULL); + nm_connection_add_setting (connection, setting); + + nm_connection_add_setting (connection, nm_setting_ppp_new ()); + + setting = nm_setting_connection_new (); + if (method->plan_name) + id = g_strdup_printf ("%s %s", method->provider_name, method->plan_name); + else + id = g_strdup_printf ("%s connection", method->provider_name); + uuid = nm_utils_uuid_generate (); + g_object_set (setting, + NM_SETTING_CONNECTION_ID, id, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_CDMA_SETTING_NAME, + NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, + NM_SETTING_CONNECTION_UUID, uuid, + NULL); + g_free (uuid); + g_free (id); + nm_connection_add_setting (connection, setting); + } + + done: + connect_3g (connection, canceled, user_data); + nma_mobile_wizard_destroy (wizard); +} + +static void +gsm_mobile_wizard_done (NMAMobileWizard *wizard, + gboolean canceled, + NMAMobileWizardAccessMethod *method, + gpointer user_data) +{ + NMConnection *connection = NULL; + + if (!canceled && method) { + NMSetting *setting; + char *uuid, *id; + + if (method->devtype != NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) { + g_warning ("Unexpected device type (not GSM)."); + canceled = TRUE; + goto done; + } + + connection = nm_simple_connection_new (); + + setting = nm_setting_gsm_new (); + g_object_set (setting, + NM_SETTING_GSM_NUMBER, "*99#", + NM_SETTING_GSM_USERNAME, method->username, + NM_SETTING_GSM_PASSWORD, method->password, + NM_SETTING_GSM_APN, method->gsm_apn, + NULL); + nm_connection_add_setting (connection, setting); + + /* Serial setting */ + setting = nm_setting_serial_new (); + g_object_set (setting, + NM_SETTING_SERIAL_BAUD, 115200, + NM_SETTING_SERIAL_BITS, 8, + NM_SETTING_SERIAL_PARITY, 'n', + NM_SETTING_SERIAL_STOPBITS, 1, + NULL); + nm_connection_add_setting (connection, setting); + + nm_connection_add_setting (connection, nm_setting_ppp_new ()); + + setting = nm_setting_connection_new (); + if (method->plan_name) + id = g_strdup_printf ("%s %s", method->provider_name, method->plan_name); + else + id = g_strdup_printf ("%s connection", method->provider_name); + uuid = nm_utils_uuid_generate (); + g_object_set (setting, + NM_SETTING_CONNECTION_ID, id, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, + NM_SETTING_CONNECTION_UUID, uuid, + NULL); + g_free (uuid); + g_free (id); + nm_connection_add_setting (connection, setting); + } + +done: + connect_3g (connection, canceled, user_data); + nma_mobile_wizard_destroy (wizard); +} + +static void +toplevel_shown (GtkWindow *toplevel, + GParamSpec *pspec, + NMAMobileWizard *wizard) +{ + gboolean visible = FALSE; + + g_object_get (G_OBJECT (toplevel), "visible", &visible, NULL); + if (visible) + nma_mobile_wizard_present (wizard); +} + +static gboolean +show_wizard_idle_cb (NMAMobileWizard *wizard) +{ + nma_mobile_wizard_present (wizard); + return FALSE; +} + +void +cc_network_panel_connect_to_3g_network (GtkWidget *toplevel, + NMClient *client, + NMDevice *device) +{ + MobileDialogClosure *closure; + NMAMobileWizard *wizard; + NMDeviceModemCapabilities caps; + gboolean visible = FALSE; + + g_debug ("connect to 3g"); + if (!NM_IS_DEVICE_MODEM (device)) { + g_warning ("Network panel loaded with connect-3g but the selected device" + " is not a modem"); + return; + } + + closure = g_slice_new (MobileDialogClosure); + closure->client = g_object_ref (client); + closure->device = g_object_ref (device); + + caps = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (device)); + if (caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) { + wizard = nma_mobile_wizard_new (GTK_WINDOW (toplevel), NULL, NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS, FALSE, + gsm_mobile_wizard_done, closure); + if (wizard == NULL) { + g_warning ("failed to construct GSM wizard"); + return; + } + } else if (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) { + wizard = nma_mobile_wizard_new (GTK_WINDOW (toplevel), NULL, NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO, FALSE, + cdma_mobile_wizard_done, closure); + if (wizard == NULL) { + g_warning ("failed to construct CDMA wizard"); + return; + } + } else { + g_warning ("Network panel loaded with connect-3g but the selected device" + " does not support GSM or CDMA"); + mobile_dialog_closure_free (closure); + return; + } + + g_object_get (G_OBJECT (toplevel), "visible", &visible, NULL); + if (visible) { + g_debug ("Scheduling showing the Mobile wizard"); + g_idle_add ((GSourceFunc) show_wizard_idle_cb, wizard); + } else { + g_debug ("Will show wizard a bit later, toplevel is not visible"); + g_signal_connect (G_OBJECT (toplevel), "notify::visible", + G_CALLBACK (toplevel_shown), wizard); + } +} diff --git a/gnome-initial-setup/pages/network/network-dialogs.h b/gnome-initial-setup/pages/network/network-dialogs.h new file mode 100644 index 0000000..0f02a52 --- /dev/null +++ b/gnome-initial-setup/pages/network/network-dialogs.h @@ -0,0 +1,41 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _NETWORK_DIALOGS_H +#define _NETWORK_DIALOGS_H + +#include <NetworkManager.h> +#include <gtk/gtk.h> + +void cc_network_panel_create_wifi_network (GtkWidget *toplevel, + NMClient *client); + +void cc_network_panel_connect_to_hidden_network (GtkWidget *toplevel, + NMClient *client); + +void cc_network_panel_connect_to_8021x_network (GtkWidget *toplevel, + NMClient *client, + NMDevice *device, + const gchar *arg_access_point); + +void cc_network_panel_connect_to_3g_network (GtkWidget *toplevel, + NMClient *client, + NMDevice *device); + +#endif /* _NETWORK_DIALOGS_H */ diff --git a/gnome-initial-setup/pages/network/network.gresource.xml b/gnome-initial-setup/pages/network/network.gresource.xml new file mode 100644 index 0000000..bf5394f --- /dev/null +++ b/gnome-initial-setup/pages/network/network.gresource.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks" alias="gis-network-page.ui">gis-network-page.ui</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.c b/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.c new file mode 100644 index 0000000..89f3e34 --- /dev/null +++ b/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.c @@ -0,0 +1,246 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright © 2020 Endless Mobile, Inc. + * + * 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: + * Philip Withnall <withnall@endlessm.com> + */ + +/* Parental controls page {{{1 */ + +#define PAGE_ID "parental-controls" + +#include "config.h" + +#include <glib.h> +#include <glib-object.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gtk/gtk.h> +#include <libmalcontent-ui/malcontent-ui.h> + +#include "parental-controls-resources.h" +#include "gis-page-header.h" +#include "gis-parental-controls-page.h" + +struct _GisParentalControlsPage +{ + GisPage parent_instance; + + GtkWidget *header; + GtkWidget *user_controls; +}; + +G_DEFINE_TYPE (GisParentalControlsPage, gis_parental_controls_page, GIS_TYPE_PAGE) + +static gboolean +gis_parental_controls_page_save_data (GisPage *gis_page, + GError **error) +{ + GisParentalControlsPage *page = GIS_PARENTAL_CONTROLS_PAGE (gis_page); + g_autoptr(GDBusConnection) system_bus = NULL; + g_autoptr(MctManager) manager = NULL; + g_auto(MctAppFilterBuilder) builder = MCT_APP_FILTER_BUILDER_INIT (); + g_autoptr(MctAppFilter) app_filter = NULL; + ActUser *main_user; + + /* The parent and child users are created by the #GisAccountPage earlier in + * the save_data() process. We now need to set the parental controls on the + * child user. The earlier step in the process must have succeeded. */ + gis_driver_get_user_permissions (gis_page->driver, &main_user, NULL); + g_return_val_if_fail (main_user != NULL, FALSE); + + mct_user_controls_build_app_filter (MCT_USER_CONTROLS (page->user_controls), &builder); + app_filter = mct_app_filter_builder_end (&builder); + + /* FIXME: should become asynchronous */ + system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); + if (system_bus == NULL) + return FALSE; + + manager = mct_manager_new (system_bus); + if (!mct_manager_set_app_filter (manager, + act_user_get_uid (main_user), + app_filter, + MCT_MANAGER_SET_VALUE_FLAGS_NONE, + NULL, + error)) + return FALSE; + + return TRUE; +} + +static void +gis_parental_controls_page_shown (GisPage *gis_page) +{ + GisParentalControlsPage *page = GIS_PARENTAL_CONTROLS_PAGE (gis_page); + + gtk_widget_grab_focus (page->user_controls); +} + +static void +update_header (GisParentalControlsPage *page) +{ + g_autofree gchar *title = NULL; + const gchar *subtitle, *icon_name; + GdkPixbuf *pixbuf; + + /* Translators: The placeholder is the user’s full name. */ + title = g_strdup_printf (_("Parental Controls for %s"), + gis_driver_get_full_name (GIS_PAGE (page)->driver)); + subtitle = _("Set restrictions on what this user can run or install."); + pixbuf = gis_driver_get_avatar (GIS_PAGE (page)->driver); + icon_name = (pixbuf != NULL) ? NULL : "dialog-password-symbolic"; + + g_object_set (G_OBJECT (page->header), + "title", title, + "subtitle", subtitle, + NULL); + if (pixbuf != NULL) + g_object_set (G_OBJECT (page->header), "pixbuf", pixbuf, NULL); + else if (icon_name != NULL) + g_object_set (G_OBJECT (page->header), "icon-name", icon_name, NULL); +} + +static void +update_user_controls (GisParentalControlsPage *page) +{ + mct_user_controls_set_user_locale (MCT_USER_CONTROLS (page->user_controls), + gis_driver_get_user_language (GIS_PAGE (page)->driver)); + mct_user_controls_set_user_display_name (MCT_USER_CONTROLS (page->user_controls), + gis_driver_get_full_name (GIS_PAGE (page)->driver)); +} + +static void +user_details_changed_cb (GObject *obj, + GParamSpec *pspec, + gpointer user_data) +{ + GisParentalControlsPage *page = GIS_PARENTAL_CONTROLS_PAGE (user_data); + + update_user_controls (page); + update_header (page); +} + +static void +gis_parental_controls_page_constructed (GObject *object) +{ + GisParentalControlsPage *page = GIS_PARENTAL_CONTROLS_PAGE (object); + g_autoptr(GPermission) permission = NULL; + g_auto(MctAppFilterBuilder) builder = MCT_APP_FILTER_BUILDER_INIT (); + g_autoptr(MctAppFilter) app_filter = NULL; + + G_OBJECT_CLASS (gis_parental_controls_page_parent_class)->constructed (object); + + /* No validation needed. */ + gis_page_set_complete (GIS_PAGE (page), TRUE); + + /* Set up the user controls. We can’t set #MctUserControls:user because + * there’s no way to represent a not-yet-created user using an #ActUser. */ + mct_user_controls_set_user_account_type (MCT_USER_CONTROLS (page->user_controls), + ACT_USER_ACCOUNT_TYPE_STANDARD); + update_user_controls (page); + + app_filter = mct_app_filter_builder_end (&builder); + mct_user_controls_set_app_filter (MCT_USER_CONTROLS (page->user_controls), app_filter); + + /* The gnome-initial-setup user should always be allowed to set parental + * controls. */ + permission = g_simple_permission_new (TRUE); + mct_user_controls_set_permission (MCT_USER_CONTROLS (page->user_controls), permission); + + /* Connect to signals. */ + g_signal_connect (GIS_PAGE (page)->driver, "notify::full-name", + G_CALLBACK (user_details_changed_cb), page); + g_signal_connect (GIS_PAGE (page)->driver, "notify::avatar", + G_CALLBACK (user_details_changed_cb), page); + g_signal_connect (GIS_PAGE (page)->driver, "notify::user-locale", + G_CALLBACK (user_details_changed_cb), page); + g_signal_connect (GIS_PAGE (page)->driver, "notify::user-display-name", + G_CALLBACK (user_details_changed_cb), page); + + update_header (page); + + gtk_widget_show (GTK_WIDGET (page)); +} + +static void +gis_parental_controls_page_dispose (GObject *object) +{ + GisParentalControlsPage *page = GIS_PARENTAL_CONTROLS_PAGE (object); + + if (GIS_PAGE (object)->driver != NULL) + { + g_signal_handlers_disconnect_by_func (GIS_PAGE (page)->driver, + user_details_changed_cb, page); + } + + G_OBJECT_CLASS (gis_parental_controls_page_parent_class)->dispose (object); +} + +static void +gis_parental_controls_page_locale_changed (GisPage *gis_page) +{ + GisParentalControlsPage *page = GIS_PARENTAL_CONTROLS_PAGE (gis_page); + + gis_page_set_title (gis_page, _("Parental Controls")); + update_header (page); +} + +static void +gis_parental_controls_page_class_init (GisParentalControlsPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GisPageClass *page_class = GIS_PAGE_CLASS (klass); + + object_class->constructed = gis_parental_controls_page_constructed; + object_class->dispose = gis_parental_controls_page_dispose; + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_parental_controls_page_locale_changed; + page_class->save_data = gis_parental_controls_page_save_data; + page_class->shown = gis_parental_controls_page_shown; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/initial-setup/gis-parental-controls-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, GisParentalControlsPage, header); + gtk_widget_class_bind_template_child (widget_class, GisParentalControlsPage, user_controls); +} + +static void +gis_parental_controls_page_init (GisParentalControlsPage *page) +{ + g_resources_register (parental_controls_get_resource ()); + + /* Ensure types exist for widgets in the UI file. */ + g_type_ensure (GIS_TYPE_PAGE_HEADER); + g_type_ensure (MCT_TYPE_USER_CONTROLS); + + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_parental_controls_page (GisDriver *driver) +{ + /* Skip parental controls if they’re not enabled. */ + if (!gis_driver_get_parental_controls_enabled (driver)) + return NULL; + + return g_object_new (GIS_TYPE_PARENTAL_CONTROLS_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.h b/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.h new file mode 100644 index 0000000..3b32341 --- /dev/null +++ b/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.h @@ -0,0 +1,38 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright © 2020 Endless Mobile, Inc. + * + * 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: + * Philip Withnall <withnall@endlessm.com> + */ + +#pragma once + +#include <glib.h> +#include <glib-object.h> + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_PARENTAL_CONTROLS_PAGE (gis_parental_controls_page_get_type ()) +G_DECLARE_FINAL_TYPE (GisParentalControlsPage, gis_parental_controls_page, GIS, PARENTAL_CONTROLS_PAGE, GisPage) + +GType gis_parental_controls_page_get_type (void); + +GisPage *gis_prepare_parental_controls_page (GisDriver *driver); + +G_END_DECLS diff --git a/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.ui b/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.ui new file mode 100644 index 0000000..8187632 --- /dev/null +++ b/gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.ui @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisParentalControlsPage" parent="GisPage"> + <child> + <object class="GtkBox" id="page"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <property name="orientation">vertical</property> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin-top">24</property> + <!-- title and subtitle are set in code, so are not set here --> + <property name="icon-name">dialog-password-symbolic</property> + <property name="show-icon" bind-source="GisParentalControlsPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + </object> + </child> + <child> + <object class="MctUserControls" id="user_controls"> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/parental-controls/meson.build b/gnome-initial-setup/pages/parental-controls/meson.build new file mode 100644 index 0000000..539460c --- /dev/null +++ b/gnome-initial-setup/pages/parental-controls/meson.build @@ -0,0 +1,10 @@ +sources += gnome.compile_resources( + 'parental-controls-resources', + files('parental-controls.gresource.xml'), + c_name: 'parental_controls', +) + +sources += files( + 'gis-parental-controls-page.c', + 'gis-parental-controls-page.h', +) diff --git a/gnome-initial-setup/pages/parental-controls/parental-controls.gresource.xml b/gnome-initial-setup/pages/parental-controls/parental-controls.gresource.xml new file mode 100644 index 0000000..59460b5 --- /dev/null +++ b/gnome-initial-setup/pages/parental-controls/parental-controls.gresource.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks" alias="gis-parental-controls-page.ui">gis-parental-controls-page.ui</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/password/account-resources.h b/gnome-initial-setup/pages/password/account-resources.h new file mode 100644 index 0000000..d2abafe --- /dev/null +++ b/gnome-initial-setup/pages/password/account-resources.h @@ -0,0 +1,7 @@ +#ifndef __RESOURCE_account_H__ +#define __RESOURCE_account_H__ + +#include <gio/gio.h> + +extern GResource *account_get_resource (void); +#endif diff --git a/gnome-initial-setup/pages/password/gis-password-page.c b/gnome-initial-setup/pages/password/gis-password-page.c new file mode 100644 index 0000000..d7a62cd --- /dev/null +++ b/gnome-initial-setup/pages/password/gis-password-page.c @@ -0,0 +1,504 @@ +/* -*- 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> + */ + +/* Password page {{{1 */ + +#define PAGE_ID "password" + +#include "config.h" +#include "password-resources.h" +#include "gis-password-page.h" + +#include "gis-keyring.h" + +#include "pw-utils.h" +#include "../account/um-utils.h" + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "gis-page-header.h" + +#define VALIDATION_TIMEOUT 600 + +struct _GisPasswordPagePrivate +{ + GtkWidget *password_entry; + GtkWidget *confirm_entry; + GtkWidget *password_strength; + GtkWidget *password_explanation; + GtkWidget *confirm_explanation; + GtkWidget *header; + + gboolean valid_confirm; + gboolean valid_password; + guint timeout_id; + const gchar *username; + gboolean parent_mode; +}; +typedef struct _GisPasswordPagePrivate GisPasswordPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisPasswordPage, gis_password_page, GIS_TYPE_PAGE); + +typedef enum +{ + PROP_PARENT_MODE = 1, +} GisPasswordPageProperty; + +static GParamSpec *obj_props[PROP_PARENT_MODE + 1]; + +static void +update_header (GisPasswordPage *page) +{ + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + g_autofree gchar *title = NULL; + g_autofree gchar *subtitle = NULL; + const gchar *icon_name; + GdkPixbuf *pixbuf; + +#ifndef HAVE_PARENTAL_CONTROLS + /* Don’t break UI compatibility if parental controls are disabled. */ + title = g_strdup (_("Set a Password")); + subtitle = g_strdup (_("Be careful not to lose your password.")); + pixbuf = NULL; + icon_name = "dialog-password-symbolic"; +#else + if (!priv->parent_mode) + { + /* Translators: The placeholder is for the user’s full name. */ + title = g_strdup_printf (_("Set a Password for %s"), + gis_driver_get_full_name (GIS_PAGE (page)->driver)); + subtitle = g_strdup (_("Be careful not to lose your password.")); + pixbuf = gis_driver_get_avatar (GIS_PAGE (page)->driver); + icon_name = (pixbuf != NULL) ? NULL : "dialog-password-symbolic"; + } + else + { + title = g_strdup (_("Set a Parent Password")); + /* Translators: The placeholder is the full name of the child user on the system. */ + subtitle = g_strdup_printf (_("This password will control access to the parental controls for %s."), + gis_driver_get_full_name (GIS_PAGE (page)->driver)); + icon_name = "org.freedesktop.MalcontentControl-symbolic"; + pixbuf = NULL; + } +#endif + + /* Doesn’t make sense to set both. */ + g_assert (icon_name == NULL || pixbuf == NULL); + + g_object_set (G_OBJECT (priv->header), + "title", title, + "subtitle", subtitle, + NULL); + if (pixbuf != NULL) + g_object_set (G_OBJECT (priv->header), "pixbuf", pixbuf, NULL); + else if (icon_name != NULL) + g_object_set (G_OBJECT (priv->header), "icon-name", icon_name, NULL); +} + +static void +set_parent_mode (GisPasswordPage *page, + gboolean parent_mode) +{ + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + + g_return_if_fail (GIS_IS_PASSWORD_PAGE (page)); + + if (priv->parent_mode == parent_mode) + return; + + priv->parent_mode = parent_mode; + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_PARENT_MODE]); + + update_header (page); +} + +static gboolean +page_validate (GisPasswordPage *page) +{ + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + + return priv->valid_confirm; +} + +static void +update_page_validation (GisPasswordPage *page) +{ + gis_page_set_complete (GIS_PAGE (page), page_validate (page)); +} + +static gboolean +gis_password_page_save_data (GisPage *gis_page, + GError **error) +{ + GisPasswordPage *page = GIS_PASSWORD_PAGE (gis_page); + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + ActUser *act_user; + UmAccountMode account_mode; + const gchar *password = NULL; + + g_assert (gis_page->driver != NULL); + + account_mode = gis_driver_get_account_mode (gis_page->driver); + + if (!priv->parent_mode) + gis_driver_get_user_permissions (gis_page->driver, &act_user, &password); + else + gis_driver_get_parent_permissions (gis_page->driver, &act_user, &password); + + if (account_mode == UM_ENTERPRISE) { + g_assert (!priv->parent_mode); + + if (password != NULL) + gis_update_login_keyring_password (password); + return TRUE; + } + + password = gtk_entry_get_text (GTK_ENTRY (priv->password_entry)); + + if (strlen (password) == 0) + act_user_set_password_mode (act_user, ACT_USER_PASSWORD_MODE_NONE); + else + act_user_set_password (act_user, password, ""); + + if (!priv->parent_mode) + gis_driver_set_user_permissions (gis_page->driver, act_user, password); + else + gis_driver_set_parent_permissions (gis_page->driver, act_user, password); + + if (!priv->parent_mode) + gis_update_login_keyring_password (password); + + return TRUE; +} + +static void +gis_password_page_shown (GisPage *gis_page) +{ + GisPasswordPage *page = GIS_PASSWORD_PAGE (gis_page); + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + + gtk_widget_grab_focus (priv->password_entry); +} + +static gboolean +validate (GisPasswordPage *page) +{ + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + const gchar *password; + const gchar *verify; + gint strength_level; + const gchar *hint; + + g_clear_handle_id (&priv->timeout_id, g_source_remove); + + password = gtk_entry_get_text (GTK_ENTRY (priv->password_entry)); + verify = gtk_entry_get_text (GTK_ENTRY (priv->confirm_entry)); + + pw_strength (password, NULL, priv->username, &hint, &strength_level); + gtk_level_bar_set_value (GTK_LEVEL_BAR (priv->password_strength), strength_level); + gtk_label_set_label (GTK_LABEL (priv->password_explanation), hint); + + gtk_label_set_label (GTK_LABEL (priv->confirm_explanation), ""); + priv->valid_confirm = FALSE; + + priv->valid_password = (strlen (password) && strength_level > 1); + if (priv->valid_password) { + set_entry_validation_checkmark (GTK_ENTRY (priv->password_entry)); + clear_entry_validation_error (GTK_ENTRY (priv->password_entry)); + } else { + set_entry_validation_error (GTK_ENTRY (priv->password_entry), _("This is a weak password.")); + } + + if (strlen (password) > 0 && strlen (verify) > 0) { + priv->valid_confirm = (strcmp (password, verify) == 0); + if (!priv->valid_confirm) { + gtk_label_set_label (GTK_LABEL (priv->confirm_explanation), _("The passwords do not match.")); + } + else { + set_entry_validation_checkmark (GTK_ENTRY (priv->confirm_entry)); + } + } + + /* + * We deliberately don’t validate that the parent password and main user + * password are different. It’s more feasible that someone would usefully + * want to set their system up that way, than it is that the parent and child + * would accidentally choose the same password. + */ + + update_page_validation (page); + + return G_SOURCE_REMOVE; +} + +static gboolean +on_focusout (GisPasswordPage *page) +{ + validate (page); + + return FALSE; +} + +static void +password_changed (GtkWidget *w, + GParamSpec *pspec, + GisPasswordPage *page) +{ + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + + clear_entry_validation_error (GTK_ENTRY (w)); + clear_entry_validation_error (GTK_ENTRY (priv->confirm_entry)); + + priv->valid_password = FALSE; + update_page_validation (page); + + if (priv->timeout_id != 0) + g_source_remove (priv->timeout_id); + priv->timeout_id = g_timeout_add (VALIDATION_TIMEOUT, (GSourceFunc)validate, page); +} + +static void +confirm_changed (GtkWidget *w, + GParamSpec *pspec, + GisPasswordPage *page) +{ + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + + clear_entry_validation_error (GTK_ENTRY (w)); + + priv->valid_confirm = FALSE; + update_page_validation (page); + + if (priv->timeout_id != 0) + g_source_remove (priv->timeout_id); + priv->timeout_id = g_timeout_add (VALIDATION_TIMEOUT, (GSourceFunc)validate, page); +} + +static void +username_changed (GObject *obj, GParamSpec *pspec, GisPasswordPage *page) +{ + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + priv->username = gis_driver_get_username (GIS_DRIVER (obj)); + + if (priv->username) + gtk_widget_show (GTK_WIDGET (page)); + else + gtk_widget_hide (GTK_WIDGET (page)); + + clear_entry_validation_error (GTK_ENTRY (priv->password_entry)); + clear_entry_validation_error (GTK_ENTRY (priv->confirm_entry)); + + validate (page); +} + +static void +full_name_or_avatar_changed (GObject *obj, + GParamSpec *pspec, + gpointer user_data) +{ + GisPasswordPage *page = GIS_PASSWORD_PAGE (user_data); + + update_header (page); +} + +static void +confirm (GisPasswordPage *page) +{ + if (page_validate (page)) + gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (page)->driver)); +} + +static void +gis_password_page_constructed (GObject *object) +{ + GisPasswordPage *page = GIS_PASSWORD_PAGE (object); + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + + G_OBJECT_CLASS (gis_password_page_parent_class)->constructed (object); + + g_signal_connect (priv->password_entry, "notify::text", + G_CALLBACK (password_changed), page); + g_signal_connect_swapped (priv->password_entry, "focus-out-event", + G_CALLBACK (on_focusout), page); + g_signal_connect_swapped (priv->password_entry, "activate", + G_CALLBACK (confirm), page); + + g_signal_connect (priv->confirm_entry, "notify::text", + G_CALLBACK (confirm_changed), page); + g_signal_connect_swapped (priv->confirm_entry, "focus-out-event", + G_CALLBACK (on_focusout), page); + g_signal_connect_swapped (priv->confirm_entry, "activate", + G_CALLBACK (confirm), page); + + g_signal_connect (GIS_PAGE (page)->driver, "notify::username", + G_CALLBACK (username_changed), page); + g_signal_connect (GIS_PAGE (page)->driver, "notify::full-name", + G_CALLBACK (full_name_or_avatar_changed), page); + g_signal_connect (GIS_PAGE (page)->driver, "notify::avatar", + G_CALLBACK (full_name_or_avatar_changed), page); + + validate (page); + update_header (page); + + gtk_widget_show (GTK_WIDGET (page)); +} + +static void +gis_password_page_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GisPasswordPage *page = GIS_PASSWORD_PAGE (object); + GisPasswordPagePrivate *priv = gis_password_page_get_instance_private (page); + + switch ((GisPasswordPageProperty) prop_id) + { + case PROP_PARENT_MODE: + g_value_set_boolean (value, priv->parent_mode); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gis_password_page_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GisPasswordPage *page = GIS_PASSWORD_PAGE (object); + + switch ((GisPasswordPageProperty) prop_id) + { + case PROP_PARENT_MODE: + set_parent_mode (page, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gis_password_page_dispose (GObject *object) +{ + if (GIS_PAGE (object)->driver) { + g_signal_handlers_disconnect_by_func (GIS_PAGE (object)->driver, + username_changed, object); + g_signal_handlers_disconnect_by_func (GIS_PAGE (object)->driver, + full_name_or_avatar_changed, object); + } + + G_OBJECT_CLASS (gis_password_page_parent_class)->dispose (object); +} + +static void +gis_password_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("Password")); +} + +static void +gis_password_page_class_init (GisPasswordPageClass *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-password-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, password_entry); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, confirm_entry); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, password_strength); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, password_explanation); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, confirm_explanation); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPasswordPage, header); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_password_page_locale_changed; + page_class->save_data = gis_password_page_save_data; + page_class->shown = gis_password_page_shown; + + object_class->constructed = gis_password_page_constructed; + object_class->get_property = gis_password_page_get_property; + object_class->set_property = gis_password_page_set_property; + object_class->dispose = gis_password_page_dispose; + + /** + * GisPasswordPage:parent-mode: + * + * If %FALSE (the default), this page will collect a password for the main + * user account. If %TRUE, it will collect a password for controlling access + * to parental controls — this will affect where the password is stored, and + * the appearance of the page. + * + * Since: 3.36 + */ + obj_props[PROP_PARENT_MODE] = + g_param_spec_boolean ("parent-mode", "Parent Mode", + "Whether to collect a password for the main user account or a parent account.", + FALSE, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props); +} + +static void +gis_password_page_init (GisPasswordPage *page) +{ + GtkCssProvider *provider; + + g_resources_register (password_get_resource ()); + g_type_ensure (GIS_TYPE_PAGE_HEADER); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/initial-setup/gis-password-page.css"); + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_password_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_PASSWORD_PAGE, + "driver", driver, + NULL); +} + +GisPage * +gis_prepare_parent_password_page (GisDriver *driver) +{ + /* Skip prompting for the parent password if parental controls aren’t enabled. */ + if (!gis_driver_get_parental_controls_enabled (driver)) + return NULL; + + return g_object_new (GIS_TYPE_PASSWORD_PAGE, + "driver", driver, + "parent-mode", TRUE, + NULL); +} diff --git a/gnome-initial-setup/pages/password/gis-password-page.css b/gnome-initial-setup/pages/password/gis-password-page.css new file mode 100644 index 0000000..e9b5f54 --- /dev/null +++ b/gnome-initial-setup/pages/password/gis-password-page.css @@ -0,0 +1,16 @@ +levelbar .strength-weak { + background-color: #cc0000; + border-color: #cc0000; +} + +levelbar .strength-low { + background-color: #f5ce00; + border-color: #f5ce00; +} + +levelbar .strength-medium, +levelbar .strength-good, +levelbar .strength-high { + background-color: #73d216; + border-color: #73d216; +} diff --git a/gnome-initial-setup/pages/password/gis-password-page.h b/gnome-initial-setup/pages/password/gis-password-page.h new file mode 100644 index 0000000..2a4d1c6 --- /dev/null +++ b/gnome-initial-setup/pages/password/gis-password-page.h @@ -0,0 +1,58 @@ +/* -*- 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> + */ + +#ifndef __GIS_PASSWORD_PAGE_H__ +#define __GIS_PASSWORD_PAGE_H__ + +#include <glib-object.h> + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_PASSWORD_PAGE (gis_password_page_get_type ()) +#define GIS_PASSWORD_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_PASSWORD_PAGE, GisPasswordPage)) +#define GIS_PASSWORD_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_PASSWORD_PAGE, GisPasswordPageClass)) +#define GIS_IS_PASSWORD_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_PASSWORD_PAGE)) +#define GIS_IS_PASSWORD_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_PASSWORD_PAGE)) +#define GIS_PASSWORD_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_PASSWORD_PAGE, GisPasswordPageClass)) + +typedef struct _GisPasswordPage GisPasswordPage; +typedef struct _GisPasswordPageClass GisPasswordPageClass; + +struct _GisPasswordPage +{ + GisPage parent; +}; + +struct _GisPasswordPageClass +{ + GisPageClass parent_class; +}; + +GType gis_password_page_get_type (void); + +GisPage *gis_prepare_password_page (GisDriver *driver); +GisPage *gis_prepare_parent_password_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_PASSWORD_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/password/gis-password-page.ui b/gnome-initial-setup/pages/password/gis-password-page.ui new file mode 100644 index 0000000..b21010c --- /dev/null +++ b/gnome-initial-setup/pages/password/gis-password-page.ui @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisPasswordPage" parent="GisPage"> + <child> + <object class="GtkBox" id="page"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <property name="orientation">vertical</property> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin_top">24</property> + <!-- title and subtitle are set in code, so are not set here --> + <property name="icon_name">dialog-password-symbolic</property> + <property name="show_icon" bind-source="GisPasswordPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + </object> + </child> + <child> + <object class="GtkGrid" id="secrets"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <property name="margin_top">40</property> + <child> + <object class="GtkLabel" id="password_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password_entry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="confirm_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Confirm</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">confirm_entry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="confirm_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLevelBar" id="password_strength"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">fill</property> + <property name="valign">center</property> + <property name="max-value">5</property> + <property name="mode">discrete</property> + <offsets> + <offset name="strength-weak" value="1"/> + <offset name="strength-low" value="2"/> + <offset name="strength-medium" value="3"/> + <offset name="strength-good" value="4"/> + <offset name="strength-high" value="5"/> + </offsets> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="password_explanation"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="height-request">50</property> + <property name="wrap">True</property> + <property name="hexpand">True</property> + <property name="wrap_mode">word-char</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="confirm_explanation"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="wrap">True</property> + <property name="hexpand">True</property> + <property name="wrap_mode">word-char</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/password/meson.build b/gnome-initial-setup/pages/password/meson.build new file mode 100644 index 0000000..bba1ab9 --- /dev/null +++ b/gnome-initial-setup/pages/password/meson.build @@ -0,0 +1,13 @@ +sources += gnome.compile_resources( + 'password-resources', + files('password.gresource.xml'), + c_name: 'password' +) + +sources += files( + 'gis-password-page.c', + 'gis-password-page.h', + join_paths(account_sources_dir, 'um-utils.h'), + 'pw-utils.c', + 'pw-utils.h', +) diff --git a/gnome-initial-setup/pages/password/password.gresource.xml b/gnome-initial-setup/pages/password/password.gresource.xml new file mode 100644 index 0000000..22cad98 --- /dev/null +++ b/gnome-initial-setup/pages/password/password.gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks" alias="gis-password-page.ui">gis-password-page.ui</file> + <file alias="gis-password-page.css">gis-password-page.css</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/password/pw-utils.c b/gnome-initial-setup/pages/password/pw-utils.c new file mode 100644 index 0000000..0913b07 --- /dev/null +++ b/gnome-initial-setup/pages/password/pw-utils.c @@ -0,0 +1,164 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 Red Hat, Inc, + * + * 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: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include "pw-utils.h" + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pwquality.h> + + +static pwquality_settings_t * +get_pwq (void) +{ + static pwquality_settings_t *settings; + + if (settings == NULL) { + gchar *err = NULL; + settings = pwquality_default_settings (); + if (pwquality_read_config (settings, NULL, (gpointer)&err) < 0) { + g_error ("failed to read pwquality configuration: %s", err); + } + } + + return settings; +} + +gint +pw_min_length (void) +{ + gint value = 0; + + if (pwquality_get_int_value (get_pwq (), PWQ_SETTING_MIN_LENGTH, &value) < 0) { + g_error ("Failed to read pwquality setting\n" ); + } + + return value; +} + +gchar * +pw_generate (void) +{ + gchar *res; + gint rv; + + rv = pwquality_generate (get_pwq (), 0, &res); + + if (rv < 0) { + g_error ("Password generation failed: %s", + pwquality_strerror (NULL, 0, rv, NULL)); + return NULL; + } + + return res; +} + +static const gchar * +pw_error_hint (gint error) +{ + switch (error) { + case PWQ_ERROR_SAME_PASSWORD: + return C_("Password hint", "The new password needs to be different from the old one."); + case PWQ_ERROR_CASE_CHANGES_ONLY: + return C_("Password hint", "This password is very similar to your last one. Try changing some letters and numbers."); + case PWQ_ERROR_TOO_SIMILAR: + return C_("Password hint", "This password is very similar to your last one. Try changing the password a bit more."); + case PWQ_ERROR_USER_CHECK: + return C_("Password hint", "This is a weak password. A password without your user name would be stronger."); + case PWQ_ERROR_GECOS_CHECK: + return C_("Password hint", "This is a weak password. Try to avoid using your name in the password."); + case PWQ_ERROR_BAD_WORDS: + return C_("Password hint", "This is a weak password. Try to avoid some of the words included in the password."); + case PWQ_ERROR_ROTATED: + return C_("Password hint", "This password is very similar to your last one. Try changing the password a bit more."); + case PWQ_ERROR_CRACKLIB_CHECK: + return C_("Password hint", "This is a weak password. Try to avoid common words."); + case PWQ_ERROR_PALINDROME: + return C_("Password hint", "This is a weak password. Try to avoid reordering existing words."); + case PWQ_ERROR_MIN_DIGITS: + return C_("Password hint", "This is a weak password. Try to use more numbers."); + case PWQ_ERROR_MIN_UPPERS: + return C_("Password hint", "This is a weak password. Try to use more uppercase letters."); + case PWQ_ERROR_MIN_LOWERS: + return C_("Password hint", "This is a weak password. Try to use more lowercase letters."); + case PWQ_ERROR_MIN_OTHERS: + return C_("Password hint", "This is a weak password. Try to use more special characters, like punctuation."); + case PWQ_ERROR_MIN_CLASSES: + return C_("Password hint", "This is a weak password. Try to use a mixture of letters, numbers and punctuation."); + case PWQ_ERROR_MAX_CONSECUTIVE: + return C_("Password hint", "This is a weak password. Try to avoid repeating the same character."); + case PWQ_ERROR_MAX_CLASS_REPEAT: + return C_("Password hint", "This is a weak password. Try to avoid repeating the same type of character: you need to mix up letters, numbers and punctuation."); + case PWQ_ERROR_MAX_SEQUENCE: + return C_("Password hint", "This is a weak password. Try to avoid sequences like 1234 or abcd."); + case PWQ_ERROR_MIN_LENGTH: + return C_("Password hint", "This is a weak password. Try to add more letters, numbers and punctuation."); + case PWQ_ERROR_EMPTY_PASSWORD: + return C_("Password hint", "Mix uppercase and lowercase and try to use a number or two."); + default: + return C_("Password hint", "Adding more letters, numbers and punctuation will make the password stronger."); + } +} + +gdouble +pw_strength (const gchar *password, + const gchar *old_password, + const gchar *username, + const gchar **hint, + gint *strength_level) +{ + gint rv, level, length = 0; + gdouble strength = 0.0; + void *auxerror; + + rv = pwquality_check (get_pwq (), + password, old_password, username, + &auxerror); + + if (password != NULL) + length = strlen (password); + + strength = CLAMP (0.01 * rv, 0.0, 1.0); + if (rv < 0) { + level = (length > 0) ? 1 : 0; + } + else if (strength < 0.50) { + level = 2; + } else if (strength < 0.75) { + level = 3; + } else if (strength < 0.90) { + level = 4; + } else { + level = 5; + } + + if (length && length < pw_min_length()) + *hint = pw_error_hint (PWQ_ERROR_MIN_LENGTH); + else + *hint = pw_error_hint (rv); + + if (strength_level) + *strength_level = level; + + return strength; +} diff --git a/gnome-initial-setup/pages/password/pw-utils.h b/gnome-initial-setup/pages/password/pw-utils.h new file mode 100644 index 0000000..2a3cc42 --- /dev/null +++ b/gnome-initial-setup/pages/password/pw-utils.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 Red Hat, Inc, + * + * 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: Matthias Clasen <mclasen@redhat.com> + */ + +#include <glib.h> + +gint pw_min_length (void); +gchar *pw_generate (void); +gdouble pw_strength (const gchar *password, + const gchar *old_password, + const gchar *username, + const gchar **hint, + gint *strength_level); diff --git a/gnome-initial-setup/pages/privacy/gis-privacy-page.c b/gnome-initial-setup/pages/privacy/gis-privacy-page.c new file mode 100644 index 0000000..80c40c3 --- /dev/null +++ b/gnome-initial-setup/pages/privacy/gis-privacy-page.c @@ -0,0 +1,285 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2015 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: + * Matthias Clasen <mclasen@redhat.com> + */ + +/* Privacy page {{{1 */ + +#define PAGE_ID "privacy" + +#include "config.h" +#include "privacy-resources.h" +#include "gis-privacy-page.h" + +#include <webkit2/webkit2.h> + +#include <locale.h> +#include <gtk/gtk.h> + +#include "gis-page-header.h" + +struct _GisPrivacyPagePrivate +{ + GtkWidget *location_switch; + GtkWidget *reporting_row; + GtkWidget *reporting_switch; + GtkWidget *reporting_label; + GtkWidget *mozilla_privacy_policy_label; + GtkWidget *distro_privacy_policy_label; + GSettings *location_settings; + GSettings *privacy_settings; + guint abrt_watch_id; +}; +typedef struct _GisPrivacyPagePrivate GisPrivacyPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisPrivacyPage, gis_privacy_page, GIS_TYPE_PAGE); + +static void +update_os_data (GisPrivacyPage *page) +{ + GisPrivacyPagePrivate *priv = gis_privacy_page_get_instance_private (page); + g_autofree char *name = g_get_os_info (G_OS_INFO_KEY_NAME); + g_autofree char *privacy_policy = g_get_os_info (G_OS_INFO_KEY_PRIVACY_POLICY_URL); + char *text; + + if (!name) + name = g_strdup ("GNOME"); + + /* Translators: the parameter here is the name of a distribution, + * like "Fedora" or "Ubuntu". It falls back to "GNOME" if we can't + * detect any distribution. + */ + text = g_strdup_printf (_("Sending reports of technical problems helps us to improve %s. Reports are sent anonymously and are scrubbed of personal data."), name); + gtk_label_set_label (GTK_LABEL (priv->reporting_label), text); + g_free (text); + + if (privacy_policy) + { + /* Translators: the parameter here is the name of a distribution, + * like "Fedora" or "Ubuntu". It falls back to "GNOME" if we can't + * detect any distribution. + */ + g_autofree char *distro_label = g_strdup_printf (_("Problem data will be collected by %s:"), name); + text = g_strdup_printf ("%s <a href='%s'>%s</a>", distro_label, privacy_policy, _("Privacy Policy")); + gtk_label_set_markup (GTK_LABEL (priv->distro_privacy_policy_label), text); + g_free (text); + } + else + { + gtk_widget_hide (priv->distro_privacy_policy_label); + } +} + +static void +abrt_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + GisPrivacyPage *page = user_data; + GisPrivacyPagePrivate *priv = gis_privacy_page_get_instance_private (page); + + gtk_widget_show (priv->reporting_row); + gtk_widget_show (priv->reporting_label); + gtk_widget_show (priv->distro_privacy_policy_label); +} + +static void +abrt_vanished_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GisPrivacyPage *page = user_data; + GisPrivacyPagePrivate *priv = gis_privacy_page_get_instance_private (page); + + gtk_widget_hide (priv->reporting_row); + gtk_widget_hide (priv->reporting_label); + gtk_widget_hide (priv->distro_privacy_policy_label); +} + +static void +gis_privacy_page_constructed (GObject *object) +{ + GisPrivacyPage *page = GIS_PRIVACY_PAGE (object); + GisPrivacyPagePrivate *priv = gis_privacy_page_get_instance_private (page); + char *text; + + G_OBJECT_CLASS (gis_privacy_page_parent_class)->constructed (object); + + gis_page_set_complete (GIS_PAGE (page), TRUE); + + priv->location_settings = g_settings_new ("org.gnome.system.location"); + priv->privacy_settings = g_settings_new ("org.gnome.desktop.privacy"); + + gtk_switch_set_active (GTK_SWITCH (priv->location_switch), TRUE); + gtk_switch_set_active (GTK_SWITCH (priv->reporting_switch), TRUE); + + update_os_data (page); + + text = g_strdup_printf ("%s <a href='%s'>%s</a>", _("Uses Mozilla Location Service:"), "https://location.services.mozilla.com/privacy", _("Privacy Policy")); + gtk_label_set_markup (GTK_LABEL (priv->mozilla_privacy_policy_label), text); + g_free (text); + + priv->abrt_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, + "org.freedesktop.problems.daemon", + G_BUS_NAME_WATCHER_FLAGS_NONE, + abrt_appeared_cb, + abrt_vanished_cb, + page, + NULL); +} + +static void +gis_privacy_page_dispose (GObject *object) +{ + GisPrivacyPage *page = GIS_PRIVACY_PAGE (object); + GisPrivacyPagePrivate *priv = gis_privacy_page_get_instance_private (page); + + g_clear_object (&priv->location_settings); + g_clear_object (&priv->privacy_settings); + + if (priv->abrt_watch_id > 0) + { + g_bus_unwatch_name (priv->abrt_watch_id); + priv->abrt_watch_id = 0; + } + + G_OBJECT_CLASS (gis_privacy_page_parent_class)->dispose (object); +} + +static gboolean +gis_privacy_page_apply (GisPage *gis_page, + GCancellable *cancellable) +{ + GisPrivacyPage *page = GIS_PRIVACY_PAGE (gis_page); + GisPrivacyPagePrivate *priv = gis_privacy_page_get_instance_private (page); + gboolean active; + + active = gtk_switch_get_active (GTK_SWITCH (priv->location_switch)); + g_settings_set_boolean (priv->location_settings, "enabled", active); + + active = gtk_switch_get_active (GTK_SWITCH (priv->reporting_switch)); + g_settings_set_boolean (priv->privacy_settings, "report-technical-problems", active); + + return FALSE; +} + +static void +notify_progress_cb (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + GtkWidget *progress_bar = user_data; + WebKitWebView *web_view = WEBKIT_WEB_VIEW (object); + gdouble progress; + + progress = webkit_web_view_get_estimated_load_progress (web_view); + + if (progress == 1.0) + gtk_widget_hide (progress_bar); + else + gtk_widget_show (progress_bar); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress_bar), progress); +} + +static gboolean +activate_link (GtkLabel *label, + const gchar *uri, + GisPrivacyPage *page) +{ + GtkWidget *dialog; + GtkWidget *overlay; + GtkWidget *view; + GtkWidget *progress_bar; + + dialog = gtk_dialog_new_with_buttons (_("Privacy Policy"), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), + GTK_DIALOG_MODAL + | GTK_DIALOG_DESTROY_WITH_PARENT + | GTK_DIALOG_USE_HEADER_BAR, + NULL, NULL); + + overlay = gtk_overlay_new (); + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), overlay); + + progress_bar = gtk_progress_bar_new (); + gtk_style_context_add_class (gtk_widget_get_style_context (progress_bar), GTK_STYLE_CLASS_OSD); + gtk_widget_set_halign (progress_bar, GTK_ALIGN_FILL); + gtk_widget_set_valign (progress_bar, GTK_ALIGN_START); + gtk_overlay_add_overlay (GTK_OVERLAY (overlay), progress_bar); + + view = webkit_web_view_new (); + gtk_widget_set_size_request (view, 600, 500); + gtk_widget_set_hexpand (view, TRUE); + gtk_widget_set_vexpand (view, TRUE); + g_signal_connect (view, "notify::estimated-load-progress", + G_CALLBACK (notify_progress_cb), progress_bar); + + gtk_container_add (GTK_CONTAINER (overlay), view); + gtk_widget_show_all (overlay); + + gtk_window_present (GTK_WINDOW (dialog)); + + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), uri); + + return TRUE; +} + +static void +gis_privacy_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("Privacy")); +} + +static void +gis_privacy_page_class_init (GisPrivacyPageClass *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-privacy-page.ui"); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPrivacyPage, location_switch); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPrivacyPage, reporting_row); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPrivacyPage, reporting_switch); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPrivacyPage, reporting_label); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPrivacyPage, mozilla_privacy_policy_label); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisPrivacyPage, distro_privacy_policy_label); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), activate_link); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_privacy_page_locale_changed; + page_class->apply = gis_privacy_page_apply; + object_class->constructed = gis_privacy_page_constructed; + object_class->dispose = gis_privacy_page_dispose; +} + +static void +gis_privacy_page_init (GisPrivacyPage *page) +{ + g_resources_register (privacy_get_resource ()); + g_type_ensure (GIS_TYPE_PAGE_HEADER); + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_privacy_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_PRIVACY_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/privacy/gis-privacy-page.h b/gnome-initial-setup/pages/privacy/gis-privacy-page.h new file mode 100644 index 0000000..9596b36 --- /dev/null +++ b/gnome-initial-setup/pages/privacy/gis-privacy-page.h @@ -0,0 +1,57 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2015 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: + * Matthias Clasen <mclasen@redhat.com> + */ + +#ifndef __GIS_PRIVACY_PAGE_H__ +#define __GIS_PRIVACY_PAGE_H__ + +#include <glib-object.h> + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_PRIVACY_PAGE (gis_privacy_page_get_type ()) +#define GIS_PRIVACY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_PRIVACY_PAGE, GisPrivacyPage)) +#define GIS_PRIVACY_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_PRIVACY_PAGE, GisPrivacyPageClass)) +#define GIS_IS_PRIVACY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_PRIVACY_PAGE)) +#define GIS_IS_PRIVACY_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_PRIVACY_PAGE)) +#define GIS_PRIVACY_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_PRIVACY_PAGE, GisPrivacyPageClass)) + +typedef struct _GisPrivacyPage GisPrivacyPage; +typedef struct _GisPrivacyPageClass GisPrivacyPageClass; + +struct _GisPrivacyPage +{ + GisPage parent; +}; + +struct _GisPrivacyPageClass +{ + GisPageClass parent_class; +}; + +GType gis_privacy_page_get_type (void); + +GisPage *gis_prepare_privacy_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_PRIVACY_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/privacy/gis-privacy-page.ui b/gnome-initial-setup/pages/privacy/gis-privacy-page.ui new file mode 100644 index 0000000..1afde98 --- /dev/null +++ b/gnome-initial-setup/pages/privacy/gis-privacy-page.ui @@ -0,0 +1,128 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="3.0"/> + <template class="GisPrivacyPage" parent="GisPage"> + <property name="visible">True</property> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <property name="orientation">vertical</property> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin_top">24</property> + <property name="title" translatable="yes">Privacy</property> + <property name="icon_name">preferences-system-privacy-symbolic</property> + <property name="show_icon" bind-source="GisPrivacyPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="margin-top">40</property> + <property name="orientation">horizontal</property> + <property name="homogeneous">True</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Location Services</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkSwitch" id="location_switch"> + <property name="halign">end</property> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="margin-top">10</property> + <property name="xalign">0</property> + <property name="max-width-chars">50</property> + <property name="wrap">True</property> + <property name="label" translatable="yes">Allows applications to determine your geographical location. An indication is shown when location services are in use.</property> + </object> + </child> + <child> + <object class="GtkLabel" id="mozilla_privacy_policy_label"> + <property name="visible">True</property> + <property name="margin-top">10</property> + <property name="xalign">0</property> + <property name="halign">start</property> + <property name="use-markup">True</property> + <signal name="activate-link" handler="activate_link"/> + </object> + </child> + <child> + <object class="GtkBox" id="reporting_row"> + <property name="margin-top">20</property> + <property name="orientation">horizontal</property> + <property name="homogeneous">True</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Automatic Problem Reporting</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkSwitch" id="reporting_switch"> + <property name="visible">True</property> + <property name="halign">end</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkLabel" id="reporting_label"> + <property name="margin-top">10</property> + <property name="xalign">0</property> + <property name="max-width-chars">50</property> + <property name="wrap">True</property> + </object> + </child> + <child> + <object class="GtkLabel" id="distro_privacy_policy_label"> + <property name="visible">True</property> + <property name="margin-top">10</property> + <property name="xalign">0</property> + <property name="halign">start</property> + <property name="use-markup">True</property> + <signal name="activate-link" handler="activate_link"/> + </object> + </child> + <child> + <object class="GtkLabel" id="footer_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Privacy controls can be changed at any time from the Settings application.</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <property name="margin_bottom">18</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">4</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/privacy/meson.build b/gnome-initial-setup/pages/privacy/meson.build new file mode 100644 index 0000000..560831c --- /dev/null +++ b/gnome-initial-setup/pages/privacy/meson.build @@ -0,0 +1,10 @@ +sources += gnome.compile_resources( + 'privacy-resources', + files('privacy.gresource.xml'), + c_name: 'privacy' +) + +sources += files( + 'gis-privacy-page.c', + 'gis-privacy-page.h' +) diff --git a/gnome-initial-setup/pages/privacy/privacy.gresource.xml b/gnome-initial-setup/pages/privacy/privacy.gresource.xml new file mode 100644 index 0000000..be75b34 --- /dev/null +++ b/gnome-initial-setup/pages/privacy/privacy.gresource.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks" alias="gis-privacy-page.ui">gis-privacy-page.ui</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/summary/gis-summary-page.c b/gnome-initial-setup/pages/summary/gis-summary-page.c new file mode 100644 index 0000000..106cf48 --- /dev/null +++ b/gnome-initial-setup/pages/summary/gis-summary-page.c @@ -0,0 +1,300 @@ +/* -*- 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> + */ + +/* Summary page {{{1 */ + +#define PAGE_ID "summary" + +#include "config.h" +#include "summary-resources.h" +#include "gis-summary-page.h" + +#include <glib/gstdio.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <stdlib.h> +#include <errno.h> + +#include <act/act-user-manager.h> + +#define SERVICE_NAME "gdm-password" + +struct _GisSummaryPagePrivate { + GtkWidget *start_button; + GtkWidget *start_button_label; + GtkWidget *tagline; + + ActUser *user_account; + const gchar *user_password; +}; +typedef struct _GisSummaryPagePrivate GisSummaryPagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisSummaryPage, gis_summary_page, GIS_TYPE_PAGE); + +static void +request_info_query (GisSummaryPage *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, + GisSummaryPage *page) +{ + g_debug ("PAM module info: %s", info); +} + +static void +on_problem (GdmUserVerifier *user_verifier, + const char *service_name, + const char *problem, + GisSummaryPage *page) +{ + g_warning ("PAM module error: %s", problem); +} + +static void +on_info_query (GdmUserVerifier *user_verifier, + const char *service_name, + const char *question, + GisSummaryPage *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, + GisSummaryPage *page) +{ + GisSummaryPagePrivate *priv = gis_summary_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, + GisSummaryPage *page) +{ + gdm_greeter_call_start_session_when_ready_sync (greeter, service_name, + TRUE, NULL, NULL); +} + +static void +add_uid_file (uid_t uid) +{ + gchar *gis_uid_path; + gchar *uid_str; + g_autoptr(GError) error = NULL; + + gis_uid_path = g_build_filename (g_get_home_dir (), + "gnome-initial-setup-uid", + NULL); + uid_str = g_strdup_printf ("%u", uid); + + if (!g_file_set_contents (gis_uid_path, uid_str, -1, &error)) + g_warning ("Unable to create %s: %s", gis_uid_path, error->message); + + g_free (uid_str); + g_free (gis_uid_path); +} + +static void +log_user_in (GisSummaryPage *page) +{ + GisSummaryPagePrivate *priv = gis_summary_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); + + /* 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)); + + 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 +done_cb (GtkButton *button, GisSummaryPage *page) +{ + gis_ensure_stamp_files (GIS_PAGE (page)->driver); + + switch (gis_driver_get_mode (GIS_PAGE (page)->driver)) + { + case GIS_DRIVER_MODE_NEW_USER: + gis_driver_hide_window (GIS_PAGE (page)->driver); + log_user_in (page); + break; + case GIS_DRIVER_MODE_EXISTING_USER: + g_application_quit (G_APPLICATION (GIS_PAGE (page)->driver)); + default: + break; + } +} + +static void +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; + + 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); + + gtk_widget_grab_focus (priv->start_button); +} + +static void +update_distro_name (GisSummaryPage *page) +{ + GisSummaryPagePrivate *priv = gis_summary_page_get_instance_private (page); + g_autofree char *name = g_get_os_info (G_OS_INFO_KEY_NAME); + char *text; + + if (!name) + name = g_strdup ("GNOME 3"); + + /* Translators: the parameter here is the name of a distribution, + * like "Fedora" or "Ubuntu". It falls back to "GNOME 3" if we can't + * detect any distribution. */ + text = g_strdup_printf (_("_Start Using %s"), name); + gtk_label_set_label (GTK_LABEL (priv->start_button_label), text); + g_free (text); + + /* Translators: the parameter here is the name of a distribution, + * like "Fedora" or "Ubuntu". It falls back to "GNOME 3" if we can't + * detect any distribution. */ + text = g_strdup_printf (_("%s is ready to be used. We hope that you love it!"), name); + gtk_label_set_label (GTK_LABEL (priv->tagline), text); + g_free (text); +} + +static void +gis_summary_page_constructed (GObject *object) +{ + GisSummaryPage *page = GIS_SUMMARY_PAGE (object); + GisSummaryPagePrivate *priv = gis_summary_page_get_instance_private (page); + + G_OBJECT_CLASS (gis_summary_page_parent_class)->constructed (object); + + update_distro_name (page); + g_signal_connect (priv->start_button, "clicked", G_CALLBACK (done_cb), page); + + gis_page_set_complete (GIS_PAGE (page), TRUE); + + gtk_widget_show (GTK_WIDGET (page)); +} + +static void +gis_summary_page_locale_changed (GisPage *page) +{ + gis_page_set_title (page, _("Setup Complete")); + update_distro_name (GIS_SUMMARY_PAGE (page)); +} + +static void +gis_summary_page_class_init (GisSummaryPageClass *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-summary-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisSummaryPage, start_button); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisSummaryPage, start_button_label); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisSummaryPage, tagline); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_summary_page_locale_changed; + page_class->shown = gis_summary_page_shown; + object_class->constructed = gis_summary_page_constructed; +} + +static void +gis_summary_page_init (GisSummaryPage *page) +{ + g_resources_register (summary_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_summary_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_SUMMARY_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/summary/gis-summary-page.h b/gnome-initial-setup/pages/summary/gis-summary-page.h new file mode 100644 index 0000000..20190f1 --- /dev/null +++ b/gnome-initial-setup/pages/summary/gis-summary-page.h @@ -0,0 +1,55 @@ +/* -*- 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> + */ + +#ifndef __GIS_SUMMARY_PAGE_H__ +#define __GIS_SUMMARY_PAGE_H__ + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_SUMMARY_PAGE (gis_summary_page_get_type ()) +#define GIS_SUMMARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_SUMMARY_PAGE, GisSummaryPage)) +#define GIS_SUMMARY_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_SUMMARY_PAGE, GisSummaryPageClass)) +#define GIS_IS_SUMMARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_SUMMARY_PAGE)) +#define GIS_IS_SUMMARY_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_SUMMARY_PAGE)) +#define GIS_SUMMARY_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_SUMMARY_PAGE, GisSummaryPageClass)) + +typedef struct _GisSummaryPage GisSummaryPage; +typedef struct _GisSummaryPageClass GisSummaryPageClass; + +struct _GisSummaryPage +{ + GisPage parent; +}; + +struct _GisSummaryPageClass +{ + GisPageClass parent_class; +}; + +GType gis_summary_page_get_type (void); + +GisPage *gis_prepare_summary_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_SUMMARY_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/summary/gis-summary-page.ui b/gnome-initial-setup/pages/summary/gis-summary-page.ui new file mode 100644 index 0000000..9f9c5eb --- /dev/null +++ b/gnome-initial-setup/pages/summary/gis-summary-page.ui @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisSummaryPage" parent="GisPage"> + <child> + <object class="GtkFrame" id="bg_frame"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow-type">none</property> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">16</property> + <child type="center"> + <object class="GtkImage" id="image"> + <property name="visible">True</property> + <property name="resource">/org/gnome/initial-setup/ready-to-go.svg</property> + <property name="width_request">128</property> + <property name="height_request">128</property> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkButton" id="start_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="halign">center</property> + <style> + <class name="suggested-action"/> + </style> + <child> + <object class="GtkLabel" id="start_button_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <property name="xpad">8</property> + <property name="ypad">8</property> + <property name="use_underline">True</property> + </object> + </child> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="tagline"> + <property name="visible">True</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <property name="max-width-chars">60</property> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="title"> + <property name="visible">True</property> + <property name="justify">center</property> + <property name="label" translatable="yes">All done!</property> + <property name="wrap">True</property> + <style> + <class name="large-title"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/summary/meson.build b/gnome-initial-setup/pages/summary/meson.build new file mode 100644 index 0000000..4b203dc --- /dev/null +++ b/gnome-initial-setup/pages/summary/meson.build @@ -0,0 +1,10 @@ +sources += gnome.compile_resources( + 'summary-resources', + files('summary.gresource.xml'), + c_name: 'summary' +) + +sources += files( + 'gis-summary-page.c', + 'gis-summary-page.h' +) diff --git a/gnome-initial-setup/pages/summary/ready-to-go.svg b/gnome-initial-setup/pages/summary/ready-to-go.svg new file mode 100644 index 0000000..9ad6af4 --- /dev/null +++ b/gnome-initial-setup/pages/summary/ready-to-go.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" version="1.0" enable-background="new"><defs><linearGradient id="d"><stop offset="0" stop-color="#26a269"/><stop offset=".143" stop-color="#75dfae"/><stop offset=".332" stop-color="#26a269"/><stop offset=".869" stop-color="#2cbb79"/><stop offset="1" stop-color="#1c774d"/></linearGradient><linearGradient id="a"><stop offset="0" stop-color="#d5d3cf"/><stop offset="1" stop-color="#f6f5f4"/></linearGradient><linearGradient id="b"><stop offset="0" stop-color="#d5d3cf"/><stop offset="1" stop-color="#949390"/></linearGradient><linearGradient id="c"><stop offset="0" stop-color="#9a9996"/><stop offset="1" stop-color="#77767b"/></linearGradient><linearGradient xlink:href="#d" id="e" x1="13.25" y1="272" x2="118" y2="272" gradientUnits="userSpaceOnUse"/><filter id="g" color-interpolation-filters="sRGB"><feBlend mode="multiply" in2="BackgroundImage"/></filter><filter id="f" color-interpolation-filters="sRGB"><feBlend mode="multiply" in2="BackgroundImage"/></filter></defs><g transform="translate(0 -172)"><ellipse cy="237.761" cx="64" style="marker:none" rx="57.933" ry="56.383" fill="url(#e)"/><ellipse style="marker:none" cx="64" cy="233.14" rx="58.125" ry="57.269" fill="#2ec27e"/><path style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1;marker:none" d="M111.592 192.047a8 8 0 00-5.498 2.422L60.563 240 45.28 224.719A8 8 0 1033.97 236.03l21.515 21.517c2.28 2.387 7.51 2.648 10.095.062l51.827-51.829c5.234-5.087 1.481-13.951-5.814-13.734z" color="#000" font-weight="400" font-family="sans-serif" overflow="visible" fill="#fff" enable-background="accumulate"/><path d="M-125.625 230.375l20.938 20.938 51.187-51.188" style="marker:none" fill="none" stroke="#a347ba" stroke-width="16" stroke-linecap="round"/><path style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1;marker:none" d="M105.5 193.063L60.562 238l-15.28-15.281a8 8 0 00-13.704 6.572 8 8 0 0113.703-4.572L60.563 240l45.53-45.531a8 8 0 01.382-.354 58.125 57.269 0 00-.975-1.053z" color="#000" font-weight="400" font-family="sans-serif" overflow="visible" fill="#26a269" enable-background="accumulate"/><path style="marker:none" d="M-125.625 310.375l20.938 20.938 51.187-51.188" fill="none" stroke="#a347ba" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/></g></svg>
\ No newline at end of file diff --git a/gnome-initial-setup/pages/summary/summary.gresource.xml b/gnome-initial-setup/pages/summary/summary.gresource.xml new file mode 100644 index 0000000..75ed09e --- /dev/null +++ b/gnome-initial-setup/pages/summary/summary.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-summary-page.ui">gis-summary-page.ui</file> + <file alias="ready-to-go.svg">ready-to-go.svg</file> + </gresource> +</gresources> + diff --git a/gnome-initial-setup/pages/timezone/backward b/gnome-initial-setup/pages/timezone/backward new file mode 100644 index 0000000..8594be6 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/backward @@ -0,0 +1,128 @@ +# This file is in the public domain, so clarified as of +# 2009-05-17 by Arthur David Olson. + +# This file provides links between current names for time zones +# and their old names. Many names changed in late 1993. + +# Link TARGET LINK-NAME +Link Africa/Nairobi Africa/Asmera +Link Africa/Abidjan Africa/Timbuktu +Link America/Argentina/Catamarca America/Argentina/ComodRivadavia +Link America/Adak America/Atka +Link America/Argentina/Buenos_Aires America/Buenos_Aires +Link America/Argentina/Catamarca America/Catamarca +Link America/Atikokan America/Coral_Harbour +Link America/Argentina/Cordoba America/Cordoba +Link America/Tijuana America/Ensenada +Link America/Indiana/Indianapolis America/Fort_Wayne +Link America/Indiana/Indianapolis America/Indianapolis +Link America/Argentina/Jujuy America/Jujuy +Link America/Indiana/Knox America/Knox_IN +Link America/Kentucky/Louisville America/Louisville +Link America/Argentina/Mendoza America/Mendoza +Link America/Toronto America/Montreal +Link America/Rio_Branco America/Porto_Acre +Link America/Argentina/Cordoba America/Rosario +Link America/Tijuana America/Santa_Isabel +Link America/Denver America/Shiprock +Link America/Port_of_Spain America/Virgin +Link Pacific/Auckland Antarctica/South_Pole +Link Asia/Ashgabat Asia/Ashkhabad +Link Asia/Kolkata Asia/Calcutta +Link Asia/Shanghai Asia/Chongqing +Link Asia/Shanghai Asia/Chungking +Link Asia/Dhaka Asia/Dacca +Link Asia/Shanghai Asia/Harbin +Link Asia/Urumqi Asia/Kashgar +Link Asia/Kathmandu Asia/Katmandu +Link Asia/Macau Asia/Macao +Link Asia/Yangon Asia/Rangoon +Link Asia/Ho_Chi_Minh Asia/Saigon +Link Asia/Jerusalem Asia/Tel_Aviv +Link Asia/Thimphu Asia/Thimbu +Link Asia/Makassar Asia/Ujung_Pandang +Link Asia/Ulaanbaatar Asia/Ulan_Bator +Link Atlantic/Faroe Atlantic/Faeroe +Link Europe/Oslo Atlantic/Jan_Mayen +Link Australia/Sydney Australia/ACT +Link Australia/Sydney Australia/Canberra +Link Australia/Lord_Howe Australia/LHI +Link Australia/Sydney Australia/NSW +Link Australia/Darwin Australia/North +Link Australia/Brisbane Australia/Queensland +Link Australia/Adelaide Australia/South +Link Australia/Hobart Australia/Tasmania +Link Australia/Melbourne Australia/Victoria +Link Australia/Perth Australia/West +Link Australia/Broken_Hill Australia/Yancowinna +Link America/Rio_Branco Brazil/Acre +Link America/Noronha Brazil/DeNoronha +Link America/Sao_Paulo Brazil/East +Link America/Manaus Brazil/West +Link America/Halifax Canada/Atlantic +Link America/Winnipeg Canada/Central +# This line is commented out, as the name exceeded the 14-character limit +# and was an unused misnomer. +#Link America/Regina Canada/East-Saskatchewan +Link America/Toronto Canada/Eastern +Link America/Edmonton Canada/Mountain +Link America/St_Johns Canada/Newfoundland +Link America/Vancouver Canada/Pacific +Link America/Regina Canada/Saskatchewan +Link America/Whitehorse Canada/Yukon +Link America/Santiago Chile/Continental +Link Pacific/Easter Chile/EasterIsland +Link America/Havana Cuba +Link Africa/Cairo Egypt +Link Europe/Dublin Eire +Link Europe/London Europe/Belfast +Link Europe/Chisinau Europe/Tiraspol +Link Europe/London GB +Link Europe/London GB-Eire +Link Etc/GMT GMT+0 +Link Etc/GMT GMT-0 +Link Etc/GMT GMT0 +Link Etc/GMT Greenwich +Link Asia/Hong_Kong Hongkong +Link Atlantic/Reykjavik Iceland +Link Asia/Tehran Iran +Link Asia/Jerusalem Israel +Link America/Jamaica Jamaica +Link Asia/Tokyo Japan +Link Pacific/Kwajalein Kwajalein +Link Africa/Tripoli Libya +Link America/Tijuana Mexico/BajaNorte +Link America/Mazatlan Mexico/BajaSur +Link America/Mexico_City Mexico/General +Link Pacific/Auckland NZ +Link Pacific/Chatham NZ-CHAT +Link America/Denver Navajo +Link Asia/Shanghai PRC +Link Pacific/Honolulu Pacific/Johnston +Link Pacific/Pohnpei Pacific/Ponape +Link Pacific/Pago_Pago Pacific/Samoa +Link Pacific/Chuuk Pacific/Truk +Link Pacific/Chuuk Pacific/Yap +Link Europe/Warsaw Poland +Link Europe/Lisbon Portugal +Link Asia/Taipei ROC +Link Asia/Seoul ROK +Link Asia/Singapore Singapore +Link Europe/Istanbul Turkey +Link Etc/UCT UCT +Link America/Anchorage US/Alaska +Link America/Adak US/Aleutian +Link America/Phoenix US/Arizona +Link America/Chicago US/Central +Link America/Indiana/Indianapolis US/East-Indiana +Link America/New_York US/Eastern +Link Pacific/Honolulu US/Hawaii +Link America/Indiana/Knox US/Indiana-Starke +Link America/Detroit US/Michigan +Link America/Denver US/Mountain +Link America/Los_Angeles US/Pacific +Link Pacific/Pago_Pago US/Samoa +Link Etc/UTC UTC +Link Etc/UTC Universal +Link Europe/Moscow W-SU +Link Etc/UTC Zulu
\ No newline at end of file diff --git a/gnome-initial-setup/pages/timezone/cc-timezone-map.c b/gnome-initial-setup/pages/timezone/cc-timezone-map.c new file mode 100644 index 0000000..3c813a6 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/cc-timezone-map.c @@ -0,0 +1,691 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * Portions from Ubiquity, Copyright (C) 2009 Canonical Ltd. + * Written by Evan Dandrea <evand@ubuntu.com> + * + * 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/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#include "cc-timezone-map.h" +#include <math.h> +#include <string.h> +#include "tz.h" + +#define PIN_HOT_POINT_X 8 +#define PIN_HOT_POINT_Y 15 + +#define DATETIME_RESOURCE_PATH "/org/gnome/control-center/datetime" + +typedef struct +{ + gdouble offset; + guchar red; + guchar green; + guchar blue; + guchar alpha; +} CcTimezoneMapOffset; + +struct _CcTimezoneMap +{ + GtkWidget parent_instance; + + GdkPixbuf *orig_background; + GdkPixbuf *orig_background_dim; + GdkPixbuf *orig_color_map; + + GdkPixbuf *background; + GdkPixbuf *color_map; + GdkPixbuf *pin; + + guchar *visible_map_pixels; + gint visible_map_rowstride; + + gdouble selected_offset; + + TzDB *tzdb; + TzLocation *location; + + gchar *bubble_text; +}; + +G_DEFINE_TYPE (CcTimezoneMap, cc_timezone_map, GTK_TYPE_WIDGET) + +enum +{ + LOCATION_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + + +static CcTimezoneMapOffset color_codes[] = +{ + {-11.0, 43, 0, 0, 255 }, + {-10.0, 85, 0, 0, 255 }, + {-9.5, 102, 255, 0, 255 }, + {-9.0, 128, 0, 0, 255 }, + {-8.0, 170, 0, 0, 255 }, + {-7.0, 212, 0, 0, 255 }, + {-6.0, 255, 0, 1, 255 }, // north + {-6.0, 255, 0, 0, 255 }, // south + {-5.0, 255, 42, 42, 255 }, + {-4.5, 192, 255, 0, 255 }, + {-4.0, 255, 85, 85, 255 }, + {-3.5, 0, 255, 0, 255 }, + {-3.0, 255, 128, 128, 255 }, + {-2.0, 255, 170, 170, 255 }, + {-1.0, 255, 213, 213, 255 }, + {0.0, 43, 17, 0, 255 }, + {1.0, 85, 34, 0, 255 }, + {2.0, 128, 51, 0, 255 }, + {3.0, 170, 68, 0, 255 }, + {3.5, 0, 255, 102, 255 }, + {4.0, 212, 85, 0, 255 }, + {4.5, 0, 204, 255, 255 }, + {5.0, 255, 102, 0, 255 }, + {5.5, 0, 102, 255, 255 }, + {5.75, 0, 238, 207, 247 }, + {6.0, 255, 127, 42, 255 }, + {6.5, 204, 0, 254, 254 }, + {7.0, 255, 153, 85, 255 }, + {8.0, 255, 179, 128, 255 }, + {9.0, 255, 204, 170, 255 }, + {9.5, 170, 0, 68, 250 }, + {10.0, 255, 230, 213, 255 }, + {10.5, 212, 124, 21, 250 }, + {11.0, 212, 170, 0, 255 }, + {11.5, 249, 25, 87, 253 }, + {12.0, 255, 204, 0, 255 }, + {12.75, 254, 74, 100, 248 }, + {13.0, 255, 85, 153, 250 }, + {-100, 0, 0, 0, 0 } +}; + + +static void +cc_timezone_map_dispose (GObject *object) +{ + CcTimezoneMap *self = CC_TIMEZONE_MAP (object); + + g_clear_object (&self->orig_background); + g_clear_object (&self->orig_background_dim); + g_clear_object (&self->orig_color_map); + g_clear_object (&self->background); + g_clear_object (&self->pin); + g_clear_pointer (&self->bubble_text, g_free); + + if (self->color_map) + { + g_clear_object (&self->color_map); + + self->visible_map_pixels = NULL; + self->visible_map_rowstride = 0; + } + + G_OBJECT_CLASS (cc_timezone_map_parent_class)->dispose (object); +} + +static void +cc_timezone_map_finalize (GObject *object) +{ + CcTimezoneMap *self = CC_TIMEZONE_MAP (object); + + g_clear_pointer (&self->tzdb, tz_db_free); + + G_OBJECT_CLASS (cc_timezone_map_parent_class)->finalize (object); +} + +/* GtkWidget functions */ +static void +cc_timezone_map_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + CcTimezoneMap *map = CC_TIMEZONE_MAP (widget); + gint size; + + size = gdk_pixbuf_get_width (map->orig_background); + + if (minimum != NULL) + *minimum = size; + if (natural != NULL) + *natural = size; +} + +static void +cc_timezone_map_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + CcTimezoneMap *map = CC_TIMEZONE_MAP (widget); + gint size; + + size = gdk_pixbuf_get_height (map->orig_background); + + if (minimum != NULL) + *minimum = size; + if (natural != NULL) + *natural = size; +} + +static void +cc_timezone_map_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + CcTimezoneMap *map = CC_TIMEZONE_MAP (widget); + GdkPixbuf *pixbuf; + + if (map->background) + g_object_unref (map->background); + + if (!gtk_widget_is_sensitive (widget)) + pixbuf = map->orig_background_dim; + else + pixbuf = map->orig_background; + + map->background = gdk_pixbuf_scale_simple (pixbuf, + allocation->width, + allocation->height, + GDK_INTERP_BILINEAR); + + if (map->color_map) + g_object_unref (map->color_map); + + map->color_map = gdk_pixbuf_scale_simple (map->orig_color_map, + allocation->width, + allocation->height, + GDK_INTERP_BILINEAR); + + map->visible_map_pixels = gdk_pixbuf_get_pixels (map->color_map); + map->visible_map_rowstride = gdk_pixbuf_get_rowstride (map->color_map); + + GTK_WIDGET_CLASS (cc_timezone_map_parent_class)->size_allocate (widget, + allocation); +} + +static void +cc_timezone_map_realize (GtkWidget *widget) +{ + GdkWindowAttr attr = { 0, }; + GtkAllocation allocation; + GdkWindow *window; + + gtk_widget_get_allocation (widget, &allocation); + + gtk_widget_set_realized (widget, TRUE); + + attr.window_type = GDK_WINDOW_CHILD; + attr.wclass = GDK_INPUT_OUTPUT; + attr.width = allocation.width; + attr.height = allocation.height; + attr.x = allocation.x; + attr.y = allocation.y; + attr.event_mask = gtk_widget_get_events (widget) + | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK; + + window = gdk_window_new (gtk_widget_get_parent_window (widget), &attr, + GDK_WA_X | GDK_WA_Y); + + gdk_window_set_user_data (window, widget); + gtk_widget_set_window (widget, window); +} + + +static gdouble +convert_longitude_to_x (gdouble longitude, gint map_width) +{ + const gdouble xdeg_offset = -6; + gdouble x; + + x = (map_width * (180.0 + longitude) / 360.0) + + (map_width * xdeg_offset / 180.0); + + return x; +} + +static gdouble +radians (gdouble degrees) +{ + return (degrees / 360.0) * G_PI * 2; +} + +static gdouble +convert_latitude_to_y (gdouble latitude, gdouble map_height) +{ + gdouble bottom_lat = -59; + gdouble top_lat = 81; + gdouble top_per, y, full_range, top_offset, map_range; + + top_per = top_lat / 180.0; + y = 1.25 * log (tan (G_PI_4 + 0.4 * radians (latitude))); + full_range = 4.6068250867599998; + top_offset = full_range * top_per; + map_range = fabs (1.25 * log (tan (G_PI_4 + 0.4 * radians (bottom_lat))) - top_offset); + y = fabs (y - top_offset); + y = y / map_range; + y = y * map_height; + return y; +} + +static void +draw_text_bubble (cairo_t *cr, + GtkWidget *widget, + gdouble pointx, + gdouble pointy) +{ + static const double corner_radius = 9.0; + static const double margin_top = 12.0; + static const double margin_bottom = 12.0; + static const double margin_left = 24.0; + static const double margin_right = 24.0; + + CcTimezoneMap *map = CC_TIMEZONE_MAP (widget); + GtkAllocation alloc; + PangoLayout *layout; + PangoRectangle text_rect; + double x; + double y; + double width; + double height; + + if (!map->bubble_text) + return; + + gtk_widget_get_allocation (widget, &alloc); + layout = gtk_widget_create_pango_layout (widget, NULL); + + /* Layout the text */ + pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); + pango_layout_set_spacing (layout, 3); + pango_layout_set_markup (layout, map->bubble_text, -1); + + pango_layout_get_pixel_extents (layout, NULL, &text_rect); + + /* Calculate the bubble size based on the text layout size */ + width = text_rect.width + margin_left + margin_right; + height = text_rect.height + margin_top + margin_bottom; + + if (pointx < alloc.width / 2) + x = pointx + 25; + else + x = pointx - width - 25; + + y = pointy - height / 2; + + /* Make sure it fits in the visible area */ + x = CLAMP (x, 0, alloc.width - width); + y = CLAMP (y, 0, alloc.height - height); + + cairo_save (cr); + cairo_translate (cr, x, y); + + /* Draw the bubble */ + cairo_new_sub_path (cr); + cairo_arc (cr, width - corner_radius, corner_radius, corner_radius, radians (-90), radians (0)); + cairo_arc (cr, width - corner_radius, height - corner_radius, corner_radius, radians (0), radians (90)); + cairo_arc (cr, corner_radius, height - corner_radius, corner_radius, radians (90), radians (180)); + cairo_arc (cr, corner_radius, corner_radius, corner_radius, radians (180), radians (270)); + cairo_close_path (cr); + + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 0.7); + cairo_fill (cr); + + /* And finally draw the text */ + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_move_to (cr, margin_left, margin_top); + pango_cairo_show_layout (cr, layout); + + g_object_unref (layout); + cairo_restore (cr); +} + +static gboolean +cc_timezone_map_draw (GtkWidget *widget, + cairo_t *cr) +{ + CcTimezoneMap *map = CC_TIMEZONE_MAP (widget); + g_autoptr(GdkPixbuf) orig_hilight = NULL; + GtkAllocation alloc; + g_autofree gchar *file = NULL; + g_autoptr(GError) err = NULL; + gdouble pointx, pointy; + char buf[16]; + + gtk_widget_get_allocation (widget, &alloc); + + /* paint background */ + gdk_cairo_set_source_pixbuf (cr, map->background, 0, 0); + cairo_paint (cr); + + /* paint hilight */ + if (gtk_widget_is_sensitive (widget)) + { + file = g_strdup_printf (DATETIME_RESOURCE_PATH "/timezone_%s.png", + g_ascii_formatd (buf, sizeof (buf), + "%g", map->selected_offset)); + } + else + { + file = g_strdup_printf (DATETIME_RESOURCE_PATH "/timezone_%s_dim.png", + g_ascii_formatd (buf, sizeof (buf), + "%g", map->selected_offset)); + + } + + orig_hilight = gdk_pixbuf_new_from_resource (file, &err); + + if (!orig_hilight) + { + g_warning ("Could not load hilight: %s", + (err) ? err->message : "Unknown Error"); + } + else + { + g_autoptr(GdkPixbuf) hilight = NULL; + + hilight = gdk_pixbuf_scale_simple (orig_hilight, alloc.width, + alloc.height, GDK_INTERP_BILINEAR); + gdk_cairo_set_source_pixbuf (cr, hilight, 0, 0); + + cairo_paint (cr); + } + + if (map->location) + { + pointx = convert_longitude_to_x (map->location->longitude, alloc.width); + pointy = convert_latitude_to_y (map->location->latitude, alloc.height); + + pointx = CLAMP (floor (pointx), 0, alloc.width); + pointy = CLAMP (floor (pointy), 0, alloc.height); + + draw_text_bubble (cr, widget, pointx, pointy); + + if (map->pin) + { + gdk_cairo_set_source_pixbuf (cr, map->pin, + pointx - PIN_HOT_POINT_X, + pointy - PIN_HOT_POINT_Y); + cairo_paint (cr); + } + } + + return TRUE; +} + +static void +update_cursor (GtkWidget *widget) +{ + GdkWindow *window; + g_autoptr(GdkCursor) cursor = NULL; + + if (!gtk_widget_get_realized (widget)) + return; + + if (gtk_widget_is_sensitive (widget)) + { + GdkDisplay *display; + display = gtk_widget_get_display (widget); + cursor = gdk_cursor_new_for_display (display, GDK_HAND2); + } + + window = gtk_widget_get_window (widget); + gdk_window_set_cursor (window, cursor); +} + +static void +cc_timezone_map_state_flags_changed (GtkWidget *widget, + GtkStateFlags prev_state) +{ + update_cursor (widget); + + if (GTK_WIDGET_CLASS (cc_timezone_map_parent_class)->state_flags_changed) + GTK_WIDGET_CLASS (cc_timezone_map_parent_class)->state_flags_changed (widget, prev_state); +} + + +static void +cc_timezone_map_class_init (CcTimezoneMapClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_timezone_map_dispose; + object_class->finalize = cc_timezone_map_finalize; + + widget_class->get_preferred_width = cc_timezone_map_get_preferred_width; + widget_class->get_preferred_height = cc_timezone_map_get_preferred_height; + widget_class->size_allocate = cc_timezone_map_size_allocate; + widget_class->realize = cc_timezone_map_realize; + widget_class->draw = cc_timezone_map_draw; + widget_class->state_flags_changed = cc_timezone_map_state_flags_changed; + + signals[LOCATION_CHANGED] = g_signal_new ("location-changed", + CC_TYPE_TIMEZONE_MAP, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); +} + + +static gint +sort_locations (TzLocation *a, + TzLocation *b) +{ + if (a->dist > b->dist) + return 1; + + if (a->dist < b->dist) + return -1; + + return 0; +} + +static void +set_location (CcTimezoneMap *map, + TzLocation *location) +{ + g_autoptr(TzInfo) info = NULL; + + map->location = location; + + info = tz_info_from_location (map->location); + + map->selected_offset = tz_location_get_utc_offset (map->location) + / (60.0*60.0) + ((info->daylight) ? -1.0 : 0.0); + + g_signal_emit (map, signals[LOCATION_CHANGED], 0, map->location); +} + +static gboolean +button_press_event (CcTimezoneMap *map, + GdkEventButton *event) +{ + gint x, y; + guchar r, g, b, a; + guchar *pixels; + gint rowstride; + gint i; + + const GPtrArray *array; + gint width, height; + GList *distances = NULL; + GtkAllocation alloc; + + x = event->x; + y = event->y; + + + rowstride = map->visible_map_rowstride; + pixels = map->visible_map_pixels; + + r = pixels[(rowstride * y + x * 4)]; + g = pixels[(rowstride * y + x * 4) + 1]; + b = pixels[(rowstride * y + x * 4) + 2]; + a = pixels[(rowstride * y + x * 4) + 3]; + + + for (i = 0; color_codes[i].offset != -100; i++) + { + if (color_codes[i].red == r && color_codes[i].green == g + && color_codes[i].blue == b && color_codes[i].alpha == a) + { + map->selected_offset = color_codes[i].offset; + } + } + + gtk_widget_queue_draw (GTK_WIDGET (map)); + + /* work out the co-ordinates */ + + array = tz_get_locations (map->tzdb); + + gtk_widget_get_allocation (GTK_WIDGET (map), &alloc); + width = alloc.width; + height = alloc.height; + + for (i = 0; i < array->len; i++) + { + gdouble pointx, pointy, dx, dy; + TzLocation *loc = array->pdata[i]; + + pointx = convert_longitude_to_x (loc->longitude, width); + pointy = convert_latitude_to_y (loc->latitude, height); + + dx = pointx - x; + dy = pointy - y; + + loc->dist = dx * dx + dy * dy; + distances = g_list_prepend (distances, loc); + + } + distances = g_list_sort (distances, (GCompareFunc) sort_locations); + + + set_location (map, (TzLocation*) distances->data); + + g_list_free (distances); + + return TRUE; +} + +static void +cc_timezone_map_init (CcTimezoneMap *map) +{ + GError *err = NULL; + + map->orig_background = gdk_pixbuf_new_from_resource (DATETIME_RESOURCE_PATH "/bg.png", + &err); + + if (!map->orig_background) + { + g_warning ("Could not load background image: %s", + (err) ? err->message : "Unknown error"); + g_clear_error (&err); + } + + map->orig_background_dim = gdk_pixbuf_new_from_resource (DATETIME_RESOURCE_PATH "/bg_dim.png", + &err); + + if (!map->orig_background_dim) + { + g_warning ("Could not load background image: %s", + (err) ? err->message : "Unknown error"); + g_clear_error (&err); + } + + map->orig_color_map = gdk_pixbuf_new_from_resource (DATETIME_RESOURCE_PATH "/cc.png", + &err); + if (!map->orig_color_map) + { + g_warning ("Could not load background image: %s", + (err) ? err->message : "Unknown error"); + g_clear_error (&err); + } + + map->pin = gdk_pixbuf_new_from_resource (DATETIME_RESOURCE_PATH "/pin.png", + &err); + if (!map->pin) + { + g_warning ("Could not load pin icon: %s", + (err) ? err->message : "Unknown error"); + g_clear_error (&err); + } + + map->tzdb = tz_load_db (); + + g_signal_connect_object (map, "button-press-event", G_CALLBACK (button_press_event), map, G_CONNECT_SWAPPED); +} + +CcTimezoneMap * +cc_timezone_map_new (void) +{ + return g_object_new (CC_TYPE_TIMEZONE_MAP, NULL); +} + +gboolean +cc_timezone_map_set_timezone (CcTimezoneMap *map, + const gchar *timezone) +{ + GPtrArray *locations; + guint i; + g_autofree gchar *real_tz = NULL; + gboolean ret; + + real_tz = tz_info_get_clean_name (map->tzdb, timezone); + + locations = tz_get_locations (map->tzdb); + ret = FALSE; + + for (i = 0; i < locations->len; i++) + { + TzLocation *loc = locations->pdata[i]; + + if (!g_strcmp0 (loc->zone, real_tz ? real_tz : timezone)) + { + set_location (map, loc); + ret = TRUE; + break; + } + } + + if (ret) + gtk_widget_queue_draw (GTK_WIDGET (map)); + + return ret; +} + +void +cc_timezone_map_set_bubble_text (CcTimezoneMap *map, + const gchar *text) +{ + g_free (map->bubble_text); + map->bubble_text = g_strdup (text); + + gtk_widget_queue_draw (GTK_WIDGET (map)); +} + +TzLocation * +cc_timezone_map_get_location (CcTimezoneMap *map) +{ + return map->location; +} diff --git a/gnome-initial-setup/pages/timezone/cc-timezone-map.h b/gnome-initial-setup/pages/timezone/cc-timezone-map.h new file mode 100644 index 0000000..f4c99e5 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/cc-timezone-map.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * 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/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#pragma once + +#include <gtk/gtk.h> +#include "tz.h" + +G_BEGIN_DECLS + +#define CC_TYPE_TIMEZONE_MAP (cc_timezone_map_get_type ()) +G_DECLARE_FINAL_TYPE (CcTimezoneMap, cc_timezone_map, CC, TIMEZONE_MAP, GtkWidget) + +CcTimezoneMap *cc_timezone_map_new (void); + +gboolean cc_timezone_map_set_timezone (CcTimezoneMap *map, + const gchar *timezone); +void cc_timezone_map_set_bubble_text (CcTimezoneMap *map, + const gchar *text); +TzLocation * cc_timezone_map_get_location (CcTimezoneMap *map); + +G_END_DECLS diff --git a/gnome-initial-setup/pages/timezone/data/bg.png b/gnome-initial-setup/pages/timezone/data/bg.png Binary files differnew file mode 100644 index 0000000..4180ee8 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/bg.png diff --git a/gnome-initial-setup/pages/timezone/data/bg_dim.png b/gnome-initial-setup/pages/timezone/data/bg_dim.png Binary files differnew file mode 100644 index 0000000..3d34b94 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/bg_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/cc.png b/gnome-initial-setup/pages/timezone/data/cc.png Binary files differnew file mode 100644 index 0000000..e2eff2b --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/cc.png diff --git a/gnome-initial-setup/pages/timezone/data/pin.png b/gnome-initial-setup/pages/timezone/data/pin.png Binary files differnew file mode 100644 index 0000000..40dd4ea --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/pin.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-1.png b/gnome-initial-setup/pages/timezone/data/timezone_-1.png Binary files differnew file mode 100644 index 0000000..fb00d83 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-1.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-10.png b/gnome-initial-setup/pages/timezone/data/timezone_-10.png Binary files differnew file mode 100644 index 0000000..472eb88 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-10.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-10_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-10_dim.png Binary files differnew file mode 100644 index 0000000..7dd94f4 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-10_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-11.png b/gnome-initial-setup/pages/timezone/data/timezone_-11.png Binary files differnew file mode 100644 index 0000000..1da3536 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-11.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-11_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-11_dim.png Binary files differnew file mode 100644 index 0000000..3ed7bb4 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-11_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-1_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-1_dim.png Binary files differnew file mode 100644 index 0000000..5a1fd9f --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-1_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-2.png b/gnome-initial-setup/pages/timezone/data/timezone_-2.png Binary files differnew file mode 100644 index 0000000..30a1ec7 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-2.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-2_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-2_dim.png Binary files differnew file mode 100644 index 0000000..da78e75 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-2_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-3.5.png b/gnome-initial-setup/pages/timezone/data/timezone_-3.5.png Binary files differnew file mode 100644 index 0000000..c1df00b --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-3.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-3.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-3.5_dim.png Binary files differnew file mode 100644 index 0000000..a72375c --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-3.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-3.png b/gnome-initial-setup/pages/timezone/data/timezone_-3.png Binary files differnew file mode 100644 index 0000000..c22dbb6 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-3.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-3_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-3_dim.png Binary files differnew file mode 100644 index 0000000..be9e495 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-3_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-4.png b/gnome-initial-setup/pages/timezone/data/timezone_-4.png Binary files differnew file mode 100644 index 0000000..a3a8dc1 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-4.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-4_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-4_dim.png Binary files differnew file mode 100644 index 0000000..d3186c8 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-4_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-5.5.png b/gnome-initial-setup/pages/timezone/data/timezone_-5.5.png Binary files differnew file mode 100644 index 0000000..b1c788d --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-5.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-5.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-5.5_dim.png Binary files differnew file mode 100644 index 0000000..cde398b --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-5.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-5.png b/gnome-initial-setup/pages/timezone/data/timezone_-5.png Binary files differnew file mode 100644 index 0000000..06c15e6 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-5_dim.png Binary files differnew file mode 100644 index 0000000..85a0325 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-6.png b/gnome-initial-setup/pages/timezone/data/timezone_-6.png Binary files differnew file mode 100644 index 0000000..8505fb1 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-6.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-6_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-6_dim.png Binary files differnew file mode 100644 index 0000000..e6de4c6 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-6_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-7.png b/gnome-initial-setup/pages/timezone/data/timezone_-7.png Binary files differnew file mode 100644 index 0000000..fec235d --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-7.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-7_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-7_dim.png Binary files differnew file mode 100644 index 0000000..aad26c0 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-7_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-8.png b/gnome-initial-setup/pages/timezone/data/timezone_-8.png Binary files differnew file mode 100644 index 0000000..bdad7bf --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-8.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-8_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-8_dim.png Binary files differnew file mode 100644 index 0000000..7caeec7 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-8_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-9.5.png b/gnome-initial-setup/pages/timezone/data/timezone_-9.5.png Binary files differnew file mode 100644 index 0000000..b1c788d --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-9.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-9.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-9.5_dim.png Binary files differnew file mode 100644 index 0000000..9b1b71c --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-9.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-9.png b/gnome-initial-setup/pages/timezone/data/timezone_-9.png Binary files differnew file mode 100644 index 0000000..04cb3cb --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-9.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-9_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-9_dim.png Binary files differnew file mode 100644 index 0000000..578b1bd --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_-9_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_0.png b/gnome-initial-setup/pages/timezone/data/timezone_0.png Binary files differnew file mode 100644 index 0000000..e59b773 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_0.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_0_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_0_dim.png Binary files differnew file mode 100644 index 0000000..13e7ac9 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_0_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_1.png b/gnome-initial-setup/pages/timezone/data/timezone_1.png Binary files differnew file mode 100644 index 0000000..2053b7e --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_1.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_10.5.png b/gnome-initial-setup/pages/timezone/data/timezone_10.5.png Binary files differnew file mode 100644 index 0000000..6ec7f9f --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_10.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_10.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_10.5_dim.png Binary files differnew file mode 100644 index 0000000..359911f --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_10.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_10.png b/gnome-initial-setup/pages/timezone/data/timezone_10.png Binary files differnew file mode 100644 index 0000000..475dcf4 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_10.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_10_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_10_dim.png Binary files differnew file mode 100644 index 0000000..9521033 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_10_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_11.5.png b/gnome-initial-setup/pages/timezone/data/timezone_11.5.png Binary files differnew file mode 100644 index 0000000..afdedd7 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_11.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_11.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_11.5_dim.png Binary files differnew file mode 100644 index 0000000..00cc5b8 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_11.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_11.png b/gnome-initial-setup/pages/timezone/data/timezone_11.png Binary files differnew file mode 100644 index 0000000..6168aa2 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_11.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_11_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_11_dim.png Binary files differnew file mode 100644 index 0000000..5a1df4e --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_11_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_12.75.png b/gnome-initial-setup/pages/timezone/data/timezone_12.75.png Binary files differnew file mode 100644 index 0000000..4f74a85 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_12.75.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_12.75_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_12.75_dim.png Binary files differnew file mode 100644 index 0000000..cc7dbde --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_12.75_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_12.png b/gnome-initial-setup/pages/timezone/data/timezone_12.png Binary files differnew file mode 100644 index 0000000..d0b3531 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_12.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_12_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_12_dim.png Binary files differnew file mode 100644 index 0000000..71514a8 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_12_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_13.png b/gnome-initial-setup/pages/timezone/data/timezone_13.png Binary files differnew file mode 100644 index 0000000..fe2f134 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_13.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_13_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_13_dim.png Binary files differnew file mode 100644 index 0000000..90e689d --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_13_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_14.png b/gnome-initial-setup/pages/timezone/data/timezone_14.png Binary files differnew file mode 100644 index 0000000..e91d4eb --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_14.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_14_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_14_dim.png Binary files differnew file mode 100644 index 0000000..e4f0a0a --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_14_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_1_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_1_dim.png Binary files differnew file mode 100644 index 0000000..b36ff22 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_1_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_2.png b/gnome-initial-setup/pages/timezone/data/timezone_2.png Binary files differnew file mode 100644 index 0000000..ec1e874 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_2.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_2_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_2_dim.png Binary files differnew file mode 100644 index 0000000..fba1021 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_2_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_3.5.png b/gnome-initial-setup/pages/timezone/data/timezone_3.5.png Binary files differnew file mode 100644 index 0000000..2dc7399 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_3.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_3.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_3.5_dim.png Binary files differnew file mode 100644 index 0000000..76eab2e --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_3.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_3.png b/gnome-initial-setup/pages/timezone/data/timezone_3.png Binary files differnew file mode 100644 index 0000000..eda59dc --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_3.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_3_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_3_dim.png Binary files differnew file mode 100644 index 0000000..d718bbc --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_3_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_4.5.png b/gnome-initial-setup/pages/timezone/data/timezone_4.5.png Binary files differnew file mode 100644 index 0000000..e09ed90 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_4.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_4.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_4.5_dim.png Binary files differnew file mode 100644 index 0000000..74461fe --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_4.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_4.png b/gnome-initial-setup/pages/timezone/data/timezone_4.png Binary files differnew file mode 100644 index 0000000..483dc53 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_4.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_4_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_4_dim.png Binary files differnew file mode 100644 index 0000000..ce18078 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_4_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5.5.png b/gnome-initial-setup/pages/timezone/data/timezone_5.5.png Binary files differnew file mode 100644 index 0000000..9b8f094 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_5.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_5.5_dim.png Binary files differnew file mode 100644 index 0000000..f7a475d --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_5.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5.75.png b/gnome-initial-setup/pages/timezone/data/timezone_5.75.png Binary files differnew file mode 100644 index 0000000..827ce1a --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_5.75.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5.75_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_5.75_dim.png Binary files differnew file mode 100644 index 0000000..1b972ff --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_5.75_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5.png b/gnome-initial-setup/pages/timezone/data/timezone_5.png Binary files differnew file mode 100644 index 0000000..1bb6d20 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_5_dim.png Binary files differnew file mode 100644 index 0000000..9165f64 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_6.5.png b/gnome-initial-setup/pages/timezone/data/timezone_6.5.png Binary files differnew file mode 100644 index 0000000..d307bf3 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_6.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_6.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_6.5_dim.png Binary files differnew file mode 100644 index 0000000..7d99a0f --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_6.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_6.png b/gnome-initial-setup/pages/timezone/data/timezone_6.png Binary files differnew file mode 100644 index 0000000..41504fc --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_6.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_6_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_6_dim.png Binary files differnew file mode 100644 index 0000000..e99f5fa --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_6_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_7.png b/gnome-initial-setup/pages/timezone/data/timezone_7.png Binary files differnew file mode 100644 index 0000000..239115a --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_7.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_7_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_7_dim.png Binary files differnew file mode 100644 index 0000000..e59f0db --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_7_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_8.75.png b/gnome-initial-setup/pages/timezone/data/timezone_8.75.png Binary files differnew file mode 100644 index 0000000..2a2917f --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_8.75.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_8.75_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_8.75_dim.png Binary files differnew file mode 100644 index 0000000..fb9caf4 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_8.75_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_8.png b/gnome-initial-setup/pages/timezone/data/timezone_8.png Binary files differnew file mode 100644 index 0000000..d210222 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_8.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_8_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_8_dim.png Binary files differnew file mode 100644 index 0000000..9612511 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_8_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_9.5.png b/gnome-initial-setup/pages/timezone/data/timezone_9.5.png Binary files differnew file mode 100644 index 0000000..1c3290c --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_9.5.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_9.5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_9.5_dim.png Binary files differnew file mode 100644 index 0000000..6a9b9ed --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_9.5_dim.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_9.png b/gnome-initial-setup/pages/timezone/data/timezone_9.png Binary files differnew file mode 100644 index 0000000..4e2beda --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_9.png diff --git a/gnome-initial-setup/pages/timezone/data/timezone_9_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_9_dim.png Binary files differnew file mode 100644 index 0000000..ac47471 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/data/timezone_9_dim.png diff --git a/gnome-initial-setup/pages/timezone/datetime.gresource.xml b/gnome-initial-setup/pages/timezone/datetime.gresource.xml new file mode 100644 index 0000000..6445cce --- /dev/null +++ b/gnome-initial-setup/pages/timezone/datetime.gresource.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/datetime"> + <file>backward</file> + <file alias="bg.png">data/bg.png</file> + <file alias="bg_dim.png">data/bg_dim.png</file> + <file alias="cc.png">data/cc.png</file> + <file alias="pin.png">data/pin.png</file> + <file alias="timezone_0.png">data/timezone_0.png</file> + <file alias="timezone_0_dim.png">data/timezone_0_dim.png</file> + <file alias="timezone_-10.png">data/timezone_-10.png</file> + <file alias="timezone_-10_dim.png">data/timezone_-10_dim.png</file> + <file alias="timezone_10.png">data/timezone_10.png</file> + <file alias="timezone_10_dim.png">data/timezone_10_dim.png</file> + <file alias="timezone_10.5.png">data/timezone_10.5.png</file> + <file alias="timezone_10.5_dim.png">data/timezone_10.5_dim.png</file> + <file alias="timezone_-1.png">data/timezone_-1.png</file> + <file alias="timezone_-1_dim.png">data/timezone_-1_dim.png</file> + <file alias="timezone_1.png">data/timezone_1.png</file> + <file alias="timezone_1_dim.png">data/timezone_1_dim.png</file> + <file alias="timezone_-11.png">data/timezone_-11.png</file> + <file alias="timezone_-11_dim.png">data/timezone_-11_dim.png</file> + <file alias="timezone_11.png">data/timezone_11.png</file> + <file alias="timezone_11_dim.png">data/timezone_11_dim.png</file> + <file alias="timezone_11.5.png">data/timezone_11.5.png</file> + <file alias="timezone_11.5_dim.png">data/timezone_11.5_dim.png</file> + <file alias="timezone_12.png">data/timezone_12.png</file> + <file alias="timezone_12_dim.png">data/timezone_12_dim.png</file> + <file alias="timezone_12.75.png">data/timezone_12.75.png</file> + <file alias="timezone_12.75_dim.png">data/timezone_12.75_dim.png</file> + <file alias="timezone_13.png">data/timezone_13.png</file> + <file alias="timezone_13_dim.png">data/timezone_13_dim.png</file> + <file alias="timezone_14.png">data/timezone_14.png</file> + <file alias="timezone_14_dim.png">data/timezone_14_dim.png</file> + <file alias="timezone_-2.png">data/timezone_-2.png</file> + <file alias="timezone_-2_dim.png">data/timezone_-2_dim.png</file> + <file alias="timezone_2.png">data/timezone_2.png</file> + <file alias="timezone_2_dim.png">data/timezone_2_dim.png</file> + <file alias="timezone_-3.png">data/timezone_-3.png</file> + <file alias="timezone_-3_dim.png">data/timezone_-3_dim.png</file> + <file alias="timezone_3.png">data/timezone_3.png</file> + <file alias="timezone_3_dim.png">data/timezone_3_dim.png</file> + <file alias="timezone_-3.5.png">data/timezone_-3.5.png</file> + <file alias="timezone_-3.5_dim.png">data/timezone_-3.5_dim.png</file> + <file alias="timezone_3.5.png">data/timezone_3.5.png</file> + <file alias="timezone_3.5_dim.png">data/timezone_3.5_dim.png</file> + <file alias="timezone_-4.png">data/timezone_-4.png</file> + <file alias="timezone_-4_dim.png">data/timezone_-4_dim.png</file> + <file alias="timezone_4.png">data/timezone_4.png</file> + <file alias="timezone_4_dim.png">data/timezone_4_dim.png</file> + <file alias="timezone_4.5.png">data/timezone_4.5.png</file> + <file alias="timezone_4.5_dim.png">data/timezone_4.5_dim.png</file> + <file alias="timezone_-5.png">data/timezone_-5.png</file> + <file alias="timezone_-5_dim.png">data/timezone_-5_dim.png</file> + <file alias="timezone_5.png">data/timezone_5.png</file> + <file alias="timezone_5_dim.png">data/timezone_5_dim.png</file> + <file alias="timezone_-5.5.png">data/timezone_-5.5.png</file> + <file alias="timezone_-5.5_dim.png">data/timezone_-5.5_dim.png</file> + <file alias="timezone_5.5.png">data/timezone_5.5.png</file> + <file alias="timezone_5.5_dim.png">data/timezone_5.5_dim.png</file> + <file alias="timezone_5.75.png">data/timezone_5.75.png</file> + <file alias="timezone_5.75_dim.png">data/timezone_5.75_dim.png</file> + <file alias="timezone_-6.png">data/timezone_-6.png</file> + <file alias="timezone_-6_dim.png">data/timezone_-6_dim.png</file> + <file alias="timezone_6.png">data/timezone_6.png</file> + <file alias="timezone_6_dim.png">data/timezone_6_dim.png</file> + <file alias="timezone_6.5.png">data/timezone_6.5.png</file> + <file alias="timezone_6.5_dim.png">data/timezone_6.5_dim.png</file> + <file alias="timezone_-7.png">data/timezone_-7.png</file> + <file alias="timezone_-7_dim.png">data/timezone_-7_dim.png</file> + <file alias="timezone_7.png">data/timezone_7.png</file> + <file alias="timezone_7_dim.png">data/timezone_7_dim.png</file> + <file alias="timezone_-8.png">data/timezone_-8.png</file> + <file alias="timezone_-8_dim.png">data/timezone_-8_dim.png</file> + <file alias="timezone_8.png">data/timezone_8.png</file> + <file alias="timezone_8_dim.png">data/timezone_8_dim.png</file> + <file alias="timezone_8.75.png">data/timezone_8.75.png</file> + <file alias="timezone_8.75_dim.png">data/timezone_8.75_dim.png</file> + <file alias="timezone_-9.png">data/timezone_-9.png</file> + <file alias="timezone_-9_dim.png">data/timezone_-9_dim.png</file> + <file alias="timezone_9.png">data/timezone_9.png</file> + <file alias="timezone_9_dim.png">data/timezone_9_dim.png</file> + <file alias="timezone_-9.5.png">data/timezone_-9.5.png</file> + <file alias="timezone_-9.5_dim.png">data/timezone_-9.5_dim.png</file> + <file alias="timezone_9.5.png">data/timezone_9.5.png</file> + <file alias="timezone_9.5_dim.png">data/timezone_9.5_dim.png</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/timezone/gis-bubble-widget.c b/gnome-initial-setup/pages/timezone/gis-bubble-widget.c new file mode 100644 index 0000000..982cce7 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/gis-bubble-widget.c @@ -0,0 +1,146 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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 "gis-bubble-widget.h" + +struct _GisBubbleWidgetPrivate +{ + GtkWidget *icon; + GtkWidget *label; +}; +typedef struct _GisBubbleWidgetPrivate GisBubbleWidgetPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisBubbleWidget, gis_bubble_widget, GTK_TYPE_BIN); + +enum { + PROP_0, + PROP_LABEL, + PROP_ICON_NAME, + PROP_LAST, +}; + +static GParamSpec *obj_props[PROP_LAST]; + +static void +gis_bubble_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GisBubbleWidget *widget = GIS_BUBBLE_WIDGET (object); + GisBubbleWidgetPrivate *priv = gis_bubble_widget_get_instance_private (widget); + + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (priv->label))); + break; + case PROP_ICON_NAME: + { + const char *icon_name; + gtk_image_get_icon_name (GTK_IMAGE (priv->icon), &icon_name, NULL); + g_value_set_string (value, icon_name); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gis_bubble_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GisBubbleWidget *widget = GIS_BUBBLE_WIDGET (object); + GisBubbleWidgetPrivate *priv = gis_bubble_widget_get_instance_private (widget); + + switch (prop_id) + { + case PROP_LABEL: + gtk_label_set_label (GTK_LABEL (priv->label), g_value_get_string (value)); + break; + case PROP_ICON_NAME: + g_object_set (GTK_IMAGE (priv->icon), + "icon-name", g_value_get_string (value), + NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +add_style_from_resource (const char *resource) +{ + GtkCssProvider *provider; + GFile *file; + char *uri; + + provider = gtk_css_provider_new (); + + uri = g_strconcat ("resource://", resource, NULL); + file = g_file_new_for_uri (uri); + + if (!gtk_css_provider_load_from_file (provider, file, NULL)) + goto out; + + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + + out: + g_object_unref (file); + g_free (uri); +} + +static void +gis_bubble_widget_class_init (GisBubbleWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-bubble-widget.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisBubbleWidget, icon); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisBubbleWidget, label); + + object_class->set_property = gis_bubble_widget_set_property; + object_class->get_property = gis_bubble_widget_get_property; + + obj_props[PROP_LABEL] = g_param_spec_string ("label", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_ICON_NAME] = g_param_spec_string ("icon-name", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST, obj_props); + + add_style_from_resource ("/org/gnome/initial-setup/gis-bubble-widget.css"); +} + +static void +gis_bubble_widget_init (GisBubbleWidget *widget) +{ + gtk_widget_init_template (GTK_WIDGET (widget)); +} diff --git a/gnome-initial-setup/pages/timezone/gis-bubble-widget.css b/gnome-initial-setup/pages/timezone/gis-bubble-widget.css new file mode 100644 index 0000000..85de577 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/gis-bubble-widget.css @@ -0,0 +1,8 @@ + +.gis-bubble-widget { + border-radius: 20px; + background-color: rgba(0, 0, 0, 0.6); + color: white; + font-weight: bold; + padding: 2em; +} diff --git a/gnome-initial-setup/pages/timezone/gis-bubble-widget.h b/gnome-initial-setup/pages/timezone/gis-bubble-widget.h new file mode 100644 index 0000000..41e5690 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/gis-bubble-widget.h @@ -0,0 +1,53 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 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> + */ + +#ifndef __GIS_BUBBLE_WIDGET_H__ +#define __GIS_BUBBLE_WIDGET_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GIS_TYPE_BUBBLE_WIDGET (gis_bubble_widget_get_type ()) +#define GIS_BUBBLE_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_BUBBLE_WIDGET, GisBubbleWidget)) +#define GIS_BUBBLE_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_BUBBLE_WIDGET, GisBubbleWidgetClass)) +#define GIS_IS_BUBBLE_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_BUBBLE_WIDGET)) +#define GIS_IS_BUBBLE_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_BUBBLE_WIDGET)) +#define GIS_BUBBLE_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_BUBBLE_WIDGET, GisBubbleWidgetClass)) + +typedef struct _GisBubbleWidget GisBubbleWidget; +typedef struct _GisBubbleWidgetClass GisBubbleWidgetClass; + +struct _GisBubbleWidget +{ + GtkBin parent; +}; + +struct _GisBubbleWidgetClass +{ + GtkBinClass parent_class; +}; + +GType gis_bubble_widget_get_type (void); + +G_END_DECLS + +#endif /* __GIS_BUBBLE_WIDGET_H__ */ diff --git a/gnome-initial-setup/pages/timezone/gis-bubble-widget.ui b/gnome-initial-setup/pages/timezone/gis-bubble-widget.ui new file mode 100644 index 0000000..1c255ce --- /dev/null +++ b/gnome-initial-setup/pages/timezone/gis-bubble-widget.ui @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="3.0"/> + <template class="GisBubbleWidget" parent="GtkBin"> + <child> + <object class="GtkAspectFrame" id="aspect_frame"> + <property name="visible">True</property> + <style> + <class name="gis-bubble-widget" /> + </style> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="width-request">128</property> + <property name="height-request">128</property> + <property name="halign">center</property> + <property name="valign">center</property> + <child> + <object class="GtkImage" id="icon"> + <property name="visible">True</property> + <property name="vexpand">True</property> + <property name="pixel-size">64</property> + </object> + </child> + <child> + <object class="GtkLabel" id="label"> + <property name="visible">True</property> + <property name="vexpand">True</property> + <property name="wrap">True</property> + <property name="justify">center</property> + </object> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/timezone/gis-timezone-page.c b/gnome-initial-setup/pages/timezone/gis-timezone-page.c new file mode 100644 index 0000000..e188208 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/gis-timezone-page.c @@ -0,0 +1,537 @@ +/* -*- 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> + */ + +/* Timezone page {{{1 */ + +#define PAGE_ID "timezone" + +#include "config.h" +#include "gis-timezone-page.h" + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include <stdlib.h> +#include <string.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> +#include <libgnome-desktop/gnome-wall-clock.h> +#include <gdesktop-enums.h> +#include <geoclue.h> +#include <geocode-glib/geocode-glib.h> + +#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE +#include <libgweather/gweather.h> + +#include "timedated.h" +#include "cc-datetime-resources.h" +#include "timezone-resources.h" + +#include "cc-timezone-map.h" +#include "gis-bubble-widget.h" + +#include "gis-page-header.h" + +#define DEFAULT_TZ "Europe/London" +#define DESKTOP_ID "gnome-datetime-panel" + +#define CLOCK_SCHEMA "org.gnome.desktop.interface" +#define CLOCK_FORMAT_KEY "clock-format" + +/* FIXME: Drop this when we depend on a version of GeoClue which has + * https://gitlab.freedesktop.org/geoclue/geoclue/-/merge_requests/73 */ +typedef GClueSimple MyGClueSimple; +G_DEFINE_AUTOPTR_CLEANUP_FUNC (MyGClueSimple, g_object_unref) + +static void stop_geolocation (GisTimezonePage *page); + +struct _GisTimezonePagePrivate +{ + GtkWidget *map; + GtkWidget *search_entry; + GtkWidget *search_overlay; + + GCancellable *geoclue_cancellable; + GClueClient *geoclue_client; + GClueSimple *geoclue_simple; + gboolean in_geoclue_callback; + GWeatherLocation *current_location; + Timedate1 *dtm; + GCancellable *dtm_cancellable; + + GnomeWallClock *clock; + GDesktopClockFormat clock_format; + gboolean in_search; + + gulong search_entry_text_changed_id; +}; +typedef struct _GisTimezonePagePrivate GisTimezonePagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisTimezonePage, gis_timezone_page, GIS_TYPE_PAGE); + +static void +set_timezone_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + Timedate1 *dtm = TIMEDATE1 (source); + g_autoptr(GError) error = NULL; + + if (!timedate1_call_set_timezone_finish (dtm, + res, + &error)) { + /* TODO: display any error in a user friendly way */ + g_warning ("Could not set system timezone: %s", error->message); + } +} + + +static void +queue_set_timezone (GisTimezonePage *page, + const char *tzid) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + + /* for now just do it */ + timedate1_call_set_timezone (priv->dtm, + tzid, + TRUE, + priv->dtm_cancellable, + set_timezone_cb, + page); +} + +static void +set_location (GisTimezonePage *page, + GWeatherLocation *location) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + + g_clear_pointer (&priv->current_location, gweather_location_unref); + + gtk_widget_set_visible (priv->search_overlay, (location == NULL)); + gis_page_set_complete (GIS_PAGE (page), (location != NULL)); + + if (location) + { + GWeatherTimezone *zone; + const char *tzid; + + priv->current_location = gweather_location_ref (location); + + zone = gweather_location_get_timezone (location); + tzid = gweather_timezone_get_tzid (zone); + + cc_timezone_map_set_timezone (CC_TIMEZONE_MAP (priv->map), tzid); + + /* If this location is manually set, stop waiting for geolocation. */ + if (!priv->in_geoclue_callback) + stop_geolocation (page); + } +} + +static void +on_location_notify (GClueSimple *simple, + GParamSpec *pspec, + gpointer user_data) +{ + GisTimezonePage *page = user_data; + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + GClueLocation *location; + gdouble latitude, longitude; + GWeatherLocation *glocation = NULL; + + location = gclue_simple_get_location (simple); + + latitude = gclue_location_get_latitude (location); + longitude = gclue_location_get_longitude (location); + + glocation = gweather_location_find_nearest_city (NULL, latitude, longitude); + priv->in_geoclue_callback = TRUE; + set_location (page, glocation); + priv->in_geoclue_callback = FALSE; + gweather_location_unref (glocation); +} + +static void +on_geoclue_simple_ready (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GisTimezonePage *page = user_data; + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + g_autoptr(GError) local_error = NULL; + g_autoptr(MyGClueSimple) geoclue_simple = NULL; + + /* This function may be called in an idle callback once @page has been + * disposed, if going through cancellation. So don’t dereference @priv or + * @page until the error has been checked. */ + geoclue_simple = gclue_simple_new_finish (res, &local_error); + if (local_error != NULL) + { + if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_critical ("Failed to connect to GeoClue2 service: %s", local_error->message); + return; + } + + priv->geoclue_simple = g_steal_pointer (&geoclue_simple); + priv->geoclue_client = gclue_simple_get_client (priv->geoclue_simple); + gclue_client_set_distance_threshold (priv->geoclue_client, + GEOCODE_LOCATION_ACCURACY_CITY); + + g_signal_connect (priv->geoclue_simple, "notify::location", + G_CALLBACK (on_location_notify), page); + + on_location_notify (priv->geoclue_simple, NULL, page); +} + +static void +get_location_from_geoclue_async (GisTimezonePage *page) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + + gclue_simple_new (DESKTOP_ID, + GCLUE_ACCURACY_LEVEL_CITY, + priv->geoclue_cancellable, + on_geoclue_simple_ready, + page); +} + +static void +entry_text_changed (GtkEditable *editable, + gpointer user_data) +{ + GisTimezonePage *page = GIS_TIMEZONE_PAGE (user_data); + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + + stop_geolocation (GIS_TIMEZONE_PAGE (user_data)); + g_signal_handler_disconnect (priv->search_entry, + priv->search_entry_text_changed_id); + priv->search_entry_text_changed_id = 0; +} + +static void +entry_location_changed (GObject *object, GParamSpec *param, GisTimezonePage *page) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + GWeatherLocationEntry *entry = GWEATHER_LOCATION_ENTRY (object); + GWeatherLocation *location; + + location = gweather_location_entry_get_location (entry); + if (!location) + return; + + priv->in_search = TRUE; + set_location (page, location); + priv->in_search = FALSE; + + gweather_location_unref (location); +} + +#define GETTEXT_PACKAGE_TIMEZONES "gnome-control-center-2.0-timezones" + +static char * +translated_city_name (TzLocation *loc) +{ + char *country; + char *name; + char *zone_translated; + char **split_translated; + gint length; + + /* Load the translation for it */ + zone_translated = g_strdup (dgettext (GETTEXT_PACKAGE_TIMEZONES, loc->zone)); + g_strdelimit (zone_translated, "_", ' '); + split_translated = g_regex_split_simple ("[\\x{2044}\\x{2215}\\x{29f8}\\x{ff0f}/]", + zone_translated, + 0, 0); + g_free (zone_translated); + + length = g_strv_length (split_translated); + + country = gnome_get_country_from_code (loc->country, NULL); + /* Translators: "city, country" */ + name = g_strdup_printf (C_("timezone loc", "%s, %s"), + split_translated[length-1], + country); + g_free (country); + g_strfreev (split_translated); + + return name; +} + +static void +update_timezone (GisTimezonePage *page, TzLocation *location) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + char *tz_desc; + char *bubble_text; + char *city_country; + char *utc_label; + char *time_label; + GTimeZone *zone; + GDateTime *date; + gboolean use_ampm; + + if (priv->clock_format == G_DESKTOP_CLOCK_FORMAT_12H) + use_ampm = TRUE; + else + use_ampm = FALSE; + + zone = g_time_zone_new (location->zone); + date = g_date_time_new_now (zone); + g_time_zone_unref (zone); + + /* Update the text bubble in the timezone map */ + city_country = translated_city_name (location); + + /* Translators: UTC here means the Coordinated Universal Time. + * %:::z will be replaced by the offset from UTC e.g. UTC+02 + */ + utc_label = g_date_time_format (date, _("UTC%:::z")); + + if (use_ampm) + /* Translators: This is the time format used in 12-hour mode. */ + time_label = g_date_time_format (date, _("%l:%M %p")); + else + /* Translators: This is the time format used in 24-hour mode. */ + time_label = g_date_time_format (date, _("%R")); + + /* Translators: "timezone (utc shift)" */ + tz_desc = g_strdup_printf (C_("timezone map", "%s (%s)"), + g_date_time_get_timezone_abbreviation (date), + utc_label); + bubble_text = g_strdup_printf ("<b>%s</b>\n" + "<small>%s</small>\n" + "<b>%s</b>", + tz_desc, + city_country, + time_label); + cc_timezone_map_set_bubble_text (CC_TIMEZONE_MAP (priv->map), bubble_text); + + g_free (tz_desc); + g_free (city_country); + g_free (utc_label); + g_free (time_label); + g_free (bubble_text); + + g_date_time_unref (date); +} + +static void +map_location_changed (CcTimezoneMap *map, + TzLocation *location, + GisTimezonePage *page) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + + gtk_widget_set_visible (priv->search_overlay, (location == NULL)); + gis_page_set_complete (GIS_PAGE (page), (location != NULL)); + + if (!priv->in_search) + gtk_entry_set_text (GTK_ENTRY (priv->search_entry), ""); + + update_timezone (page, location); + queue_set_timezone (page, location->zone); +} + +static void +on_clock_changed (GnomeWallClock *clock, + GParamSpec *pspec, + GisTimezonePage *page) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + TzLocation *location; + + if (!gtk_widget_get_mapped (priv->map)) + return; + + if (gtk_widget_is_visible (priv->search_overlay)) + return; + + location = cc_timezone_map_get_location (CC_TIMEZONE_MAP (priv->map)); + if (location) + update_timezone (page, location); +} + +static void +entry_mapped (GtkWidget *widget, + gpointer user_data) +{ + gtk_widget_grab_focus (widget); +} + +static void +stop_geolocation (GisTimezonePage *page) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + + if (priv->geoclue_cancellable) + { + g_cancellable_cancel (priv->geoclue_cancellable); + g_clear_object (&priv->geoclue_cancellable); + } + + if (priv->geoclue_client) + { + gclue_client_call_stop (priv->geoclue_client, NULL, NULL, NULL); + priv->geoclue_client = NULL; + } + g_clear_object (&priv->geoclue_simple); +} + +static void +page_added (GisTimezonePage *page) +{ + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + + if (priv->geoclue_cancellable == NULL) + { + priv->geoclue_cancellable = g_cancellable_new (); + get_location_from_geoclue_async (page); + } +} + +static void +gis_timezone_page_constructed (GObject *object) +{ + GisTimezonePage *page = GIS_TIMEZONE_PAGE (object); + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + GError *error; + GSettings *settings; + + G_OBJECT_CLASS (gis_timezone_page_parent_class)->constructed (object); + + priv->dtm_cancellable = g_cancellable_new (); + + error = NULL; + priv->dtm = timedate1_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + priv->dtm_cancellable, + &error); + if (priv->dtm == NULL) { + g_error ("Failed to create proxy for timedated: %s", error->message); + exit (1); + } + + priv->clock = g_object_new (GNOME_TYPE_WALL_CLOCK, NULL); + g_signal_connect (priv->clock, "notify::clock", G_CALLBACK (on_clock_changed), page); + + settings = g_settings_new (CLOCK_SCHEMA); + priv->clock_format = g_settings_get_enum (settings, CLOCK_FORMAT_KEY); + g_object_unref (settings); + + set_location (page, NULL); + + priv->search_entry_text_changed_id = + g_signal_connect (priv->search_entry, "changed", + G_CALLBACK (entry_text_changed), page); + g_signal_connect (priv->search_entry, "notify::location", + G_CALLBACK (entry_location_changed), page); + g_signal_connect (priv->search_entry, "map", + G_CALLBACK (entry_mapped), page); + g_signal_connect (priv->map, "location-changed", + G_CALLBACK (map_location_changed), page); + g_signal_connect (GTK_WIDGET (page), "parent-set", + G_CALLBACK (page_added), NULL); + + gtk_widget_show (GTK_WIDGET (page)); +} + +static void +gis_timezone_page_dispose (GObject *object) +{ + GisTimezonePage *page = GIS_TIMEZONE_PAGE (object); + GisTimezonePagePrivate *priv = gis_timezone_page_get_instance_private (page); + + stop_geolocation (page); + + if (priv->dtm_cancellable != NULL) + { + g_cancellable_cancel (priv->dtm_cancellable); + g_clear_object (&priv->dtm_cancellable); + } + + g_clear_object (&priv->dtm); + g_clear_object (&priv->clock); + + G_OBJECT_CLASS (gis_timezone_page_parent_class)->dispose (object); +} + +static void +gis_timezone_page_locale_changed (GisPage *page) +{ + gis_page_set_title (GIS_PAGE (page), _("Time Zone")); +} + +static gboolean +gis_timezone_page_apply (GisPage *page, + GCancellable *cancellable) +{ + /* Once the user accepts the location, it would be unkind to change it if + * GeoClue suddenly tells us we're somewhere else. + */ + stop_geolocation (GIS_TIMEZONE_PAGE (page)); + + return FALSE; +} + +static void +gis_timezone_page_class_init (GisTimezonePageClass *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-timezone-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisTimezonePage, map); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisTimezonePage, search_entry); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisTimezonePage, search_overlay); + + page_class->page_id = PAGE_ID; + page_class->locale_changed = gis_timezone_page_locale_changed; + page_class->apply = gis_timezone_page_apply; + object_class->constructed = gis_timezone_page_constructed; + object_class->dispose = gis_timezone_page_dispose; +} + +static void +gis_timezone_page_init (GisTimezonePage *page) +{ + g_resources_register (timezone_get_resource ()); + g_resources_register (datetime_get_resource ()); + g_type_ensure (CC_TYPE_TIMEZONE_MAP); + g_type_ensure (GIS_TYPE_BUBBLE_WIDGET); + g_type_ensure (GIS_TYPE_PAGE_HEADER); + + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_timezone_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_TIMEZONE_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/timezone/gis-timezone-page.h b/gnome-initial-setup/pages/timezone/gis-timezone-page.h new file mode 100644 index 0000000..e9ba8ed --- /dev/null +++ b/gnome-initial-setup/pages/timezone/gis-timezone-page.h @@ -0,0 +1,57 @@ +/* -*- 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> + */ + +#ifndef __GIS_TIMEZONE_PAGE_H__ +#define __GIS_TIMEZONE_PAGE_H__ + +#include <glib-object.h> + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_TIMEZONE_PAGE (gis_timezone_page_get_type ()) +#define GIS_TIMEZONE_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_TIMEZONE_PAGE, GisTimezonePage)) +#define GIS_TIMEZONE_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_TIMEZONE_PAGE, GisTimezonePageClass)) +#define GIS_IS_TIMEZONE_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_TIMEZONE_PAGE)) +#define GIS_IS_TIMEZONE_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_TIMEZONE_PAGE)) +#define GIS_TIMEZONE_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_TIMEZONE_PAGE, GisTimezonePageClass)) + +typedef struct _GisTimezonePage GisTimezonePage; +typedef struct _GisTimezonePageClass GisTimezonePageClass; + +struct _GisTimezonePage +{ + GisPage parent; +}; + +struct _GisTimezonePageClass +{ + GisPageClass parent_class; +}; + +GType gis_timezone_page_get_type (void); + +GisPage *gis_prepare_timezone_page (GisDriver *driver); + +G_END_DECLS + +#endif /* __GIS_TIMEZONE_PAGE_H__ */ diff --git a/gnome-initial-setup/pages/timezone/gis-timezone-page.ui b/gnome-initial-setup/pages/timezone/gis-timezone-page.ui new file mode 100644 index 0000000..07fc352 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/gis-timezone-page.ui @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.16.0 on Tue Oct 22 19:34:41 2013 --> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="GisTimezonePage" parent="GisPage"> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="halign">center</property> + <property name="valign">fill</property> + <child> + <object class="GisPageHeader" id="header"> + <property name="visible">True</property> + <property name="margin_top">24</property> + <property name="title" translatable="yes">Time Zone</property> + <property name="subtitle" translatable="yes">The time zone will be set automatically if your location can be found. You can also search for a city to set it yourself.</property> + <property name="icon_name">find-location-symbolic</property> + <property name="show_icon" bind-source="GisTimezonePage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/> + </object> + </child> + <child> + <object class="GtkBox" id="page_box"> + <property name="visible">True</property> + <property name="margin_top">18</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">14</property> + <child> + <object class="GWeatherLocationEntry" id="search_entry"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="max-width-chars">55</property> + </object> + </child> + <child> + <object class="GtkFrame" id="map_frame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_bottom">18</property> + <property name="hexpand">True</property> + <property name="label_xalign">0</property> + <child> + <object class="GtkOverlay" id="map_overlay"> + <property name="visible">True</property> + <child> + <object class="CcTimezoneMap" id="map"> + <property name="visible">True</property> + <property name="hexpand">True</property> + </object> + </child> + <child type="overlay"> + <object class="GisBubbleWidget" id="search_overlay"> + <property name="visible">True</property> + <property name="label" translatable="yes">Please search for a nearby city</property> + <property name="icon-name">edit-find-symbolic</property> + <property name="halign">center</property> + <property name="valign">center</property> + </object> + <packing> + <property name="pass-through">True</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/timezone/meson.build b/gnome-initial-setup/pages/timezone/meson.build new file mode 100644 index 0000000..1c4853d --- /dev/null +++ b/gnome-initial-setup/pages/timezone/meson.build @@ -0,0 +1,28 @@ +sources += gnome.gdbus_codegen( + 'timedated', + 'timedated1-interface.xml', + interface_prefix: 'org.freedesktop.', +) + +sources += gnome.compile_resources( + 'cc-datetime-resources', + files('datetime.gresource.xml'), + c_name: 'datetime' +) + +sources += gnome.compile_resources( + 'timezone-resources', + files('timezone.gresource.xml'), + c_name: 'timezone' +) + +sources += files( + 'cc-timezone-map.c', + 'cc-timezone-map.h', + 'tz.c', + 'tz.h', + 'gis-bubble-widget.c', + 'gis-bubble-widget.h', + 'gis-timezone-page.c', + 'gis-timezone-page.h' +) diff --git a/gnome-initial-setup/pages/timezone/timedated1-interface.xml b/gnome-initial-setup/pages/timezone/timedated1-interface.xml new file mode 100644 index 0000000..3370e0e --- /dev/null +++ b/gnome-initial-setup/pages/timezone/timedated1-interface.xml @@ -0,0 +1,28 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.freedesktop.timedate1"> + <property name="Timezone" type="s" access="read"/> + <property name="LocalRTC" type="b" access="read"/> + <property name="CanNTP" type="b" access="read"/> + <property name="NTP" type="b" access="read"/> + <method name="SetTime"> + <arg name="usec_utc" type="x" direction="in"/> + <arg name="relative" type="b" direction="in"/> + <arg name="user_interaction" type="b" direction="in"/> + </method> + <method name="SetTimezone"> + <arg name="timezone" type="s" direction="in"/> + <arg name="user_interaction" type="b" direction="in"/> + </method> + <method name="SetLocalRTC"> + <arg name="local_rtc" type="b" direction="in"/> + <arg name="fix_system" type="b" direction="in"/> + <arg name="user_interaction" type="b" direction="in"/> + </method> + <method name="SetNTP"> + <arg name="use_ntp" type="b" direction="in"/> + <arg name="user_interaction" type="b" direction="in"/> + </method> + </interface> +</node> diff --git a/gnome-initial-setup/pages/timezone/timezone.gresource.xml b/gnome-initial-setup/pages/timezone/timezone.gresource.xml new file mode 100644 index 0000000..cf36818 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/timezone.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-timezone-page.ui">gis-timezone-page.ui</file> + <file preprocess="xml-stripblanks" alias="gis-bubble-widget.ui">gis-bubble-widget.ui</file> + <file alias="gis-bubble-widget.css">gis-bubble-widget.css</file> + </gresource> +</gresources> diff --git a/gnome-initial-setup/pages/timezone/tz.c b/gnome-initial-setup/pages/timezone/tz.c new file mode 100644 index 0000000..6e96dd4 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/tz.c @@ -0,0 +1,470 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Generic timezone utilities. + * + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Authors: Hans Petter Jansson <hpj@ximian.com> + * + * Largely based on Michael Fulbright's work on Anaconda. + * + * 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/>. + */ + + +#include <glib.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <math.h> +#include <string.h> +#include <ctype.h> +#include "tz.h" +#include "cc-datetime-resources.h" + + +/* Forward declarations for private functions */ + +static float convert_pos (gchar *pos, int digits); +static int compare_country_names (const void *a, const void *b); +static void sort_locations_by_country (GPtrArray *locations); +static gchar * tz_data_file_get (void); +static void load_backward_tz (TzDB *tz_db); + +/* ---------------- * + * Public interface * + * ---------------- */ +TzDB * +tz_load_db (void) +{ + g_autofree gchar *tz_data_file = NULL; + TzDB *tz_db; + FILE *tzfile; + char buf[4096]; + + tz_data_file = tz_data_file_get (); + if (!tz_data_file) { + g_warning ("Could not get the TimeZone data file name"); + return NULL; + } + tzfile = fopen (tz_data_file, "r"); + if (!tzfile) { + g_warning ("Could not open *%s*\n", tz_data_file); + return NULL; + } + + tz_db = g_new0 (TzDB, 1); + tz_db->locations = g_ptr_array_new (); + + while (fgets (buf, sizeof(buf), tzfile)) + { + g_auto(GStrv) tmpstrarr = NULL; + g_autofree gchar *latstr = NULL; + g_autofree gchar *lngstr = NULL; + gchar *p; + TzLocation *loc; + + if (*buf == '#') continue; + + g_strchomp(buf); + tmpstrarr = g_strsplit(buf,"\t", 6); + + latstr = g_strdup (tmpstrarr[1]); + p = latstr + 1; + while (*p != '-' && *p != '+') p++; + lngstr = g_strdup (p); + *p = '\0'; + + loc = g_new0 (TzLocation, 1); + loc->country = g_strdup (tmpstrarr[0]); + loc->zone = g_strdup (tmpstrarr[2]); + loc->latitude = convert_pos (latstr, 2); + loc->longitude = convert_pos (lngstr, 3); + +#ifdef __sun + if (tmpstrarr[3] && *tmpstrarr[3] == '-' && tmpstrarr[4]) + loc->comment = g_strdup (tmpstrarr[4]); + + if (tmpstrarr[3] && *tmpstrarr[3] != '-' && !islower(loc->zone)) { + TzLocation *locgrp; + + /* duplicate entry */ + locgrp = g_new0 (TzLocation, 1); + locgrp->country = g_strdup (tmpstrarr[0]); + locgrp->zone = g_strdup (tmpstrarr[3]); + locgrp->latitude = convert_pos (latstr, 2); + locgrp->longitude = convert_pos (lngstr, 3); + locgrp->comment = (tmpstrarr[4]) ? g_strdup (tmpstrarr[4]) : NULL; + + g_ptr_array_add (tz_db->locations, (gpointer) locgrp); + } +#else + loc->comment = (tmpstrarr[3]) ? g_strdup(tmpstrarr[3]) : NULL; +#endif + + g_ptr_array_add (tz_db->locations, (gpointer) loc); + } + + fclose (tzfile); + + /* now sort by country */ + sort_locations_by_country (tz_db->locations); + + /* Load up the hashtable of backward links */ + load_backward_tz (tz_db); + + return tz_db; +} + +static void +tz_location_free (TzLocation *loc) +{ + g_free (loc->country); + g_free (loc->zone); + g_free (loc->comment); + + g_free (loc); +} + +void +tz_db_free (TzDB *db) +{ + g_ptr_array_foreach (db->locations, (GFunc) tz_location_free, NULL); + g_ptr_array_free (db->locations, TRUE); + g_hash_table_destroy (db->backward); + g_free (db); +} + +GPtrArray * +tz_get_locations (TzDB *db) +{ + return db->locations; +} + + +gchar * +tz_location_get_country (TzLocation *loc) +{ + return loc->country; +} + + +gchar * +tz_location_get_zone (TzLocation *loc) +{ + return loc->zone; +} + + +gchar * +tz_location_get_comment (TzLocation *loc) +{ + return loc->comment; +} + + +void +tz_location_get_position (TzLocation *loc, double *longitude, double *latitude) +{ + *longitude = loc->longitude; + *latitude = loc->latitude; +} + +glong +tz_location_get_utc_offset (TzLocation *loc) +{ + g_autoptr(TzInfo) tz_info = NULL; + glong offset; + + tz_info = tz_info_from_location (loc); + offset = tz_info->utc_offset; + return offset; +} + +TzInfo * +tz_info_from_location (TzLocation *loc) +{ + TzInfo *tzinfo; + time_t curtime; + struct tm *curzone; + g_autofree gchar *tz_env_value = NULL; + + g_return_val_if_fail (loc != NULL, NULL); + g_return_val_if_fail (loc->zone != NULL, NULL); + + tz_env_value = g_strdup (getenv ("TZ")); + setenv ("TZ", loc->zone, 1); + +#if 0 + tzset (); +#endif + tzinfo = g_new0 (TzInfo, 1); + + curtime = time (NULL); + curzone = localtime (&curtime); + +#ifndef __sun + /* Currently this solution doesnt seem to work - I get that */ + /* America/Phoenix uses daylight savings, which is wrong */ + tzinfo->tzname_normal = g_strdup (curzone->tm_zone); + if (curzone->tm_isdst) + tzinfo->tzname_daylight = + g_strdup (&curzone->tm_zone[curzone->tm_isdst]); + else + tzinfo->tzname_daylight = NULL; + + tzinfo->utc_offset = curzone->tm_gmtoff; +#else + tzinfo->tzname_normal = NULL; + tzinfo->tzname_daylight = NULL; + tzinfo->utc_offset = 0; +#endif + + tzinfo->daylight = curzone->tm_isdst; + + if (tz_env_value) + setenv ("TZ", tz_env_value, 1); + else + unsetenv ("TZ"); + + return tzinfo; +} + + +void +tz_info_free (TzInfo *tzinfo) +{ + g_return_if_fail (tzinfo != NULL); + + if (tzinfo->tzname_normal) g_free (tzinfo->tzname_normal); + if (tzinfo->tzname_daylight) g_free (tzinfo->tzname_daylight); + g_free (tzinfo); +} + +struct { + const char *orig; + const char *dest; +} aliases[] = { + { "Asia/Istanbul", "Europe/Istanbul" }, /* Istanbul is in both Europe and Asia */ + { "Europe/Nicosia", "Asia/Nicosia" }, /* Ditto */ + { "EET", "Europe/Istanbul" }, /* Same tz as the 2 above */ + { "HST", "Pacific/Honolulu" }, + { "WET", "Europe/Brussels" }, /* Other name for the mainland Europe tz */ + { "CET", "Europe/Brussels" }, /* ditto */ + { "MET", "Europe/Brussels" }, + { "Etc/Zulu", "Etc/GMT" }, + { "Etc/UTC", "Etc/GMT" }, + { "GMT", "Etc/GMT" }, + { "Greenwich", "Etc/GMT" }, + { "Etc/UCT", "Etc/GMT" }, + { "Etc/GMT0", "Etc/GMT" }, + { "Etc/GMT+0", "Etc/GMT" }, + { "Etc/GMT-0", "Etc/GMT" }, + { "Etc/Universal", "Etc/GMT" }, + { "PST8PDT", "America/Los_Angeles" }, /* Other name for the Atlantic tz */ + { "EST", "America/New_York" }, /* Other name for the Eastern tz */ + { "EST5EDT", "America/New_York" }, /* ditto */ + { "CST6CDT", "America/Chicago" }, /* Other name for the Central tz */ + { "MST", "America/Denver" }, /* Other name for the mountain tz */ + { "MST7MDT", "America/Denver" }, /* ditto */ +}; + +static gboolean +compare_timezones (const char *a, + const char *b) +{ + if (g_str_equal (a, b)) + return TRUE; + if (strchr (b, '/') == NULL) { + g_autofree gchar *prefixed = NULL; + + prefixed = g_strdup_printf ("/%s", b); + if (g_str_has_suffix (a, prefixed)) + return TRUE; + } + + return FALSE; +} + +char * +tz_info_get_clean_name (TzDB *tz_db, + const char *tz) +{ + char *ret; + const char *timezone; + guint i; + gboolean replaced; + + /* Remove useless prefixes */ + if (g_str_has_prefix (tz, "right/")) + tz = tz + strlen ("right/"); + else if (g_str_has_prefix (tz, "posix/")) + tz = tz + strlen ("posix/"); + + /* Here start the crazies */ + replaced = FALSE; + + for (i = 0; i < G_N_ELEMENTS (aliases); i++) { + if (compare_timezones (tz, aliases[i].orig)) { + replaced = TRUE; + timezone = aliases[i].dest; + break; + } + } + + /* Try again! */ + if (!replaced) { + /* Ignore crazy solar times from the '80s */ + if (g_str_has_prefix (tz, "Asia/Riyadh") || + g_str_has_prefix (tz, "Mideast/Riyadh")) { + timezone = "Asia/Riyadh"; + replaced = TRUE; + } + } + + if (!replaced) + timezone = tz; + + ret = g_hash_table_lookup (tz_db->backward, timezone); + if (ret == NULL) + return g_strdup (timezone); + return g_strdup (ret); +} + +/* ----------------- * + * Private functions * + * ----------------- */ + +static gchar * +tz_data_file_get (void) +{ + gchar *file; + + file = g_strdup (TZ_DATA_FILE); + + return file; +} + +static float +convert_pos (gchar *pos, int digits) +{ + gchar whole[10]; + gchar *fraction; + gint i; + float t1, t2; + + if (!pos || strlen(pos) < 4 || digits > 9) return 0.0; + + for (i = 0; i < digits + 1; i++) whole[i] = pos[i]; + whole[i] = '\0'; + fraction = pos + digits + 1; + + t1 = g_strtod (whole, NULL); + t2 = g_strtod (fraction, NULL); + + if (t1 >= 0.0) return t1 + t2/pow (10.0, strlen(fraction)); + else return t1 - t2/pow (10.0, strlen(fraction)); +} + + +#if 0 + +/* Currently not working */ +static void +free_tzdata (TzLocation *tz) +{ + + if (tz->country) + g_free(tz->country); + if (tz->zone) + g_free(tz->zone); + if (tz->comment) + g_free(tz->comment); + + g_free(tz); +} +#endif + + +static int +compare_country_names (const void *a, const void *b) +{ + const TzLocation *tza = * (TzLocation **) a; + const TzLocation *tzb = * (TzLocation **) b; + + return strcmp (tza->zone, tzb->zone); +} + + +static void +sort_locations_by_country (GPtrArray *locations) +{ + qsort (locations->pdata, locations->len, sizeof (gpointer), + compare_country_names); +} + +static void +load_backward_tz (TzDB *tz_db) +{ + g_auto(GStrv) lines = NULL; + g_autoptr(GBytes) bytes = NULL; + const char *contents; + guint i; + + tz_db->backward = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + bytes = g_resources_lookup_data ("/org/gnome/control-center/datetime/backward", + G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); + contents = (const char *) g_bytes_get_data (bytes, NULL); + + lines = g_strsplit (contents, "\n", -1); + + for (i = 0; lines[i] != NULL; i++) + { + g_auto(GStrv) items = NULL; + guint j; + char *real, *alias; + + if (g_ascii_strncasecmp (lines[i], "Link\t", 5) != 0) + continue; + + items = g_strsplit (lines[i], "\t", -1); + real = NULL; + alias = NULL; + /* Skip the "Link<tab>" part */ + for (j = 1; items[j] != NULL; j++) + { + if (items[j][0] == '\0') + continue; + if (real == NULL) + { + real = items[j]; + continue; + } + alias = items[j]; + break; + } + + if (real == NULL || alias == NULL) + g_warning ("Could not parse line: %s", lines[i]); + + /* We don't need more than one name for it */ + if (g_str_equal (real, "Etc/UTC") || + g_str_equal (real, "Etc/UCT")) + real = "Etc/GMT"; + + g_hash_table_insert (tz_db->backward, g_strdup (alias), g_strdup (real)); + } +} + diff --git a/gnome-initial-setup/pages/timezone/tz.h b/gnome-initial-setup/pages/timezone/tz.h new file mode 100644 index 0000000..a2376f8 --- /dev/null +++ b/gnome-initial-setup/pages/timezone/tz.h @@ -0,0 +1,92 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Generic timezone utilities. + * + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Authors: Hans Petter Jansson <hpj@ximian.com> + * + * Largely based on Michael Fulbright's work on Anaconda. + * + * 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/>. + */ + +#pragma once + +#include <glib.h> + +G_BEGIN_DECLS + +#ifndef __sun +# define TZ_DATA_FILE "/usr/share/zoneinfo/zone.tab" +#else +# define TZ_DATA_FILE "/usr/share/lib/zoneinfo/tab/zone_sun.tab" +#endif + +typedef struct _TzDB TzDB; +typedef struct _TzLocation TzLocation; +typedef struct _TzInfo TzInfo; + + +struct _TzDB +{ + GPtrArray *locations; + GHashTable *backward; +}; + +struct _TzLocation +{ + gchar *country; + gdouble latitude; + gdouble longitude; + gchar *zone; + gchar *comment; + + gdouble dist; /* distance to clicked point for comparison */ +}; + +/* see the glibc info page information on time zone information */ +/* tzname_normal is the default name for the timezone */ +/* tzname_daylight is the name of the zone when in daylight savings */ +/* utc_offset is offset in seconds from utc */ +/* daylight if non-zero then location obeys daylight savings */ + +struct _TzInfo +{ + gchar *tzname_normal; + gchar *tzname_daylight; + glong utc_offset; + gint daylight; +}; + + +TzDB *tz_load_db (void); +void tz_db_free (TzDB *db); +char * tz_info_get_clean_name (TzDB *tz_db, + const char *tz); +GPtrArray *tz_get_locations (TzDB *db); +void tz_location_get_position (TzLocation *loc, + double *longitude, double *latitude); +char *tz_location_get_country (TzLocation *loc); +gchar *tz_location_get_zone (TzLocation *loc); +gchar *tz_location_get_comment (TzLocation *loc); +glong tz_location_get_utc_offset (TzLocation *loc); +gint tz_location_set_locally (TzLocation *loc); +TzInfo *tz_info_from_location (TzLocation *loc); +void tz_info_free (TzInfo *tz_info); + + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (TzDB, tz_db_free) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (TzInfo, tz_info_free) + +G_END_DECLS diff --git a/gnome-initial-setup/pages/welcome/gis-welcome-page.c b/gnome-initial-setup/pages/welcome/gis-welcome-page.c new file mode 100644 index 0000000..793d147 --- /dev/null +++ b/gnome-initial-setup/pages/welcome/gis-welcome-page.c @@ -0,0 +1,268 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2020 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: + * Matthias Clasen <mclasen@redhat.com> + */ + +/* Welcome page {{{1 */ + +#define PAGE_ID "welcome" + +#include "config.h" +#include "welcome-resources.h" +#include "gis-welcome-page.h" +#include "gis-assistant.h" + + +struct _GisWelcomePage +{ + GisPage parent; +}; + +typedef struct +{ + GtkWidget *header; + GtkWidget *title; +} GisWelcomePagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GisWelcomePage, gis_welcome_page, GIS_TYPE_PAGE) + +static void +update_welcome_header (GisWelcomePage *page) +{ + GisWelcomePagePrivate *priv = gis_welcome_page_get_instance_private (page); + const char *path = "/org/gnome/initial-setup/initial-setup-welcome.svg"; + g_autoptr(GdkPixbuf) pixbuf = NULL; + + pixbuf = gdk_pixbuf_new_from_resource_at_scale (path, 1000, -1, TRUE, NULL); + gtk_image_set_from_pixbuf (GTK_IMAGE (priv->header), pixbuf); +} + +typedef struct +{ + char *major; + char *minor; + char *micro; + char *distributor; + char *date; + char **current; +} VersionData; + +static void +version_data_free (VersionData *data) +{ + g_free (data->major); + g_free (data->minor); + g_free (data->micro); + g_free (data->distributor); + g_free (data->date); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (VersionData, version_data_free); + +static void +version_start_element_handler (GMarkupParseContext *ctx, + const char *element_name, + const char **attr_names, + const char **attr_values, + gpointer user_data, + GError **error) +{ + VersionData *data = user_data; + if (g_str_equal (element_name, "platform")) + data->current = &data->major; + else if (g_str_equal (element_name, "minor")) + data->current = &data->minor; + else if (g_str_equal (element_name, "micro")) + data->current = &data->micro; + else if (g_str_equal (element_name, "distributor")) + data->current = &data->distributor; + else if (g_str_equal (element_name, "date")) + data->current = &data->date; + else + data->current = NULL; +} + +static void +version_end_element_handler (GMarkupParseContext *ctx, + const char *element_name, + gpointer user_data, + GError **error) +{ + VersionData *data = user_data; + data->current = NULL; +} + +static void +version_text_handler (GMarkupParseContext *ctx, + const char *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + VersionData *data = user_data; + if (data->current != NULL) + { + g_autofree char *stripped = NULL; + + stripped = g_strstrip (g_strdup (text)); + g_free (*data->current); + *data->current = g_steal_pointer (&stripped); + } +} + +static gboolean +load_gnome_version (char **version, + char **distributor, + char **date) +{ + GMarkupParser version_parser = { + version_start_element_handler, + version_end_element_handler, + version_text_handler, + NULL, + NULL, + }; + g_autoptr(GError) error = NULL; + g_autoptr(GMarkupParseContext) ctx = NULL; + g_autofree char *contents = NULL; + gsize length; + g_autoptr(VersionData) data = NULL; + + if (!g_file_get_contents (DATADIR "/gnome/gnome-version.xml", + &contents, + &length, + &error)) + return FALSE; + + data = g_new0 (VersionData, 1); + ctx = g_markup_parse_context_new (&version_parser, 0, data, NULL); + if (!g_markup_parse_context_parse (ctx, contents, length, &error)) + { + g_warning ("Invalid version file: '%s'", error->message); + } + else + { + if (version != NULL) + { + if (strcmp (data->micro, "0") == 0) + *version = g_strdup_printf ("%s.%s", data->major, data->minor); + else + *version = g_strdup_printf ("%s.%s.%s", data->major, data->minor, data->micro); + } + + if (distributor != NULL) + *distributor = g_strdup (data->distributor); + if (date != NULL) + *date = g_strdup (data->date); + + return TRUE; + } + + return FALSE; +}; + +static void +update_welcome_title (GisWelcomePage *page) +{ + GisWelcomePagePrivate *priv = gis_welcome_page_get_instance_private (page); + g_autofree char *name = g_get_os_info (G_OS_INFO_KEY_NAME); + g_autofree char *entity = NULL; + g_autofree char *text = NULL; + + if (name != NULL) + { + g_autofree char *version = g_get_os_info (G_OS_INFO_KEY_VERSION_ID); + entity = g_strdup_printf ("%s %s", name, version); + } + else + { + g_autofree char *version = NULL; + load_gnome_version (&version, NULL, NULL); + entity = g_strdup_printf ("GNOME %s", version); + } + + /* Translators: This is meant to be a warm, engaging welcome message, + * like greeting somebody at the door. If the exclamation mark is not + * suitable for this in your language you may replace it. The space + * before the exclamation mark in this string is a typographical thin + * space (U200a) to improve the spacing in the title, which you can + * keep or remove. The %s is getting replaced with the name and version + * of the OS, e.g. "GNOME 3.38" + */ + text = g_strdup_printf (_("Welcome to %s !"), entity); + + gtk_label_set_label (GTK_LABEL (priv->title), text); +} + +static void +gis_welcome_page_constructed (GObject *object) +{ + GisWelcomePage *page = GIS_WELCOME_PAGE (object); + + G_OBJECT_CLASS (gis_welcome_page_parent_class)->constructed (object); + + update_welcome_header (page); + update_welcome_title (page); + + gis_page_set_complete (GIS_PAGE (page), TRUE); + gtk_widget_show (GTK_WIDGET (page)); +} + +static void +start_setup (GtkButton *button, GisWelcomePage *page) +{ + GisAssistant *assistant; + + assistant = GIS_ASSISTANT (gtk_widget_get_ancestor (GTK_WIDGET (page), GIS_TYPE_ASSISTANT)); + + gis_assistant_next_page (assistant); +} + +static void +gis_welcome_page_class_init (GisWelcomePageClass *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-welcome-page.ui"); + + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisWelcomePage, header); + gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisWelcomePage, title); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), start_setup); + + page_class->page_id = PAGE_ID; + object_class->constructed = gis_welcome_page_constructed; +} + +static void +gis_welcome_page_init (GisWelcomePage *page) +{ + g_resources_register (welcome_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GisPage * +gis_prepare_welcome_page (GisDriver *driver) +{ + return g_object_new (GIS_TYPE_WELCOME_PAGE, + "driver", driver, + NULL); +} diff --git a/gnome-initial-setup/pages/welcome/gis-welcome-page.h b/gnome-initial-setup/pages/welcome/gis-welcome-page.h new file mode 100644 index 0000000..73cb6e4 --- /dev/null +++ b/gnome-initial-setup/pages/welcome/gis-welcome-page.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2020 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: + * Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <glib-object.h> + +#include "gnome-initial-setup.h" + +G_BEGIN_DECLS + +#define GIS_TYPE_WELCOME_PAGE (gis_welcome_page_get_type ()) +G_DECLARE_FINAL_TYPE (GisWelcomePage, gis_welcome_page, GIS, WELCOME_PAGE, GisPage) + +GType gis_welcome_page_get_type (void); + +GisPage *gis_prepare_welcome_page (GisDriver *driver); + +G_END_DECLS diff --git a/gnome-initial-setup/pages/welcome/gis-welcome-page.ui b/gnome-initial-setup/pages/welcome/gis-welcome-page.ui new file mode 100644 index 0000000..6993640 --- /dev/null +++ b/gnome-initial-setup/pages/welcome/gis-welcome-page.ui @@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="3.0"/> + <template class="GisWelcomePage" parent="GisPage"> + <property name="title" translatable="yes">Setup</property> + <property name="margin-top">0</property> + <property name="margin-bottom">0</property> + <property name="margin-start">0</property> + <property name="margin-end">0</property> + <property name="has-forward">1</property> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="halign">fill</property> + <property name="valign">fill</property> + <child> + <object class="GtkImage" id="header"> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <property name="visible">True</property> + <property name="vexpand">1</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="spacing">20</property> + <child> + <object class="GtkLabel" id="title"> + <property name="visible">True</property> + <!-- This is set to a translated string at runtime --> + <property name="label">Welcome to the latest GNOME!</property> + <style> + <class name="title-1"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Setup will guide you through making an account and enabling some features. We’ll have you up and running in no time.</property> + <property name="wrap">1</property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="justify">center</property> + </object> + </child> + <child> + <object class="GtkButton"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="label" translatable="yes">_Start Setup</property> + <property name="use-underline">1</property> + <signal name="clicked" handler="start_setup"/> + <style> + <class name="suggested-action"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/gnome-initial-setup/pages/welcome/initial-setup-welcome.svg b/gnome-initial-setup/pages/welcome/initial-setup-welcome.svg new file mode 100644 index 0000000..9edc14f --- /dev/null +++ b/gnome-initial-setup/pages/welcome/initial-setup-welcome.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1920" height="754"><defs><linearGradient xlink:href="#c" id="m" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.3 0 0 .83333 -19.2 34)" x1="64" y1="180" x2="64" y2="250"/><linearGradient y2="250" x2="64" y1="180" x1="64" gradientTransform="matrix(1.3 0 0 .83333 -19.2 34)" gradientUnits="userSpaceOnUse" id="k" xlink:href="#c"/><linearGradient id="a"><stop offset="0" stop-color="#d0bb8e"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient xlink:href="#b" id="g" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.3 0 0 1 -19.2 16.87)" x1="64" y1="254" x2="64" y2="292"/><linearGradient id="b"><stop offset="0" stop-color="#cdab8f"/><stop offset="1" stop-color="#d3b69d"/></linearGradient><linearGradient xlink:href="#b" id="l" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.3 0 0 1 -19.2 -4)" x1="64" y1="254" x2="64" y2="292"/><linearGradient xlink:href="#a" id="e" gradientUnits="userSpaceOnUse" x1="128.817" y1="-97.698" x2="158.518" y2="-127.399" gradientTransform="matrix(-.03667 .05186 .03667 .05186 180.124 169.116)"/><linearGradient id="c"><stop offset="0" stop-color="#e3cfbf"/><stop offset="1" stop-color="#e7d5c7"/></linearGradient><linearGradient y2="292" x2="64" y1="254" x1="64" gradientTransform="matrix(1.3 0 0 1 -19.2 -4)" gradientUnits="userSpaceOnUse" id="h" xlink:href="#b"/><linearGradient y2="250" x2="64" y1="180" x1="64" gradientTransform="matrix(1.3 0 0 .83333 -19.2 34)" gradientUnits="userSpaceOnUse" id="i" xlink:href="#c"/><linearGradient y2="292" x2="64" y1="254" x1="64" gradientTransform="matrix(1.3 0 0 1 -19.2 -4)" gradientUnits="userSpaceOnUse" id="j" xlink:href="#b"/><clipPath clipPathUnits="userSpaceOnUse" id="d"><path d="M2330.729-970.526h1007.5V-1366.5h-1007.5z" style="marker:none" color="#000" overflow="visible" fill="#4a86cf" stroke-width="1.357"/></clipPath></defs><path d="M0 754h1920V0H0z" style="marker:none" color="#000" overflow="visible" fill="#1c71d8"/><g clip-path="url(#d)" transform="translate(-4440.203 2603.279) scale(1.90507)"><g transform="matrix(0 2.88666 -2.88666 0 3741.302 -1534.524)"><path style="marker:none" d="M170.003 170.124l-1.993 1.993h3.986z" fill="url(#e)"/><path d="M170.003 170.124l-.797.797h1.595z" style="marker:none" fill="#424048"/><path d="M171.598 172.117h-3.189v27.633h3.189z" fill="#f6d32d"/><path d="M169.184 172.117h-1.174v27.643l1.174-.01z" fill="#f9f06b"/><path d="M170.823 172.117h1.173v27.643l-1.173-.01z" fill="#e5a50a"/><rect ry="1.554" rx="1.554" y="199.76" x="168.01" height="3.969" width="3.969" style="marker:none" fill="#f66151"/><path style="marker:none" d="M169.565 199.76c-.056 0-.111.003-.166.01a1.55 1.55 0 011.39 1.545v.86a1.55 1.55 0 01-1.39 1.545c.055.006.11.01.166.01h.86c.86 0 1.554-.694 1.554-1.555v-.86c0-.861-.693-1.555-1.554-1.555z" fill="#ed333b"/><rect style="marker:none" width="3.969" height="1.323" x="168.01" y="199.76" rx="0" ry="0" fill="#f6f5f4"/><rect ry="0" rx="0" y="199.76" x="170.789" height="1.323" width="1.191" style="marker:none" fill="#deddda"/></g><g transform="matrix(2.19794 0 0 2.19794 2477.53 -1455.549)"><rect style="marker:none" width="23.283" height="35.454" x="248.708" y="158.75" rx="11.642" ry="11.642" fill-opacity=".102"/><rect ry="11.642" rx="11.642" y="157.692" x="248.708" height="35.454" width="23.283" style="marker:none" fill="#deddda"/><rect style="marker:none" width="23.283" height="35.454" x="248.708" y="156.104" rx="11.642" ry="11.642" fill="#f6f5f4"/><path d="M260.615 170.656v-14.552" fill="none" stroke="#c0bfbc" stroke-width=".265"/><rect style="marker:none" width="5.292" height="13.229" x="257.969" y="160.073" rx="2.646" ry="2.646" fill="#77767b"/><circle r="1.455" cy="162.003" cx="260.603" style="marker:none" fill="#c0bfbc" fill-opacity=".102"/></g><g transform="scale(.95548) rotate(45 2986.865 2708.27)"><path d="M69.202 198.092c-2.25 2.117-4.37 6.101-4.37 13.602l-.014 34.5-10.179 10.178a1.92 1.92 0 00-1.343.158l-12.125 6.088c-4.633-2.888-10.848-2.327-14.864 1.688-4.671 4.672-4.672 12.321 0 16.993 4.67 4.671 12.32 4.671 16.992 0l.043-.044.005.045 15.556-15.556.017.017 5.892-5.896-.004 7.829h.025v21.999l.035-.028v.06c0 6.606 5.41 12.016 12.016 12.016s12.014-5.409 12.014-12.015c0-5.68-3.998-10.471-9.317-11.705l-4.269-12.879a1.92 1.92 0 00-.838-1.06v-13.889l24.027-24.047c11.314-11.314 4.496-18.132 4.496-18.132l-28.523 28.523v-40.845s-2.72 0-5.272 2.4zm-39.36 69.75a6.978 6.978 0 019.922 0 6.979 6.979 0 010 9.921 6.979 6.979 0 01-9.922 0 6.978 6.978 0 010-9.921zm42.069 16.91a6.994 6.994 0 014.972-2.043 6.979 6.979 0 017.015 7.017 6.978 6.978 0 01-7.015 7.015 6.979 6.979 0 01-7.016-7.016c0-1.952.778-3.706 2.044-4.972z" style="marker:none" fill-opacity=".102" fill-rule="evenodd"/><path d="M65.995 268.962h6v-72l-1.415-1.414s-6.585 5.061-6.585 17.414l.005 27.19.097 2.723 1.898 2.087z" fill="#9a9996" fill-rule="evenodd"/><use xlink:href="#f" transform="scale(-1 1) rotate(-45 -.464 413.359)" width="100%" height="100%"/><g id="f" display="inline"><path d="M55.975 290h9.667v-72S56 218 56 234z" fill="#fff" fill-rule="evenodd" transform="scale(-1 1) rotate(-45 -31.3 415.202)"/><g transform="scale(-1 1) rotate(-45 -32.3 412.788)"><path d="M54 312v-24c0-2 2-2 2-2h6.037s1.918-.136 2.444 1.45l4.397 13.27z" fill="#ed333b" fill-rule="evenodd"/><circle r="9.516" cy="312.033" cx="-66.05" transform="scale(-1 1)" fill="none" stroke="#ed333b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/><circle cx="-66" cy="312" r="7" transform="scale(-1 1)" fill="none" stroke="#e01b24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g></g><circle transform="scale(-1 1) rotate(-45)" cx="-223.446" cy="125.865" r="2" fill="#3d3846"/></g><g transform="matrix(0 2.88666 2.88666 0 2747.647 -1526.123)"><rect style="marker:none" width="3.44" height="2.117" x="173.567" y="198.702" rx=".468" ry=".468" fill="#1a5fb4"/><path style="marker:none" d="M174.805 157.427h.963c.1 0 .164.083.18.184l1.058 6.482c.043.261-.208.478-.467.478h-2.505c-.259 0-.51-.217-.467-.478l1.058-6.482c.016-.1.08-.184.18-.184z" fill="#1a5fb4"/><path style="marker:none" fill="#fff" d="M173.302 164.042h3.969v35.719h-3.969z"/><rect style="marker:none" width=".794" height="2.381" x="174.89" y="155.84" rx=".397" ry=".468" fill="#1a5fb4"/><rect style="marker:none" width="1.323" height="1.058" x="174.625" y="156.633" rx="0" ry="0" fill="#fff"/><path style="marker:none" fill="#f6f5f4" d="M175.948 164.042h1.323v35.719h-1.323z"/></g><g transform="matrix(2.43265 0 0 2.43265 2602.769 -1538.917)"><rect style="marker:none" width="95.25" height="58.208" x="47.625" y="84.667" rx="2.884" ry="2.884"/><rect ry="2.884" rx="2.884" y="145.256" x="47.625" height="58.208" width="95.25" style="marker:none" fill-opacity=".102"/><rect style="marker:none" width="95.25" height="58.208" x="47.625" y="144.198" rx="2.884" ry="2.884" fill="#deddda"/><rect ry="2.884" rx="2.884" y="142.875" x="47.625" height="58.208" width="95.25" style="marker:none" fill="#f6f5f4"/><rect style="marker:none" width="84.667" height="29.104" x="52.917" y="148.167" rx="1.958" ry="1.958" fill="#c0bfbc"/><rect ry="1.958" rx="1.958" y="148.696" x="52.917" height="29.104" width="84.667" style="marker:none" fill="#deddda"/><rect ry=".826" rx=".826" y="137.583" x="63.5" height="7.408" width="63.5" style="marker:none"/><rect ry="2.884" rx="2.884" y="179.917" x="82.021" height="15.875" width="26.458" style="marker:none" fill="#deddda"/><rect ry="1.574" rx="1.574" y="86.284" x="49.158" height="54.975" width="92.183" style="marker:none" fill="#62a0ea"/><rect style="marker:none" width="46.302" height="5.292" x="74.083" y="170.656" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="170.656" x="121.708" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="129.646" y="170.656" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="164.042" x="125.677" height="5.292" width="5.292" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="117.74" y="164.042" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="164.042" x="109.802" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="101.865" y="164.042" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="164.042" x="93.927" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="85.99" y="164.042" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="164.042" x="78.052" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="70.115" y="164.042" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="164.042" x="62.177" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="121.708" y="157.427" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="157.427" x="113.771" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="105.833" y="157.427" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="157.427" x="97.896" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="89.958" y="157.427" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="157.427" x="82.021" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="74.083" y="157.427" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="157.427" x="66.146" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="10.583" height="5.292" x="54.24" y="157.427" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="150.813" x="125.677" height="5.292" width="10.583" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="117.74" y="150.813" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="150.813" x="109.802" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="101.865" y="150.813" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="150.813" x="93.927" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="85.99" y="150.813" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="150.813" x="78.052" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="70.115" y="150.813" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="150.813" x="62.177" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="54.24" y="150.813" rx="1.013" ry="1.013" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="54.24" y="164.042" rx="1.013" ry="1.013" fill="#241f31"/><rect style="marker:none" width="6.615" height="5.292" x="66.146" y="170.656" rx="1.013" ry="1.013" fill="#241f31"/><rect ry="1.013" rx="1.013" y="170.656" x="54.24" height="5.292" width="10.583" style="marker:none" fill="#241f31"/><rect ry="1.013" rx="1.013" y="157.427" x="129.646" height="5.292" width="6.615" style="marker:none" fill="#241f31"/><rect style="marker:none" width="3.969" height="11.906" x="132.292" y="157.427" rx="1.013" ry="1.013" fill="#241f31"/><g stroke-width=".892" fill="#fff"><path d="M112.497 102.02l12.984-12.984v12.985zM125.48 102.021l12.053 12.053H125.48z" opacity=".2"/><path d="M101.339 138.053l12.053-12.062h-12.053z" opacity=".1"/><path d="M125.445 113.937l-12.053 12.053h12.053z" opacity=".2"/></g></g><g transform="matrix(2.09898 0 0 2.09898 3311.213 -1533.99)"><g transform="matrix(.5269 0 0 .5269 161.93 58.874)"><rect ry="8" rx="8" y="190.009" x="-348" height="104" width="88" style="marker:none" fill-opacity=".102"/><rect style="marker:none" width="88" height="104" x="-348" y="188" rx="8" ry="8" fill="#1e737e"/><rect ry="8" rx="8" y="188" x="-348" height="102" width="88" style="marker:none" fill="#f6f5f4"/><path d="M-340.177 188h64.72a7.805 7.805 0 017.823 7.822l-.102 94.178h-72.441a7.805 7.805 0 01-7.823-7.822v-86.356a7.805 7.805 0 017.823-7.822z" style="marker:none" fill="#fff"/><rect ry="8" rx="8" y="180" x="-348" height="100" width="88" style="marker:none" fill="#1e737e"/><rect style="marker:none" width="88" height="98" x="-348" y="180" rx="8" ry="8" fill="#27a0a4"/></g><g transform="matrix(.5269 0 0 .5269 -31.97 60.455)" fill="#3d3846" stroke="#241f31" stroke-width="2"><circle cx="32" cy="187" r="3"/><circle r="3" cy="187" cx="96"/><circle cx="48" cy="187" r="3"/><circle cx="64" cy="187" r="3"/><circle r="3" cy="187" cx="80"/></g><path d="M-15.125 149.486a1.054 1.054 0 00-1.037 1.069v8.43a1.054 1.054 0 102.107 0v-8.43a1.054 1.054 0 00-1.07-1.069zm8.432 0a1.054 1.054 0 00-1.04 1.069v8.43a1.054 1.054 0 102.108 0v-8.43a1.054 1.054 0 00-1.068-1.069zm8.43 0a1.054 1.054 0 00-1.038 1.069v8.43a1.054 1.054 0 102.108 0v-8.43a1.054 1.054 0 00-1.07-1.069zm8.429 0a1.054 1.054 0 00-1.037 1.069v8.43a1.054 1.054 0 102.107 0v-8.43a1.054 1.054 0 00-1.07-1.069zm8.432 0a1.054 1.054 0 00-1.04 1.069v8.43a1.054 1.054 0 102.108 0v-8.43a1.054 1.054 0 00-1.068-1.069z" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1" color="#000" font-weight="400" font-family="sans-serif" overflow="visible" fill="#deddda"/><circle r="1.058" cy="151.642" cx="-15.081" style="marker:none" fill="#fff"/><circle style="marker:none" cx="-6.639" cy="151.642" r="1.058" fill="#fff"/><circle r="1.058" cy="151.642" cx="1.757" style="marker:none" fill="#fff"/><circle style="marker:none" cx="10.199" cy="151.642" r="1.058" fill="#fff"/><circle r="1.058" cy="151.642" cx="18.665" style="marker:none" fill="#fff"/></g><g transform="matrix(.93209 0 0 .93209 -1000.99 4648.193)"><path fill="url(#g)" d="M12 232.87h104v76H12z" transform="translate(3662.15 -6599.407) scale(1.38952)"/><path fill="#ac815b" d="M3678.824-6343.47h144.51v93.972h-144.51z"/><path style="marker:none" opacity=".211" fill="#fff" d="M3678.824-6249.498h144.51v2.779h-144.51z"/><path d="M3823.141-6248.491v-95.055l35.515 20.505v92.076z" fill="#e7d5c7"/><path style="marker:none" fill="#7d5738" d="M3678.868-6342.373h144.07v53.989h-144.07z"/><path d="M3678.859-6248.491v-95.055l-35.515 20.505v92.076z" fill="#e7d5c7"/><rect ry="0" rx="0" y="-6343.605" x="3678.625" height="1.25" width="144.375" style="marker:none" fill="#e7d5c7"/><path d="M3679-6249.48h145v39h-145z" style="marker:none" fill="#e7d5c7"/></g><path style="marker:none" fill-opacity=".102" d="M2210.672-1079.515h130.66v85.432h-130.66z"/><path fill="url(#h)" d="M12 212h104v76H12z" transform="translate(2195.596 -1360.94) scale(1.25635)"/><path fill="url(#i)" d="M12 184h104v68H12z" transform="translate(2195.596 -1360.94) scale(1.25635)"/><path style="marker:none" opacity=".05" d="M2210.672-1044.339h130.66v2.513h-130.66z"/><path style="marker:none" opacity=".2" fill="#fff" d="M2213.184-1129.77h2.513v85.431h-2.513zM2336.307-1129.77h2.513v85.431h-2.513z"/><path style="marker:none" fill-opacity=".102" d="M2348.87-1079.515h130.66v85.432h-130.66z"/><path fill="url(#j)" d="M12 212h104v76H12z" transform="translate(2333.794 -1360.94) scale(1.25635)"/><path fill="url(#k)" d="M12 184h104v68H12z" transform="translate(2333.794 -1360.94) scale(1.25635)"/><path style="marker:none" opacity=".05" d="M2348.87-1044.339h130.66v2.513h-130.66z"/><path style="marker:none" opacity=".2" fill="#fff" d="M2351.383-1129.77h2.513v85.431h-2.513zM2474.505-1129.77h2.513v85.431h-2.513z"/><path style="marker:none" d="M2257.81-1130.138h83.672v85.76h-77.875l-5.796-4.96z" fill-opacity=".102"/><path style="marker:none" fill="#9a9996" d="M2341.332-1094.593v8.206h-130.66v-8.206zM2421.596-1085.003v8.205h-85.316v-8.205z"/><path style="marker:none" fill="#77767b" d="M2271.9-1044.338h8.205v45.129h-8.205z"/><path style="marker:none" d="M2349.05-1130.138h40.555l24.593 26.797v58.964h-65.149z" fill-opacity=".102"/><path style="marker:none" fill="#9a9996" d="M2271.9-1129.653h8.205v85.315h-8.205z"/><g><path fill="url(#l)" d="M12 212h104v76H12z" transform="translate(2242.906 -1411.193) scale(1.25635)"/><path fill="url(#m)" d="M12 184h104v68H12z" transform="translate(2242.906 -1411.193) scale(1.25635)"/><path style="marker:none" opacity=".05" d="M2257.982-1094.593h130.66v2.513h-130.66z"/><path style="marker:none" opacity=".2" fill="#fff" d="M2260.495-1180.025h2.512v85.432h-2.512zM2383.617-1180.025h2.513v85.432h-2.513z"/></g><path style="marker:none" fill="#77767b" d="M2403.555-1044.455h8.205v45.13h-8.205z"/><path style="marker:none" fill="#9a9996" d="M2403.555-1129.77h8.205v85.315h-8.205z"/><path style="marker:none" opacity=".136" d="M2430.86-1084.892h48.954v7.797h-48.955z"/><path style="marker:none" fill="#9a9996" d="M2479.472-1085.481v8.205L2430.116-1093v-8.205zM2388.644-1144.845v8.205h-130.66v-8.205z"/><path style="marker:none" fill="#77767b" d="M2319.209-1094.593h8.205v45.13h-8.205z"/><path style="marker:none" fill="#9a9996" d="M2319.209-1179.908h8.205v85.315h-8.205z"/></g></svg>
\ No newline at end of file diff --git a/gnome-initial-setup/pages/welcome/meson.build b/gnome-initial-setup/pages/welcome/meson.build new file mode 100644 index 0000000..be977dc --- /dev/null +++ b/gnome-initial-setup/pages/welcome/meson.build @@ -0,0 +1,10 @@ +sources += gnome.compile_resources( + 'welcome-resources', + files('welcome.gresource.xml'), + c_name: 'welcome', +) + +sources += files( + 'gis-welcome-page.c', + 'gis-welcome-page.h', +) diff --git a/gnome-initial-setup/pages/welcome/welcome.gresource.xml b/gnome-initial-setup/pages/welcome/welcome.gresource.xml new file mode 100644 index 0000000..9a2f9c6 --- /dev/null +++ b/gnome-initial-setup/pages/welcome/welcome.gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/initial-setup"> + <file preprocess="xml-stripblanks">gis-welcome-page.ui</file> + <file preprocess="xml-stripblanks">initial-setup-welcome.svg</file> + </gresource> +</gresources> |