summaryrefslogtreecommitdiffstats
path: root/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c
diff options
context:
space:
mode:
Diffstat (limited to 'gnome-initial-setup/pages/keyboard/gis-keyboard-page.c')
-rw-r--r--gnome-initial-setup/pages/keyboard/gis-keyboard-page.c541
1 files changed, 541 insertions, 0 deletions
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);
+}