summaryrefslogtreecommitdiffstats
path: root/gnome-initial-setup
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:09:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:09:04 +0000
commit829aea9a4dce048f63c6d9568b50ab4bdb7609e3 (patch)
tree7c5d5ac80e81dc5186f19649f48387a67df20425 /gnome-initial-setup
parentInitial commit. (diff)
downloadgnome-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 '')
-rw-r--r--gnome-initial-setup.doap31
-rw-r--r--gnome-initial-setup/cc-common-language.c318
-rw-r--r--gnome-initial-setup/cc-common-language.h37
-rw-r--r--gnome-initial-setup/gis-assistant.c538
-rw-r--r--gnome-initial-setup/gis-assistant.gresource.xml8
-rw-r--r--gnome-initial-setup/gis-assistant.h67
-rw-r--r--gnome-initial-setup/gis-assistant.ui102
-rw-r--r--gnome-initial-setup/gis-driver.c1031
-rw-r--r--gnome-initial-setup/gis-driver.h148
-rw-r--r--gnome-initial-setup/gis-keyring.c104
-rw-r--r--gnome-initial-setup/gis-keyring.h35
-rw-r--r--gnome-initial-setup/gis-page-header.c203
-rw-r--r--gnome-initial-setup/gis-page-header.css13
-rw-r--r--gnome-initial-setup/gis-page-header.h36
-rw-r--r--gnome-initial-setup/gis-page-header.ui42
-rw-r--r--gnome-initial-setup/gis-page.c426
-rw-r--r--gnome-initial-setup/gis-page.h93
-rw-r--r--gnome-initial-setup/gnome-initial-setup-copy-worker.c99
-rw-r--r--gnome-initial-setup/gnome-initial-setup.c370
-rw-r--r--gnome-initial-setup/gnome-initial-setup.h42
-rw-r--r--gnome-initial-setup/meson.build76
-rw-r--r--gnome-initial-setup/pages/account/account.gresource.xml10
-rw-r--r--gnome-initial-setup/pages/account/gis-account-avatar-chooser.ui48
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-enterprise.c855
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-enterprise.h63
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-enterprise.ui447
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-local.c721
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-local.h60
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-local.ui182
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page-style.css6
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page.c318
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page.h55
-rw-r--r--gnome-initial-setup/pages/account/gis-account-page.ui64
-rw-r--r--gnome-initial-setup/pages/account/gis-account-pages.c32
-rw-r--r--gnome-initial-setup/pages/account/gis-account-pages.h36
-rw-r--r--gnome-initial-setup/pages/account/meson.build33
-rw-r--r--gnome-initial-setup/pages/account/org.freedesktop.realmd.xml666
-rw-r--r--gnome-initial-setup/pages/account/um-photo-dialog.c475
-rw-r--r--gnome-initial-setup/pages/account/um-photo-dialog.h49
-rw-r--r--gnome-initial-setup/pages/account/um-realm-manager.c878
-rw-r--r--gnome-initial-setup/pages/account/um-realm-manager.h108
-rw-r--r--gnome-initial-setup/pages/account/um-utils.c648
-rw-r--r--gnome-initial-setup/pages/account/um-utils.h63
-rw-r--r--gnome-initial-setup/pages/goa/gis-goa-page.c391
-rw-r--r--gnome-initial-setup/pages/goa/gis-goa-page.h55
-rw-r--r--gnome-initial-setup/pages/goa/gis-goa-page.ui66
-rw-r--r--gnome-initial-setup/pages/goa/goa.gresource.xml7
-rw-r--r--gnome-initial-setup/pages/goa/meson.build10
-rw-r--r--gnome-initial-setup/pages/keyboard/cc-ibus-utils.c43
-rw-r--r--gnome-initial-setup/pages/keyboard/cc-ibus-utils.h29
-rw-r--r--gnome-initial-setup/pages/keyboard/cc-input-chooser.c910
-rw-r--r--gnome-initial-setup/pages/keyboard/cc-input-chooser.h65
-rw-r--r--gnome-initial-setup/pages/keyboard/gis-keyboard-page.c541
-rw-r--r--gnome-initial-setup/pages/keyboard/gis-keyboard-page.h70
-rw-r--r--gnome-initial-setup/pages/keyboard/gis-keyboard-page.ui33
-rw-r--r--gnome-initial-setup/pages/keyboard/input-chooser.ui32
-rw-r--r--gnome-initial-setup/pages/keyboard/keyboard.gresource.xml7
-rw-r--r--gnome-initial-setup/pages/keyboard/meson.build14
-rw-r--r--gnome-initial-setup/pages/language/cc-language-chooser.c624
-rw-r--r--gnome-initial-setup/pages/language/cc-language-chooser.h63
-rw-r--r--gnome-initial-setup/pages/language/cc-util.c105
-rw-r--r--gnome-initial-setup/pages/language/cc-util.h28
-rw-r--r--gnome-initial-setup/pages/language/gis-language-page.c312
-rw-r--r--gnome-initial-setup/pages/language/gis-language-page.h57
-rw-r--r--gnome-initial-setup/pages/language/gis-language-page.ui46
-rw-r--r--gnome-initial-setup/pages/language/gis-welcome-widget.c245
-rw-r--r--gnome-initial-setup/pages/language/gis-welcome-widget.h56
-rw-r--r--gnome-initial-setup/pages/language/gis-welcome-widget.ui15
-rw-r--r--gnome-initial-setup/pages/language/language-chooser.ui32
-rw-r--r--gnome-initial-setup/pages/language/language.gresource.xml10
-rw-r--r--gnome-initial-setup/pages/language/meson.build16
-rw-r--r--gnome-initial-setup/pages/meson.build20
-rw-r--r--gnome-initial-setup/pages/network/gis-network-page.c853
-rw-r--r--gnome-initial-setup/pages/network/gis-network-page.h55
-rw-r--r--gnome-initial-setup/pages/network/gis-network-page.ui118
-rw-r--r--gnome-initial-setup/pages/network/meson.build12
-rw-r--r--gnome-initial-setup/pages/network/network-dialogs.c517
-rw-r--r--gnome-initial-setup/pages/network/network-dialogs.h41
-rw-r--r--gnome-initial-setup/pages/network/network.gresource.xml6
-rw-r--r--gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.c246
-rw-r--r--gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.h38
-rw-r--r--gnome-initial-setup/pages/parental-controls/gis-parental-controls-page.ui28
-rw-r--r--gnome-initial-setup/pages/parental-controls/meson.build10
-rw-r--r--gnome-initial-setup/pages/parental-controls/parental-controls.gresource.xml6
-rw-r--r--gnome-initial-setup/pages/password/account-resources.h7
-rw-r--r--gnome-initial-setup/pages/password/gis-password-page.c504
-rw-r--r--gnome-initial-setup/pages/password/gis-password-page.css16
-rw-r--r--gnome-initial-setup/pages/password/gis-password-page.h58
-rw-r--r--gnome-initial-setup/pages/password/gis-password-page.ui173
-rw-r--r--gnome-initial-setup/pages/password/meson.build13
-rw-r--r--gnome-initial-setup/pages/password/password.gresource.xml7
-rw-r--r--gnome-initial-setup/pages/password/pw-utils.c164
-rw-r--r--gnome-initial-setup/pages/password/pw-utils.h29
-rw-r--r--gnome-initial-setup/pages/privacy/gis-privacy-page.c285
-rw-r--r--gnome-initial-setup/pages/privacy/gis-privacy-page.h57
-rw-r--r--gnome-initial-setup/pages/privacy/gis-privacy-page.ui128
-rw-r--r--gnome-initial-setup/pages/privacy/meson.build10
-rw-r--r--gnome-initial-setup/pages/privacy/privacy.gresource.xml6
-rw-r--r--gnome-initial-setup/pages/summary/gis-summary-page.c300
-rw-r--r--gnome-initial-setup/pages/summary/gis-summary-page.h55
-rw-r--r--gnome-initial-setup/pages/summary/gis-summary-page.ui85
-rw-r--r--gnome-initial-setup/pages/summary/meson.build10
-rw-r--r--gnome-initial-setup/pages/summary/ready-to-go.svg1
-rw-r--r--gnome-initial-setup/pages/summary/summary.gresource.xml8
-rw-r--r--gnome-initial-setup/pages/timezone/backward128
-rw-r--r--gnome-initial-setup/pages/timezone/cc-timezone-map.c691
-rw-r--r--gnome-initial-setup/pages/timezone/cc-timezone-map.h39
-rw-r--r--gnome-initial-setup/pages/timezone/data/bg.pngbin0 -> 213448 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/bg_dim.pngbin0 -> 95862 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/cc.pngbin0 -> 51482 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/pin.pngbin0 -> 666 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-1.pngbin0 -> 8012 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-10.pngbin0 -> 7783 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-10_dim.pngbin0 -> 5139 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-11.pngbin0 -> 8347 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-11_dim.pngbin0 -> 4815 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-1_dim.pngbin0 -> 4953 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-2.pngbin0 -> 4333 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-2_dim.pngbin0 -> 2670 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-3.5.pngbin0 -> 740 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-3.5_dim.pngbin0 -> 995 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-3.pngbin0 -> 13615 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-3_dim.pngbin0 -> 8773 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-4.pngbin0 -> 16851 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-4_dim.pngbin0 -> 9785 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-5.5.pngbin0 -> 437 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-5.5_dim.pngbin0 -> 859 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-5.pngbin0 -> 19166 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-5_dim.pngbin0 -> 12224 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-6.pngbin0 -> 13764 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-6_dim.pngbin0 -> 8833 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-7.pngbin0 -> 11977 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-7_dim.pngbin0 -> 7868 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-8.pngbin0 -> 6801 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-8_dim.pngbin0 -> 4261 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-9.5.pngbin0 -> 437 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-9.5_dim.pngbin0 -> 859 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-9.pngbin0 -> 7908 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_-9_dim.pngbin0 -> 4972 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_0.pngbin0 -> 11074 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_0_dim.pngbin0 -> 7074 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_1.pngbin0 -> 15458 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_10.5.pngbin0 -> 421 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_10.5_dim.pngbin0 -> 844 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_10.pngbin0 -> 12829 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_10_dim.pngbin0 -> 8395 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_11.5.pngbin0 -> 446 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_11.5_dim.pngbin0 -> 868 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_11.pngbin0 -> 12113 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_11_dim.pngbin0 -> 6744 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_12.75.pngbin0 -> 409 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_12.75_dim.pngbin0 -> 846 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_12.pngbin0 -> 7130 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_12_dim.pngbin0 -> 3935 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_13.pngbin0 -> 621 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_13_dim.pngbin0 -> 876 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_14.pngbin0 -> 7722 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_14_dim.pngbin0 -> 4150 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_1_dim.pngbin0 -> 10187 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_2.pngbin0 -> 12854 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_2_dim.pngbin0 -> 8709 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_3.5.pngbin0 -> 2142 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_3.5_dim.pngbin0 -> 1781 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_3.pngbin0 -> 17475 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_3_dim.pngbin0 -> 9877 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_4.5.pngbin0 -> 1773 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_4.5_dim.pngbin0 -> 1385 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_4.pngbin0 -> 4954 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_4_dim.pngbin0 -> 2754 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_5.5.pngbin0 -> 5692 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_5.5_dim.pngbin0 -> 3471 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_5.75.pngbin0 -> 2885 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_5.75_dim.pngbin0 -> 1596 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_5.pngbin0 -> 14539 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_5_dim.pngbin0 -> 8117 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_6.5.pngbin0 -> 1609 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_6.5_dim.pngbin0 -> 1675 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_6.pngbin0 -> 7654 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_6_dim.pngbin0 -> 4584 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_7.pngbin0 -> 14412 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_7_dim.pngbin0 -> 7972 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_8.75.pngbin0 -> 13993 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_8.75_dim.pngbin0 -> 7064 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_8.pngbin0 -> 16050 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_8_dim.pngbin0 -> 9378 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_9.5.pngbin0 -> 1959 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_9.5_dim.pngbin0 -> 1611 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_9.pngbin0 -> 14366 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/data/timezone_9_dim.pngbin0 -> 8362 bytes
-rw-r--r--gnome-initial-setup/pages/timezone/datetime.gresource.xml88
-rw-r--r--gnome-initial-setup/pages/timezone/gis-bubble-widget.c146
-rw-r--r--gnome-initial-setup/pages/timezone/gis-bubble-widget.css8
-rw-r--r--gnome-initial-setup/pages/timezone/gis-bubble-widget.h53
-rw-r--r--gnome-initial-setup/pages/timezone/gis-bubble-widget.ui39
-rw-r--r--gnome-initial-setup/pages/timezone/gis-timezone-page.c537
-rw-r--r--gnome-initial-setup/pages/timezone/gis-timezone-page.h57
-rw-r--r--gnome-initial-setup/pages/timezone/gis-timezone-page.ui74
-rw-r--r--gnome-initial-setup/pages/timezone/meson.build28
-rw-r--r--gnome-initial-setup/pages/timezone/timedated1-interface.xml28
-rw-r--r--gnome-initial-setup/pages/timezone/timezone.gresource.xml8
-rw-r--r--gnome-initial-setup/pages/timezone/tz.c470
-rw-r--r--gnome-initial-setup/pages/timezone/tz.h92
-rw-r--r--gnome-initial-setup/pages/welcome/gis-welcome-page.c268
-rw-r--r--gnome-initial-setup/pages/welcome/gis-welcome-page.h37
-rw-r--r--gnome-initial-setup/pages/welcome/gis-welcome-page.ui67
-rw-r--r--gnome-initial-setup/pages/welcome/initial-setup-welcome.svg1
-rw-r--r--gnome-initial-setup/pages/welcome/meson.build10
-rw-r--r--gnome-initial-setup/pages/welcome/welcome.gresource.xml7
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 (&current_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
new file mode 100644
index 0000000..4180ee8
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/bg.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/bg_dim.png b/gnome-initial-setup/pages/timezone/data/bg_dim.png
new file mode 100644
index 0000000..3d34b94
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/bg_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/cc.png b/gnome-initial-setup/pages/timezone/data/cc.png
new file mode 100644
index 0000000..e2eff2b
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/cc.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/pin.png b/gnome-initial-setup/pages/timezone/data/pin.png
new file mode 100644
index 0000000..40dd4ea
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/pin.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-1.png b/gnome-initial-setup/pages/timezone/data/timezone_-1.png
new file mode 100644
index 0000000..fb00d83
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-1.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-10.png b/gnome-initial-setup/pages/timezone/data/timezone_-10.png
new file mode 100644
index 0000000..472eb88
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-10.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-10_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-10_dim.png
new file mode 100644
index 0000000..7dd94f4
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-10_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-11.png b/gnome-initial-setup/pages/timezone/data/timezone_-11.png
new file mode 100644
index 0000000..1da3536
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-11.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-11_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-11_dim.png
new file mode 100644
index 0000000..3ed7bb4
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-11_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-1_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-1_dim.png
new file mode 100644
index 0000000..5a1fd9f
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-1_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-2.png b/gnome-initial-setup/pages/timezone/data/timezone_-2.png
new file mode 100644
index 0000000..30a1ec7
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-2.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-2_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-2_dim.png
new file mode 100644
index 0000000..da78e75
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-2_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-3.5.png b/gnome-initial-setup/pages/timezone/data/timezone_-3.5.png
new file mode 100644
index 0000000..c1df00b
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-3.5.png
Binary files differ
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
new file mode 100644
index 0000000..a72375c
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-3.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-3.png b/gnome-initial-setup/pages/timezone/data/timezone_-3.png
new file mode 100644
index 0000000..c22dbb6
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-3.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-3_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-3_dim.png
new file mode 100644
index 0000000..be9e495
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-3_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-4.png b/gnome-initial-setup/pages/timezone/data/timezone_-4.png
new file mode 100644
index 0000000..a3a8dc1
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-4.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-4_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-4_dim.png
new file mode 100644
index 0000000..d3186c8
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-4_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-5.5.png b/gnome-initial-setup/pages/timezone/data/timezone_-5.5.png
new file mode 100644
index 0000000..b1c788d
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-5.5.png
Binary files differ
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
new file mode 100644
index 0000000..cde398b
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-5.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-5.png b/gnome-initial-setup/pages/timezone/data/timezone_-5.png
new file mode 100644
index 0000000..06c15e6
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-5.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-5_dim.png
new file mode 100644
index 0000000..85a0325
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-6.png b/gnome-initial-setup/pages/timezone/data/timezone_-6.png
new file mode 100644
index 0000000..8505fb1
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-6.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-6_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-6_dim.png
new file mode 100644
index 0000000..e6de4c6
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-6_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-7.png b/gnome-initial-setup/pages/timezone/data/timezone_-7.png
new file mode 100644
index 0000000..fec235d
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-7.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-7_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-7_dim.png
new file mode 100644
index 0000000..aad26c0
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-7_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-8.png b/gnome-initial-setup/pages/timezone/data/timezone_-8.png
new file mode 100644
index 0000000..bdad7bf
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-8.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-8_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-8_dim.png
new file mode 100644
index 0000000..7caeec7
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-8_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-9.5.png b/gnome-initial-setup/pages/timezone/data/timezone_-9.5.png
new file mode 100644
index 0000000..b1c788d
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-9.5.png
Binary files differ
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
new file mode 100644
index 0000000..9b1b71c
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-9.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-9.png b/gnome-initial-setup/pages/timezone/data/timezone_-9.png
new file mode 100644
index 0000000..04cb3cb
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-9.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_-9_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_-9_dim.png
new file mode 100644
index 0000000..578b1bd
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_-9_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_0.png b/gnome-initial-setup/pages/timezone/data/timezone_0.png
new file mode 100644
index 0000000..e59b773
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_0.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_0_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_0_dim.png
new file mode 100644
index 0000000..13e7ac9
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_0_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_1.png b/gnome-initial-setup/pages/timezone/data/timezone_1.png
new file mode 100644
index 0000000..2053b7e
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_1.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_10.5.png b/gnome-initial-setup/pages/timezone/data/timezone_10.5.png
new file mode 100644
index 0000000..6ec7f9f
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_10.5.png
Binary files differ
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
new file mode 100644
index 0000000..359911f
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_10.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_10.png b/gnome-initial-setup/pages/timezone/data/timezone_10.png
new file mode 100644
index 0000000..475dcf4
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_10.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_10_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_10_dim.png
new file mode 100644
index 0000000..9521033
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_10_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_11.5.png b/gnome-initial-setup/pages/timezone/data/timezone_11.5.png
new file mode 100644
index 0000000..afdedd7
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_11.5.png
Binary files differ
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
new file mode 100644
index 0000000..00cc5b8
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_11.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_11.png b/gnome-initial-setup/pages/timezone/data/timezone_11.png
new file mode 100644
index 0000000..6168aa2
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_11.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_11_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_11_dim.png
new file mode 100644
index 0000000..5a1df4e
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_11_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_12.75.png b/gnome-initial-setup/pages/timezone/data/timezone_12.75.png
new file mode 100644
index 0000000..4f74a85
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_12.75.png
Binary files differ
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
new file mode 100644
index 0000000..cc7dbde
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_12.75_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_12.png b/gnome-initial-setup/pages/timezone/data/timezone_12.png
new file mode 100644
index 0000000..d0b3531
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_12.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_12_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_12_dim.png
new file mode 100644
index 0000000..71514a8
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_12_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_13.png b/gnome-initial-setup/pages/timezone/data/timezone_13.png
new file mode 100644
index 0000000..fe2f134
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_13.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_13_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_13_dim.png
new file mode 100644
index 0000000..90e689d
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_13_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_14.png b/gnome-initial-setup/pages/timezone/data/timezone_14.png
new file mode 100644
index 0000000..e91d4eb
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_14.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_14_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_14_dim.png
new file mode 100644
index 0000000..e4f0a0a
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_14_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_1_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_1_dim.png
new file mode 100644
index 0000000..b36ff22
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_1_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_2.png b/gnome-initial-setup/pages/timezone/data/timezone_2.png
new file mode 100644
index 0000000..ec1e874
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_2.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_2_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_2_dim.png
new file mode 100644
index 0000000..fba1021
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_2_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_3.5.png b/gnome-initial-setup/pages/timezone/data/timezone_3.5.png
new file mode 100644
index 0000000..2dc7399
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_3.5.png
Binary files differ
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
new file mode 100644
index 0000000..76eab2e
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_3.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_3.png b/gnome-initial-setup/pages/timezone/data/timezone_3.png
new file mode 100644
index 0000000..eda59dc
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_3.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_3_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_3_dim.png
new file mode 100644
index 0000000..d718bbc
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_3_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_4.5.png b/gnome-initial-setup/pages/timezone/data/timezone_4.5.png
new file mode 100644
index 0000000..e09ed90
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_4.5.png
Binary files differ
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
new file mode 100644
index 0000000..74461fe
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_4.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_4.png b/gnome-initial-setup/pages/timezone/data/timezone_4.png
new file mode 100644
index 0000000..483dc53
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_4.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_4_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_4_dim.png
new file mode 100644
index 0000000..ce18078
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_4_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5.5.png b/gnome-initial-setup/pages/timezone/data/timezone_5.5.png
new file mode 100644
index 0000000..9b8f094
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_5.5.png
Binary files differ
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
new file mode 100644
index 0000000..f7a475d
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_5.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5.75.png b/gnome-initial-setup/pages/timezone/data/timezone_5.75.png
new file mode 100644
index 0000000..827ce1a
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_5.75.png
Binary files differ
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
new file mode 100644
index 0000000..1b972ff
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_5.75_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5.png b/gnome-initial-setup/pages/timezone/data/timezone_5.png
new file mode 100644
index 0000000..1bb6d20
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_5.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_5_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_5_dim.png
new file mode 100644
index 0000000..9165f64
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_6.5.png b/gnome-initial-setup/pages/timezone/data/timezone_6.5.png
new file mode 100644
index 0000000..d307bf3
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_6.5.png
Binary files differ
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
new file mode 100644
index 0000000..7d99a0f
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_6.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_6.png b/gnome-initial-setup/pages/timezone/data/timezone_6.png
new file mode 100644
index 0000000..41504fc
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_6.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_6_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_6_dim.png
new file mode 100644
index 0000000..e99f5fa
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_6_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_7.png b/gnome-initial-setup/pages/timezone/data/timezone_7.png
new file mode 100644
index 0000000..239115a
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_7.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_7_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_7_dim.png
new file mode 100644
index 0000000..e59f0db
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_7_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_8.75.png b/gnome-initial-setup/pages/timezone/data/timezone_8.75.png
new file mode 100644
index 0000000..2a2917f
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_8.75.png
Binary files differ
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
new file mode 100644
index 0000000..fb9caf4
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_8.75_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_8.png b/gnome-initial-setup/pages/timezone/data/timezone_8.png
new file mode 100644
index 0000000..d210222
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_8.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_8_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_8_dim.png
new file mode 100644
index 0000000..9612511
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_8_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_9.5.png b/gnome-initial-setup/pages/timezone/data/timezone_9.5.png
new file mode 100644
index 0000000..1c3290c
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_9.5.png
Binary files differ
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
new file mode 100644
index 0000000..6a9b9ed
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_9.5_dim.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_9.png b/gnome-initial-setup/pages/timezone/data/timezone_9.png
new file mode 100644
index 0000000..4e2beda
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_9.png
Binary files differ
diff --git a/gnome-initial-setup/pages/timezone/data/timezone_9_dim.png b/gnome-initial-setup/pages/timezone/data/timezone_9_dim.png
new file mode 100644
index 0000000..ac47471
--- /dev/null
+++ b/gnome-initial-setup/pages/timezone/data/timezone_9_dim.png
Binary files differ
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>