From 63e7d5728b5eb333504d1e3df9a802b1d9046310 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 24 Aug 2023 21:19:40 -0400 Subject: [PATCH 04/12] keyboard: Get default input sources from gnome-desktop Right now, we figure out the default input sources ourselves, based on the current locale and layout information coming from localed. This logic needs to be duplicated in several components, so its now provided by gnome-desktop. This commit changes it over to use gnome-desktop APIs. The same time if leverages a gnome-desktop API to fix a bug where cyrillic layouts were getting added without a latin counterpart. --- .../pages/keyboard/gis-keyboard-page.c | 312 ++++++------------ 1 file changed, 107 insertions(+), 205 deletions(-) diff --git a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c index ad48d933..39cb7db6 100644 --- a/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c +++ b/gnome-initial-setup/pages/keyboard/gis-keyboard-page.c @@ -18,510 +18,412 @@ * Michael Wood * * Based on gnome-control-center cc-region-panel.c */ #define PAGE_ID "keyboard" #include "config.h" #include #include #include #include #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #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" #define KEY_MRU_SOURCES "mru-sources" +#define KEY_INPUT_OPTIONS "xkb-options" struct _GisKeyboardPagePrivate { GtkWidget *input_chooser; GDBusProxy *localed; GCancellable *cancellable; GPermission *permission; GSettings *input_settings; + char **default_input_source_ids; + char **default_input_source_types; + char **default_input_options; - GSList *system_sources; + gboolean should_skip; }; 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_clear_pointer (&priv->default_input_source_ids, g_strfreev); + g_clear_pointer (&priv->default_input_source_types, g_strfreev); + g_clear_pointer (&priv->default_input_options, g_strfreev); G_OBJECT_CLASS (gis_keyboard_page_parent_class)->finalize (object); } static void -set_input_settings (GisKeyboardPage *self) +set_input_settings (GisKeyboardPage *self, + const char *type, + const char *id) { GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); - const gchar *type; - const gchar *id; - GVariantBuilder builder; - GSList *l; + + GVariantBuilder input_source_builder; + GVariantBuilder input_options_builder; + size_t i; + gboolean has_latin_layout = FALSE; 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)")); + g_variant_builder_init (&input_source_builder, G_VARIANT_TYPE ("a(ss)")); + g_variant_builder_init (&input_options_builder, G_VARIANT_TYPE ("as")); - if (g_str_equal (type, "xkb")) { - g_variant_builder_add (&builder, "(ss)", type, id); - is_xkb_source = TRUE; - } + if (type != NULL && id != NULL) { + has_latin_layout = !gnome_input_source_is_non_latin (type, id); + if (g_str_equal (type, "xkb")) { + g_variant_builder_add (&input_source_builder, "(ss)", type, id); - for (l = priv->system_sources; l; l = l->next) { - const gchar *sid = l->data; + is_xkb_source = TRUE; + } + } - if (g_str_equal (id, sid) && g_str_equal (type, "xkb")) + for (i = 0; priv->default_input_source_ids && priv->default_input_source_ids[i] != NULL; i++) { + if (g_str_equal (id, priv->default_input_source_ids[i]) && g_str_equal (type, priv->default_input_source_types[i])) continue; - g_variant_builder_add (&builder, "(ss)", "xkb", sid); + g_variant_builder_add (&input_source_builder, "(ss)", priv->default_input_source_types[i], priv->default_input_source_ids[i]); + + if (!gnome_input_source_is_non_latin (priv->default_input_source_types[i], priv->default_input_source_ids[i])) + has_latin_layout = TRUE; + } + + if (type != NULL && id != NULL) { + if (!is_xkb_source) { + g_variant_builder_add (&input_source_builder, "(ss)", type, id); + } + } + + if (!has_latin_layout) { + g_variant_builder_add (&input_source_builder, "(ss)", "xkb", "us"); } - if (!is_xkb_source) - g_variant_builder_add (&builder, "(ss)", type, id); + for (i = 0; priv->default_input_options[i] != NULL; i++) { + g_variant_builder_add (&input_options_builder, "s", priv->default_input_options[i]); + } if (type != NULL && id != NULL) { GVariantBuilder mru_input_source_builder; g_variant_builder_init (&mru_input_source_builder, G_VARIANT_TYPE ("a(ss)")); g_variant_builder_add (&mru_input_source_builder, "(ss)", type, id); g_settings_set_value (priv->input_settings, KEY_MRU_SOURCES, g_variant_builder_end (&mru_input_source_builder)); } - g_settings_set_value (priv->input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder)); - + g_settings_set_value (priv->input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&input_source_builder)); + g_settings_set_value (priv->input_settings, KEY_INPUT_OPTIONS, g_variant_builder_end (&input_options_builder)); 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; + size_t i; 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); + for (i = 0; priv->default_input_source_ids && priv->default_input_source_ids[i] != NULL; i++) { + const gchar *sid = priv->default_input_source_ids[i]; + g_auto (GStrv) lv = NULL; + + if (!g_str_equal (priv->default_input_source_types[i], "xkb")) + continue; + + 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); + const gchar *type; + const gchar *id; - set_input_settings (self); + 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)); + + set_input_settings (self, type, id); 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) +add_default_input_sources (GisKeyboardPage *self) { - 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); + set_input_settings (self, NULL, NULL); } 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); + priv->should_skip = TRUE; + + if (priv->default_input_source_ids != NULL) + add_default_input_sources (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); + cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser), + priv->default_input_source_ids[0], + priv->default_input_source_types[0]); } 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); + 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 +on_got_default_sources (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GisKeyboardPage *self = data; + GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self); + g_autoptr (GError) error = NULL; + gboolean success = FALSE; + g_auto (GStrv) ids = NULL; + g_auto (GStrv) types = NULL; + g_auto (GStrv) options = NULL; + + success = gnome_get_default_input_sources_finish (res, &ids, &types, &options, &error); + + if (!success) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to fetch default input sources: %s", error->message); + return; + } + + priv->default_input_source_ids = g_steal_pointer (&ids); + priv->default_input_source_types = g_steal_pointer (&types); + priv->default_input_options = g_steal_pointer (&options); + + if (priv->should_skip) { + add_default_input_sources (self); + return; + } + + preselect_input_source (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_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); + gnome_get_default_input_sources (priv->cancellable, on_got_default_sources, 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_set_visible (GTK_WIDGET (self), TRUE); } 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; -- 2.41.0