summaryrefslogtreecommitdiffstats
path: root/gnome-initial-setup/pages/keyboard
diff options
context:
space:
mode:
Diffstat (limited to 'gnome-initial-setup/pages/keyboard')
-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.c883
-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.ui36
-rw-r--r--gnome-initial-setup/pages/keyboard/input-chooser.ui22
-rw-r--r--gnome-initial-setup/pages/keyboard/keyboard.gresource.xml7
-rw-r--r--gnome-initial-setup/pages/keyboard/meson.build14
10 files changed, 1710 insertions, 0 deletions
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..efba249
--- /dev/null
+++ b/gnome-initial-setup/pages/keyboard/cc-input-chooser.c
@@ -0,0 +1,883 @@
+/*
+ * 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 *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_append (GTK_BOX (widget), gtk_label_new (text));
+ 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_append (GTK_BOX (widget->box), widget->label);
+
+
+ widget->checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");
+ gtk_box_append (GTK_BOX (widget->box), widget->checkmark);
+
+ 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_append (GTK_BOX (widget->box), label);
+
+ g_object_set_data_full (G_OBJECT (widget->box), "input-widget", widget,
+ input_widget_free);
+
+ return widget->box;
+}
+
+static void
+sync_all_checkmarks (CcInputChooser *chooser)
+{
+ CcInputChooserPrivate *priv;
+ GtkWidget *row;
+
+ priv = cc_input_chooser_get_instance_private (chooser);
+ row = gtk_widget_get_first_child (priv->input_list);
+ while (row) {
+ InputWidget *widget;
+ GtkWidget *child;
+ gboolean should_be_visible;
+
+ child = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (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;
+
+ row = gtk_widget_get_next_sibling (row);
+ }
+
+ 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_style_context_add_class (gtk_widget_get_style_context (arrow), "dim-label");
+ gtk_widget_set_margin_top (widget, 12);
+ gtk_widget_set_margin_bottom (widget, 12);
+ gtk_widget_set_hexpand (arrow, TRUE);
+ gtk_widget_set_halign (arrow, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (arrow, GTK_ALIGN_CENTER);
+ gtk_box_append (GTK_BOX (widget), arrow);
+
+ 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);
+ return widget;
+}
+
+static void
+choose_non_extras (CcInputChooser *chooser)
+{
+ CcInputChooserPrivate *priv;
+ GtkWidget *row;
+ guint count = 0;
+
+ priv = cc_input_chooser_get_instance_private (chooser);
+ row = gtk_widget_get_first_child (priv->input_list);
+ while (row) {
+ InputWidget *widget;
+ GtkWidget *child;
+
+ if (++count > MIN_ROWS)
+ break;
+
+ child = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row));
+ widget = get_input_widget (child);
+ if (widget == NULL)
+ break;
+
+ widget->is_extra = FALSE;
+
+ row = gtk_widget_get_next_sibling (row);
+ }
+}
+
+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_list_box_append (GTK_LIST_BOX (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);
+
+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_list_box_row_get_child (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_editable_get_text (GTK_EDITABLE (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_list_box_row_get_child (a));
+ lb = get_input_widget (gtk_list_box_row_get_child (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_grab_focus (priv->filter_entry);
+
+ 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_list_box_row_get_child (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);
+ }
+}
+
+#ifdef HAVE_IBUS
+static void
+update_ibus_active_sources (CcInputChooser *chooser)
+{
+ CcInputChooserPrivate *priv;
+ IBusEngineDesc *engine_desc;
+ GtkWidget *child;
+ const gchar *type;
+ const gchar *id;
+ gchar *name;
+
+ priv = cc_input_chooser_get_instance_private (chooser);
+ child = gtk_widget_get_first_child (priv->input_list);
+ while (child) {
+ InputWidget *row;
+
+ row = get_input_widget (child);
+ child = gtk_widget_get_next_sibling (child);
+
+ 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);
+ }
+ }
+}
+
+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_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_list_box_append (GTK_LIST_BOX (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);
+
+ 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_editable_set_text (GTK_EDITABLE (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..a47d8a1
--- /dev/null
+++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.ui
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GisKeyboardPage" parent="GisPage">
+ <child>
+ <object class="AdwPreferencesPage">
+ <child>
+ <object class="AdwPreferencesGroup">
+
+ <child>
+ <object class="GtkBox" id="page">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GisPageHeader" id="header">
+ <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>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </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..5fe2229
--- /dev/null
+++ b/gnome-initial-setup/pages/keyboard/input-chooser.ui
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<interface>
+ <template class="CcInputChooser" parent="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkSearchEntry" id="filter_entry">
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="input_list">
+ <property name="vexpand">True</property>
+ <property name="halign">fill</property>
+ <property name="valign">start</property>
+ <style>
+ <class name="boxed-list" />
+ </style>
+ </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'
+)