/* * Copyright (C) 2012 Red Hat, Inc. * * Written by: Rui Matos * * 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, 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. */ #include #include #include #include #include #include #define XKEYBOARD_CONFIG_(String) ((char *) g_dgettext ("xkeyboard-config", String)) #define GNOME_DESKTOP_USE_UNSTABLE_API #include "gnome-languages.h" #include "gnome-xkb-info.h" #ifndef XKB_RULES_FILE #define XKB_RULES_FILE "evdev" #endif typedef struct _Layout Layout; struct _Layout { gchar *id; gchar *xkb_name; gchar *short_desc; gchar *description; gboolean is_variant; const Layout *main_layout; GSList *iso639Ids; GSList *iso3166Ids; }; typedef struct _XkbOption XkbOption; struct _XkbOption { gchar *id; gchar *description; }; typedef struct _XkbOptionGroup XkbOptionGroup; struct _XkbOptionGroup { gchar *id; gchar *description; gboolean allow_multiple_selection; GHashTable *options_table; }; struct _GnomeXkbInfoPrivate { GHashTable *option_groups_table; GHashTable *layouts_by_country; GHashTable *layouts_by_language; GHashTable *layouts_table; /* Only used while parsing */ XkbOptionGroup *current_parser_group; XkbOption *current_parser_option; Layout *current_parser_layout; Layout *current_parser_variant; gchar *current_parser_iso639Id; gchar *current_parser_iso3166Id; gchar **current_parser_text; }; G_DEFINE_TYPE (GnomeXkbInfo, gnome_xkb_info, G_TYPE_OBJECT); static void free_layout (gpointer data) { Layout *layout = data; g_return_if_fail (layout != NULL); g_free (layout->id); g_free (layout->xkb_name); g_free (layout->short_desc); g_free (layout->description); g_slist_free_full (layout->iso639Ids, g_free); g_slist_free_full (layout->iso3166Ids, g_free); g_slice_free (Layout, layout); } static void free_option (gpointer data) { XkbOption *option = data; g_return_if_fail (option != NULL); g_free (option->id); g_free (option->description); g_slice_free (XkbOption, option); } static void free_option_group (gpointer data) { XkbOptionGroup *group = data; g_return_if_fail (group != NULL); g_free (group->id); g_free (group->description); g_hash_table_destroy (group->options_table); g_slice_free (XkbOptionGroup, group); } static gchar * get_xml_rules_file_path (const gchar *suffix) { gchar *rules_file; gchar *xml_rules_file; rules_file = g_build_filename (XKB_BASE, "rules", XKB_RULES_FILE, NULL); xml_rules_file = g_strdup_printf ("%s%s", rules_file, suffix); g_free (rules_file); return xml_rules_file; } static void parse_start_element (GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer data, GError **error) { GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv; if (priv->current_parser_text) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Expected character data but got element '%s'", element_name); return; } if (strcmp (element_name, "name") == 0) { if (priv->current_parser_variant) priv->current_parser_text = &priv->current_parser_variant->xkb_name; else if (priv->current_parser_layout) priv->current_parser_text = &priv->current_parser_layout->xkb_name; else if (priv->current_parser_option) priv->current_parser_text = &priv->current_parser_option->id; else if (priv->current_parser_group) priv->current_parser_text = &priv->current_parser_group->id; } else if (strcmp (element_name, "description") == 0) { if (priv->current_parser_variant) priv->current_parser_text = &priv->current_parser_variant->description; else if (priv->current_parser_layout) priv->current_parser_text = &priv->current_parser_layout->description; else if (priv->current_parser_option) priv->current_parser_text = &priv->current_parser_option->description; else if (priv->current_parser_group) priv->current_parser_text = &priv->current_parser_group->description; } else if (strcmp (element_name, "shortDescription") == 0) { if (priv->current_parser_variant) priv->current_parser_text = &priv->current_parser_variant->short_desc; else if (priv->current_parser_layout) priv->current_parser_text = &priv->current_parser_layout->short_desc; } else if (strcmp (element_name, "iso639Id") == 0) { priv->current_parser_text = &priv->current_parser_iso639Id; } else if (strcmp (element_name, "iso3166Id") == 0) { priv->current_parser_text = &priv->current_parser_iso3166Id; } else if (strcmp (element_name, "layout") == 0) { if (priv->current_parser_layout) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'layout' elements can't be nested"); return; } priv->current_parser_layout = g_slice_new0 (Layout); } else if (strcmp (element_name, "variant") == 0) { Layout *layout; if (priv->current_parser_variant) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'variant' elements can't be nested"); return; } if (!priv->current_parser_layout) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'variant' elements must be inside 'layout' elements"); return; } if (!priv->current_parser_layout->xkb_name) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'variant' elements must be inside named 'layout' elements"); return; } layout = g_hash_table_lookup (priv->layouts_table, priv->current_parser_layout->xkb_name); if (!layout) layout = priv->current_parser_layout; priv->current_parser_variant = g_slice_new0 (Layout); priv->current_parser_variant->is_variant = TRUE; priv->current_parser_variant->main_layout = layout; } else if (strcmp (element_name, "group") == 0) { if (priv->current_parser_group) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'group' elements can't be nested"); return; } priv->current_parser_group = g_slice_new0 (XkbOptionGroup); /* Maps option ids to XkbOption structs. Owns the XkbOption structs. */ priv->current_parser_group->options_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_option); g_markup_collect_attributes (element_name, attribute_names, attribute_values, error, G_MARKUP_COLLECT_BOOLEAN | G_MARKUP_COLLECT_OPTIONAL, "allowMultipleSelection", &priv->current_parser_group->allow_multiple_selection, G_MARKUP_COLLECT_INVALID); } else if (strcmp (element_name, "option") == 0) { if (priv->current_parser_option) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'option' elements can't be nested"); return; } if (!priv->current_parser_group) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'option' elements must be inside 'group' elements"); return; } priv->current_parser_option = g_slice_new0 (XkbOption); } } static void add_layout_to_table (GHashTable *table, const gchar *key, Layout *layout) { GHashTable *set; if (!layout->id) return; set = g_hash_table_lookup (table, key); if (!set) { set = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_replace (table, g_strdup (key), set); } else { if (g_hash_table_contains (set, layout->id)) return; } g_hash_table_replace (set, layout->id, layout); } static void add_layout_to_locale_tables (Layout *layout, GHashTable *layouts_by_language, GHashTable *layouts_by_country) { GSList *l, *lang_codes, *country_codes; gchar *language, *country; lang_codes = layout->iso639Ids; country_codes = layout->iso3166Ids; if (layout->is_variant) { if (!lang_codes) lang_codes = layout->main_layout->iso639Ids; if (!country_codes) country_codes = layout->main_layout->iso3166Ids; } for (l = lang_codes; l; l = l->next) { language = gnome_get_language_from_code ((gchar *) l->data, NULL); if (language) { add_layout_to_table (layouts_by_language, language, layout); g_free (language); } } for (l = country_codes; l; l = l->next) { country = gnome_get_country_from_code ((gchar *) l->data, NULL); if (country) { add_layout_to_table (layouts_by_country, country, layout); g_free (country); } } } static void add_iso639 (Layout *layout, gchar *id) { layout->iso639Ids = g_slist_prepend (layout->iso639Ids, id); } static void add_iso3166 (Layout *layout, gchar *id) { layout->iso3166Ids = g_slist_prepend (layout->iso3166Ids, id); } static void parse_end_element (GMarkupParseContext *context, const gchar *element_name, gpointer data, GError **error) { GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv; if (strcmp (element_name, "layout") == 0) { if (!priv->current_parser_layout->description || !priv->current_parser_layout->xkb_name) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'layout' elements must enclose 'description' and 'name' elements"); return; } priv->current_parser_layout->id = g_strdup (priv->current_parser_layout->xkb_name); if (g_hash_table_contains (priv->layouts_table, priv->current_parser_layout->id)) { g_clear_pointer (&priv->current_parser_layout, free_layout); return; } g_hash_table_replace (priv->layouts_table, priv->current_parser_layout->id, priv->current_parser_layout); add_layout_to_locale_tables (priv->current_parser_layout, priv->layouts_by_language, priv->layouts_by_country); priv->current_parser_layout = NULL; } else if (strcmp (element_name, "variant") == 0) { if (!priv->current_parser_variant->description || !priv->current_parser_variant->xkb_name) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'variant' elements must enclose 'description' and 'name' elements"); return; } priv->current_parser_variant->id = g_strjoin ("+", priv->current_parser_layout->xkb_name, priv->current_parser_variant->xkb_name, NULL); g_hash_table_replace (priv->layouts_table, priv->current_parser_variant->id, priv->current_parser_variant); add_layout_to_locale_tables (priv->current_parser_variant, priv->layouts_by_language, priv->layouts_by_country); priv->current_parser_variant = NULL; } else if (strcmp (element_name, "iso639Id") == 0) { if (!priv->current_parser_iso639Id) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'iso639Id' elements must enclose text"); return; } if (priv->current_parser_variant) add_iso639 (priv->current_parser_variant, priv->current_parser_iso639Id); else if (priv->current_parser_layout) add_iso639 (priv->current_parser_layout, priv->current_parser_iso639Id); priv->current_parser_iso639Id = NULL; } else if (strcmp (element_name, "iso3166Id") == 0) { if (!priv->current_parser_iso3166Id) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'iso3166Id' elements must enclose text"); return; } if (priv->current_parser_variant) add_iso3166 (priv->current_parser_variant, priv->current_parser_iso3166Id); else if (priv->current_parser_layout) add_iso3166 (priv->current_parser_layout, priv->current_parser_iso3166Id); priv->current_parser_iso3166Id = NULL; } else if (strcmp (element_name, "group") == 0) { if (!priv->current_parser_group->description || !priv->current_parser_group->id) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'group' elements must enclose 'description' and 'name' elements"); return; } g_hash_table_replace (priv->option_groups_table, priv->current_parser_group->id, priv->current_parser_group); priv->current_parser_group = NULL; } else if (strcmp (element_name, "option") == 0) { if (!priv->current_parser_option->description || !priv->current_parser_option->id) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "'option' elements must enclose 'description' and 'name' elements"); return; } g_hash_table_replace (priv->current_parser_group->options_table, priv->current_parser_option->id, priv->current_parser_option); priv->current_parser_option = NULL; } } static void parse_text (GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer data, GError **error) { GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv; if (priv->current_parser_text) { *priv->current_parser_text = g_strndup (text, text_len); priv->current_parser_text = NULL; } } static void parse_error (GMarkupParseContext *context, GError *error, gpointer data) { GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv; free_option_group (priv->current_parser_group); free_option (priv->current_parser_option); free_layout (priv->current_parser_layout); free_layout (priv->current_parser_variant); g_free (priv->current_parser_iso639Id); g_free (priv->current_parser_iso3166Id); } static const GMarkupParser markup_parser = { parse_start_element, parse_end_element, parse_text, NULL, parse_error }; static void parse_rules_file (GnomeXkbInfo *self, const gchar *path, GError **error) { gchar *buffer; gsize length; GMarkupParseContext *context; GError *sub_error = NULL; g_file_get_contents (path, &buffer, &length, &sub_error); if (sub_error) { g_propagate_error (error, sub_error); return; } context = g_markup_parse_context_new (&markup_parser, 0, self, NULL); g_markup_parse_context_parse (context, buffer, length, &sub_error); g_markup_parse_context_free (context); g_free (buffer); if (sub_error) g_propagate_error (error, sub_error); } static void parse_rules (GnomeXkbInfo *self) { GnomeXkbInfoPrivate *priv = self->priv; GSettings *settings; gboolean show_all_sources; gchar *file_path; GError *error = NULL; /* Make sure the translated strings we get from XKEYBOARD_CONFIG() are * in UTF-8 and not in the current locale */ bind_textdomain_codeset ("xkeyboard-config", "UTF-8"); /* Maps option group ids to XkbOptionGroup structs. Owns the XkbOptionGroup structs. */ priv->option_groups_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_option_group); /* Maps country strings to a GHashTable which is a set of Layout struct pointers into the Layout structs stored in layouts_table. */ priv->layouts_by_country = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy); /* Maps language strings to a GHashTable which is a set of Layout struct pointers into the Layout structs stored in layouts_table. */ priv->layouts_by_language = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy); /* Maps layout ids to Layout structs. Owns the Layout structs. */ priv->layouts_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_layout); file_path = get_xml_rules_file_path (".xml"); parse_rules_file (self, file_path, &error); if (error) goto cleanup; g_free (file_path); settings = g_settings_new ("org.gnome.desktop.input-sources"); show_all_sources = g_settings_get_boolean (settings, "show-all-sources"); g_object_unref (settings); if (!show_all_sources) return; file_path = get_xml_rules_file_path (".extras.xml"); parse_rules_file (self, file_path, &error); if (error) goto cleanup; g_free (file_path); return; cleanup: g_warning ("Failed to load XKB rules file %s: %s", file_path, error->message); g_clear_pointer (&error, g_error_free); g_clear_pointer (&file_path, g_free); g_clear_pointer (&priv->option_groups_table, g_hash_table_destroy); g_clear_pointer (&priv->layouts_by_country, g_hash_table_destroy); g_clear_pointer (&priv->layouts_by_language, g_hash_table_destroy); g_clear_pointer (&priv->layouts_table, g_hash_table_destroy); } static gboolean ensure_rules_are_parsed (GnomeXkbInfo *self) { GnomeXkbInfoPrivate *priv = self->priv; if (!priv->layouts_table) parse_rules (self); return !!priv->layouts_table; } static void gnome_xkb_info_init (GnomeXkbInfo *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GNOME_TYPE_XKB_INFO, GnomeXkbInfoPrivate); } static void gnome_xkb_info_finalize (GObject *self) { GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (self)->priv; if (priv->option_groups_table) g_hash_table_destroy (priv->option_groups_table); if (priv->layouts_by_country) g_hash_table_destroy (priv->layouts_by_country); if (priv->layouts_by_language) g_hash_table_destroy (priv->layouts_by_language); if (priv->layouts_table) g_hash_table_destroy (priv->layouts_table); G_OBJECT_CLASS (gnome_xkb_info_parent_class)->finalize (self); } static void gnome_xkb_info_class_init (GnomeXkbInfoClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = gnome_xkb_info_finalize; g_type_class_add_private (gobject_class, sizeof (GnomeXkbInfoPrivate)); } /** * gnome_xkb_info_new: * * Returns: (transfer full): a new #GnomeXkbInfo instance. */ GnomeXkbInfo * gnome_xkb_info_new (void) { return g_object_new (GNOME_TYPE_XKB_INFO, NULL); } /** * gnome_xkb_info_get_all_layouts: * @self: a #GnomeXkbInfo * * Returns a list of all layout identifiers we know about. * * Return value: (transfer container) (element-type utf8): the list * of layout names. The caller takes ownership of the #GList but not * of the strings themselves, those are internally allocated and must * not be modified. * * Since: 3.6 */ GList * gnome_xkb_info_get_all_layouts (GnomeXkbInfo *self) { GnomeXkbInfoPrivate *priv; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL); priv = self->priv; if (!ensure_rules_are_parsed (self)) return NULL; return g_hash_table_get_keys (priv->layouts_table); } /** * gnome_xkb_info_get_all_option_groups: * @self: a #GnomeXkbInfo * * Returns a list of all option group identifiers we know about. * * Return value: (transfer container) (element-type utf8): the list * of option group ids. The caller takes ownership of the #GList but * not of the strings themselves, those are internally allocated and * must not be modified. * * Since: 3.6 */ GList * gnome_xkb_info_get_all_option_groups (GnomeXkbInfo *self) { GnomeXkbInfoPrivate *priv; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL); priv = self->priv; if (!ensure_rules_are_parsed (self)) return NULL; return g_hash_table_get_keys (priv->option_groups_table); } /** * gnome_xkb_info_description_for_group: * @self: a #GnomeXkbInfo * @group_id: identifier for group * * Return value: the translated description for the group @group_id. * * Since: 3.8 */ const gchar * gnome_xkb_info_description_for_group (GnomeXkbInfo *self, const gchar *group_id) { GnomeXkbInfoPrivate *priv; const XkbOptionGroup *group; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL); priv = self->priv; if (!ensure_rules_are_parsed (self)) return NULL; group = g_hash_table_lookup (priv->option_groups_table, group_id); if (!group) return NULL; return XKEYBOARD_CONFIG_(group->description); } /** * gnome_xkb_info_get_options_for_group: * @self: a #GnomeXkbInfo * @group_id: group's identifier about which to retrieve the options * * Returns a list of all option identifiers we know about for group * @group_id. * * Return value: (transfer container) (element-type utf8): the list * of option ids. The caller takes ownership of the #GList but not of * the strings themselves, those are internally allocated and must not * be modified. * * Since: 3.6 */ GList * gnome_xkb_info_get_options_for_group (GnomeXkbInfo *self, const gchar *group_id) { GnomeXkbInfoPrivate *priv; const XkbOptionGroup *group; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL); priv = self->priv; if (!ensure_rules_are_parsed (self)) return NULL; group = g_hash_table_lookup (priv->option_groups_table, group_id); if (!group) return NULL; return g_hash_table_get_keys (group->options_table); } /** * gnome_xkb_info_description_for_option: * @self: a #GnomeXkbInfo * @group_id: identifier for group containing the option * @id: option identifier * * Return value: the translated description for the option @id. * * Since: 3.6 */ const gchar * gnome_xkb_info_description_for_option (GnomeXkbInfo *self, const gchar *group_id, const gchar *id) { GnomeXkbInfoPrivate *priv; const XkbOptionGroup *group; const XkbOption *option; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL); priv = self->priv; if (!ensure_rules_are_parsed (self)) return NULL; group = g_hash_table_lookup (priv->option_groups_table, group_id); if (!group) return NULL; option = g_hash_table_lookup (group->options_table, id); if (!option) return NULL; return XKEYBOARD_CONFIG_(option->description); } /** * gnome_xkb_info_get_layout_info: * @self: a #GnomeXkbInfo * @id: layout's identifier about which to retrieve the info * @display_name: (out) (allow-none) (transfer none): location to store * the layout's display name, or %NULL * @short_name: (out) (allow-none) (transfer none): location to store * the layout's short name, or %NULL * @xkb_layout: (out) (allow-none) (transfer none): location to store * the layout's XKB name, or %NULL * @xkb_variant: (out) (allow-none) (transfer none): location to store * the layout's XKB variant, or %NULL * * Retrieves information about a layout. Both @display_name and * @short_name are suitable to show in UIs and might be localized if * translations are available. * * Some layouts don't provide a short name (2 or 3 letters) or don't * specify a XKB variant, in those cases @short_name or @xkb_variant * are empty strings, i.e. "". * * If the given layout doesn't exist the return value is %FALSE and * all the (out) parameters are set to %NULL. * * Return value: %TRUE if the layout exists or %FALSE otherwise. * * Since: 3.6 */ gboolean gnome_xkb_info_get_layout_info (GnomeXkbInfo *self, const gchar *id, const gchar **display_name, const gchar **short_name, const gchar **xkb_layout, const gchar **xkb_variant) { GnomeXkbInfoPrivate *priv; const Layout *layout; if (display_name) *display_name = NULL; if (short_name) *short_name = NULL; if (xkb_layout) *xkb_layout = NULL; if (xkb_variant) *xkb_variant = NULL; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), FALSE); priv = self->priv; if (!ensure_rules_are_parsed (self)) return FALSE; if (!g_hash_table_lookup_extended (priv->layouts_table, id, NULL, (gpointer *)&layout)) return FALSE; if (display_name) *display_name = XKEYBOARD_CONFIG_(layout->description); if (!layout->is_variant) { if (short_name) *short_name = XKEYBOARD_CONFIG_(layout->short_desc ? layout->short_desc : ""); if (xkb_layout) *xkb_layout = layout->xkb_name; if (xkb_variant) *xkb_variant = ""; } else { if (short_name) *short_name = XKEYBOARD_CONFIG_(layout->short_desc ? layout->short_desc : layout->main_layout->short_desc ? layout->main_layout->short_desc : ""); if (xkb_layout) *xkb_layout = layout->main_layout->xkb_name; if (xkb_variant) *xkb_variant = layout->xkb_name; } return TRUE; } static void collect_layout_ids (gpointer key, gpointer value, gpointer data) { Layout *layout = value; GList **list = data; *list = g_list_prepend (*list, layout->id); } /** * gnome_xkb_info_get_layouts_for_language: * @self: a #GnomeXkbInfo * @language_code: an ISO 639 code string * * Returns a list of all layout identifiers we know about for * @language_code. * * Return value: (transfer container) (element-type utf8): the list * of layout ids. The caller takes ownership of the #GList but not of * the strings themselves, those are internally allocated and must not * be modified. * * Since: 3.8 */ GList * gnome_xkb_info_get_layouts_for_language (GnomeXkbInfo *self, const gchar *language_code) { GnomeXkbInfoPrivate *priv; GHashTable *layouts_for_language; gchar *language; GList *list; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL); priv = self->priv; if (!ensure_rules_are_parsed (self)) return NULL; language = gnome_get_language_from_code (language_code, NULL); if (!language) return NULL; layouts_for_language = g_hash_table_lookup (priv->layouts_by_language, language); g_free (language); if (!layouts_for_language) return NULL; list = NULL; g_hash_table_foreach (layouts_for_language, collect_layout_ids, &list); return list; } /** * gnome_xkb_info_get_layouts_for_country: * @self: a #GnomeXkbInfo * @country_code: an ISO 3166 code string * * Returns a list of all layout identifiers we know about for * @country_code. * * Return value: (transfer container) (element-type utf8): the list * of layout ids. The caller takes ownership of the #GList but not of * the strings themselves, those are internally allocated and must not * be modified. * * Since: 3.8 */ GList * gnome_xkb_info_get_layouts_for_country (GnomeXkbInfo *self, const gchar *country_code) { GnomeXkbInfoPrivate *priv; GHashTable *layouts_for_country; gchar *country; GList *list; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL); priv = self->priv; if (!ensure_rules_are_parsed (self)) return NULL; country = gnome_get_country_from_code (country_code, NULL); if (!country) return NULL; layouts_for_country = g_hash_table_lookup (priv->layouts_by_country, country); g_free (country); if (!layouts_for_country) return NULL; list = NULL; g_hash_table_foreach (layouts_for_country, collect_layout_ids, &list); return list; } static void collect_languages (gpointer value, gpointer data) { gchar *language = value; GList **list = data; *list = g_list_append (*list, language); } /** * gnome_xkb_info_get_languages_for_layout: * @self: a #GnomeXkbInfo * @layout_id: a layout identifier * * Returns a list of all languages supported by a layout, given by * @layout_id. * * Return value: (transfer container) (element-type utf8): the list of * ISO 639 code strings. The caller takes ownership of the #GList but * not of the strings themselves, those are internally allocated and * must not be modified. * * Since: 3.18 */ GList * gnome_xkb_info_get_languages_for_layout (GnomeXkbInfo *self, const gchar *layout_id) { GnomeXkbInfoPrivate *priv; Layout *layout; GList *list; g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL); priv = self->priv; if (!ensure_rules_are_parsed (self)) return NULL; layout = g_hash_table_lookup (priv->layouts_table, layout_id); if (!layout) return NULL; list = NULL; g_slist_foreach (layout->iso639Ids, collect_languages, &list); return list; }