diff options
author | Rui Matos <tiagomatos@gmail.com> | 2012-05-11 01:15:23 +0200 |
---|---|---|
committer | Rui Matos <tiagomatos@gmail.com> | 2012-05-15 02:17:01 +0200 |
commit | d47beb45612eb2b134f4d30a7872f39ee84b1ae3 (patch) | |
tree | ea7ea27d531adb4c6063893acfbe8e8cd43405b4 | |
parent | ad2a238420dc77b99301f4fe9f40022a8c4a3b4c (diff) | |
download | gnome-control-center-wip/input-sources.tar.gz |
region: Get the available XKB layouts from the XKB rules filewip/input-sources
Instead of having just a handful of harcoded ones. The IBus input
sources that we claim to support are still hardcoded for now.
-rw-r--r-- | configure.ac | 10 | ||||
-rw-r--r-- | panels/region/Makefile.am | 7 | ||||
-rw-r--r-- | panels/region/gnome-region-panel-input.c | 241 | ||||
-rw-r--r-- | panels/region/xkb-rules-db.c | 529 | ||||
-rw-r--r-- | panels/region/xkb-rules-db.h | 38 |
5 files changed, 667 insertions, 158 deletions
diff --git a/configure.ac b/configure.ac index 4f2a50eba..3561ab47b 100644 --- a/configure.ac +++ b/configure.ac @@ -113,7 +113,8 @@ PKG_CHECK_MODULES(COLOR_PANEL, $COMMON_MODULES colord >= 0.1.8) PKG_CHECK_MODULES(PRINTERS_PANEL, $COMMON_MODULES polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION) PKG_CHECK_MODULES(REGION_PANEL, $COMMON_MODULES - polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION) + polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION + xkbfile) PKG_CHECK_MODULES(SCREEN_PANEL, $COMMON_MODULES) PKG_CHECK_MODULES(SOUND_PANEL, $COMMON_MODULES libxml-2.0 libcanberra-gtk3 >= $CANBERRA_REQUIRED_VERSION @@ -134,6 +135,13 @@ PKG_CHECK_MODULES(WACOM_PANEL, $COMMON_MODULES GDESKTOP_PREFIX=`$PKG_CONFIG --variable prefix gsettings-desktop-schemas` AC_SUBST(GDESKTOP_PREFIX) +AC_ARG_WITH(xkb-config-root, + AS_HELP_STRING([--with-xkb-config-root=<paths>], + [Set default XKB config root (default: ${datadir}/X11/xkb)]), + [XKBCONFIGROOT="$withval"], + [XKBCONFIGROOT=${datadir}/X11/xkb]) +AC_SUBST([XKBCONFIGROOT]) + # Check for NetworkManager ~0.9 PKG_CHECK_MODULES(NETWORK_MANAGER, NetworkManager >= $NETWORK_MANAGER_REQUIRED_VERSION libnm-glib >= $NETWORK_MANAGER_REQUIRED_VERSION diff --git a/panels/region/Makefile.am b/panels/region/Makefile.am index 00f82071e..0bb080a1c 100644 --- a/panels/region/Makefile.am +++ b/panels/region/Makefile.am @@ -1,12 +1,15 @@ # This is used in PANEL_CFLAGS cappletname = region +XKBCONFIGROOT=@XKBCONFIGROOT@ + INCLUDES = \ $(PANEL_CFLAGS) \ $(REGION_PANEL_CFLAGS) \ -DGNOMELOCALEDIR="\"$(datadir)/locale\"" \ -DGNOMECC_DATA_DIR="\"$(pkgdatadir)\"" \ -DGNOMECC_UI_DIR="\"$(uidir)\"" \ + -DDFLT_XKB_CONFIG_ROOT=\"$(XKBCONFIGROOT)\" \ -I$(srcdir)/../common/ \ $(NULL) @@ -24,7 +27,9 @@ libregion_la_SOURCES = \ gnome-region-panel-system.c \ gnome-region-panel-system.h \ gnome-region-panel-input.c \ - gnome-region-panel-input.h + gnome-region-panel-input.h \ + xkb-rules-db.c \ + xkb-rules-db.h libregion_la_LIBADD = $(PANEL_LIBS) $(REGION_PANEL_LIBS) $(builddir)/../common/liblanguage.la diff --git a/panels/region/gnome-region-panel-input.c b/panels/region/gnome-region-panel-input.c index 7157b3cac..7a6d41824 100644 --- a/panels/region/gnome-region-panel-input.c +++ b/panels/region/gnome-region-panel-input.c @@ -26,6 +26,7 @@ #include <glib.h> #include <glib/gi18n.h> +#include "xkb-rules-db.h" #include "gnome-region-panel-input.h" #define WID(s) GTK_WIDGET(gtk_builder_get_object (builder, s)) @@ -50,108 +51,22 @@ * - Allow changing shortcuts ? */ +typedef struct _InputSource InputSource; struct _InputSource { const gchar *name; - const gchar *layout; - const gchar *engine; + const gchar *short_name; + const gchar *xkb_layout; + const gchar *xkb_variant; + const gchar *ibus_engine; }; -static const struct _InputSource input_sources[] = - { - { "English (US)", "us", "" }, - { "Catalan", "ad", "" }, - { "Afghani", "af", "" }, - { "Arabic", "ara", "" }, - { "Albanian", "al", "" }, - { "Armenian", "am", "" }, - { "German (Austria)", "at", "" }, - { "Azerbaijani", "az", "" }, - { "Belarusian", "by", "" }, - { "Belgian", "be", "" }, - { "Bengali", "bd", "" }, - { "Indian", "in", "" }, - { "Bosnian", "ba", "" }, - { "Portuguese (Brazil)", "br", "" }, - { "Bulgarian", "bg", "" }, - { "Arabic (Morocco)", "ma", "" }, - { "English (Cameroon)", "cm", "" }, - { "Burmese", "mm", "" }, - { "French (Canada)", "ca", "" }, - { "French (Democratic Republic of the Congo)", "cd", "" }, - { "Chinese", "cn", "" }, - { "Croatian", "hr", "" }, - { "Czech", "cz", "" }, - { "Danish", "dk", "" }, - { "Dutch", "nl", "" }, - { "Dzongkha", "bt", "" }, - { "Estonian", "ee", "" }, - { "Persian", "ir", "" }, - { "Iraqi", "iq", "" }, - { "Faroese", "fo", "" }, - { "Finnish", "fi", "" }, - { "French", "fr", "" }, - { "English (Ghana)", "gh", "" }, - { "French (Guinea)", "gn", "" }, - { "Georgian", "ge", "" }, - { "German", "de", "" }, - { "Greek", "gr", "" }, - { "Hungarian", "hu", "" }, - { "Icelandic", "is", "" }, - { "Hebrew", "il", "" }, - { "Italian", "it", "" }, - { "Japanese", "jp", "anthy" }, - { "Kyrgyz", "kg", "" }, - { "Khmer (Cambodia)", "kh", "" }, - { "Kazakh", "kz", "" }, - { "Lao", "la", "" }, - { "Spanish (Latin American)", "latam","" }, - { "Lithuanian", "lt", "" }, - { "Latvian", "lv", "" }, - { "Maori", "mao", "" }, - { "Montenegrin", "me", "" }, - { "Macedonian", "mk", "" }, - { "Maltese", "mt", "" }, - { "Mongolian", "mn", "" }, - { "Norwegian", "no", "" }, - { "Polish", "pl", "" }, - { "Portuguese", "pt", "" }, - { "Romanian", "ro", "" }, - { "Russian", "ru", "" }, - { "Serbian", "rs", "" }, - { "Slovenian", "si", "" }, - { "Slovak", "sk", "" }, - { "Spanish", "es", "" }, - { "Swedish", "se", "" }, - { "German (Switzerland)", "ch", "" }, - { "Arabic (Syria)", "sy", "" }, - { "Tajik", "tj", "" }, - { "Sinhala", "lk", "" }, - { "Thai", "th", "" }, - { "Turkish", "tr", "" }, - { "Taiwanese", "tw", "" }, - { "Ukrainian", "ua", "" }, - { "English (UK)", "gb", "" }, - { "Uzbek", "uz", "" }, - { "Vietnamese", "vn", "" }, - { "Korean", "kr", "hangul" }, - { "Irish", "ie", "" }, - { "Urdu (Pakistan)", "pk", "" }, - { "Dhivehi", "mv", "" }, - { "English (South Africa)", "za", "" }, - { "Esperanto", "epo", "" }, - { "Nepali", "np", "" }, - { "English (Nigeria)", "ng", "" }, - { "Amharic", "et", "" }, - { "Wolof", "sn", "" }, - { "Braille", "brai", "" }, - { "Turkmen", "tm", "" }, - { "Bambara", "ml", "" }, - { "Swahili (Tanzania)", "tz", "" }, - { "Swahili (Kenya)", "ke", "" }, - { "Tswana", "bw", "" }, - { "Filipino", "ph", "" } - }; +static const InputSource ibus_sources[] = { + { "Chinese (Bopomofo)", "般", "us", "", "bopomofo" }, + { "Chinese (Pinyin)", "拼", "us", "", "pinyin" }, + { "Japanese (Anthy)", "あ", "jp", "", "anthy" }, + { "Korean (Hangul)", "한", "us", "", "hangul" }, +}; #define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources" @@ -165,24 +80,17 @@ static gboolean input_chooser_get_selected (GtkWidget *chooser, GtkTreeModel **model, GtkTreeIter *iter); -enum -{ - COL_NAME, - COL_LAYOUT, - COL_ENGINE -}; - static gboolean -add_source_to_hash (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) +add_source_to_table (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) { GHashTable *hash = data; gchar *name; - gtk_tree_model_get (model, iter, COL_NAME, &name, -1); - g_hash_table_insert (hash, name, GINT_TO_POINTER (1)); + gtk_tree_model_get (model, iter, 0, &name, -1); + g_hash_table_add (hash, name); return FALSE; } @@ -191,30 +99,35 @@ static void populate_model (GtkListStore *store, GtkListStore *active_sources) { - GHashTable *active_hash; + GHashTable *active_sources_table; GtkTreeIter iter; + GSList *all_sources, *tmp; gint i; - active_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + active_sources_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); gtk_tree_model_foreach (GTK_TREE_MODEL (active_sources), - add_source_to_hash, - active_hash); + add_source_to_table, + active_sources_table); - for (i = 0; i < G_N_ELEMENTS (input_sources); ++i) + all_sources = xkb_rules_db_get_all_layout_names (); + + for (i = 0; i < G_N_ELEMENTS (ibus_sources); ++i) + all_sources = g_slist_prepend (all_sources, (gpointer)ibus_sources[i].name); + + for (tmp = all_sources; tmp; tmp = tmp->next) { - if (g_hash_table_lookup (active_hash, input_sources[i].name)) + if (g_hash_table_contains (active_sources_table, tmp->data)) continue; gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, - COL_NAME, input_sources[i].name, - COL_LAYOUT, input_sources[i].layout, - COL_ENGINE, input_sources[i].engine, + 0, tmp->data, -1); } - g_hash_table_destroy (active_hash); + g_slist_free (all_sources); + g_hash_table_destroy (active_sources_table); } static void @@ -222,21 +135,17 @@ populate_with_active_sources (GtkListStore *store) { GVariant *sources; GVariantIter iter; - const gchar *layout; - const gchar *engine; const gchar *name; GtkTreeIter tree_iter; sources = g_settings_get_value (is_settings, KEY_INPUT_SOURCES); g_variant_iter_init (&iter, sources); - while (g_variant_iter_next (&iter, "(&s&s&s)", &name, &layout, &engine)) + while (g_variant_iter_next (&iter, "(&s&s&s&s&s)", &name, NULL, NULL, NULL, NULL)) { gtk_list_store_append (store, &tree_iter); gtk_list_store_set (store, &tree_iter, - COL_NAME, name, - COL_LAYOUT, layout, - COL_ENGINE, engine, + 0, name, -1); } @@ -248,23 +157,36 @@ update_configuration (GtkTreeModel *model) { GtkTreeIter iter; gchar *name; - gchar *layout; - gchar *engine; + const gchar *short_name = ""; + const gchar *xkb_layout = ""; + const gchar *xkb_variant = ""; + const gchar *ibus_engine = ""; GVariantBuilder builder; + gint i; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sss)")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssss)")); gtk_tree_model_get_iter_first (model, &iter); do { gtk_tree_model_get (model, &iter, - COL_NAME, &name, - COL_LAYOUT, &layout, - COL_ENGINE, &engine, + 0, &name, -1); - g_variant_builder_add (&builder, "(sss)", name, layout, engine); + + if (xkb_rules_db_get_layout_info (name, &short_name, &xkb_layout, &xkb_variant)) + ibus_engine = ""; + else + for (i = 0; i < G_N_ELEMENTS (ibus_sources); ++i) + if (strcmp (name, ibus_sources[i].name) == 0) + { + short_name = ibus_sources[i].short_name; + xkb_layout = ibus_sources[i].xkb_layout; + xkb_variant = ibus_sources[i].xkb_variant; + ibus_engine = ibus_sources[i].ibus_engine; + break; + } + + g_variant_builder_add (&builder, "(sssss)", name, short_name, xkb_layout, xkb_variant, ibus_engine); g_free (name); - g_free (layout); - g_free (engine); } while (gtk_tree_model_iter_next (model, &iter)); g_settings_set_value (is_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder)); @@ -358,14 +280,10 @@ chooser_response (GtkWidget *chooser, gint response_id, gpointer data) GtkTreeView *my_tv; GtkListStore *my_model; GtkTreeIter my_iter; - gchar *layout; - gchar *engine; gchar *name; gtk_tree_model_get (model, &iter, - COL_NAME, &name, - COL_LAYOUT, &layout, - COL_ENGINE, &engine, + 0, &name, -1); my_tv = GTK_TREE_VIEW (WID ("active_input_sources")); @@ -374,14 +292,9 @@ chooser_response (GtkWidget *chooser, gint response_id, gpointer data) gtk_list_store_append (my_model, &my_iter); gtk_list_store_set (my_model, &my_iter, - COL_NAME, name, - COL_LAYOUT, layout, - COL_ENGINE, engine, + 0, name, -1); - g_free (name); - g_free (engine); - g_free (layout); gtk_tree_selection_select_iter (gtk_tree_view_get_selection (my_tv), &my_iter); @@ -496,8 +409,11 @@ show_selected_layout (GtkButton *button, gpointer data) GtkBuilder *builder = data; GtkTreeModel *model; GtkTreeIter iter; - gchar *layout; + gchar *name; gchar *kbd_viewer_args; + const gchar *xkb_layout = ""; + const gchar *xkb_variant = ""; + gint i; g_debug ("show selected layout"); @@ -505,15 +421,29 @@ show_selected_layout (GtkButton *button, gpointer data) return; gtk_tree_model_get (model, &iter, - COL_LAYOUT, &layout, + 0, &name, -1); - kbd_viewer_args = g_strdup_printf ("gkbd-keyboard-display -l %s", layout); + if (!xkb_rules_db_get_layout_info (name, NULL, &xkb_layout, &xkb_variant)) + for (i = 0; i < G_N_ELEMENTS (ibus_sources); ++i) + if (strcmp (name, ibus_sources[i].name) == 0) + { + xkb_layout = ibus_sources[i].xkb_layout; + xkb_variant = ibus_sources[i].xkb_variant; + break; + } + + if (xkb_variant[0]) + kbd_viewer_args = g_strdup_printf ("gkbd-keyboard-display -l \"%s\t%s\"", + xkb_layout, xkb_variant); + else + kbd_viewer_args = g_strdup_printf ("gkbd-keyboard-display -l %s", + xkb_layout); g_spawn_command_line_async (kbd_viewer_args, NULL); g_free (kbd_viewer_args); - g_free (layout); + g_free (name); } /* Main setup {{{1 */ @@ -556,11 +486,10 @@ setup_input_tabs (GtkBuilder *builder, column = gtk_tree_view_column_new (); cell = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, cell, TRUE); - gtk_tree_view_column_add_attribute (column, cell, "text", COL_NAME); + gtk_tree_view_column_add_attribute (column, cell, "text", 0); gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); - /* layout, engine, name */ - store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + store = gtk_list_store_new (1, G_TYPE_STRING); populate_with_active_sources (store); @@ -710,7 +639,7 @@ filter_func (GtkTreeModel *model, return TRUE; gtk_tree_model_get (model, iter, - COL_NAME, &name, + 0, &name, -1); pattern = search_pattern_list; @@ -764,7 +693,7 @@ input_chooser_new (GtkBuilder *main_builder) visible_column = gtk_tree_view_column_new_with_attributes ("Input Sources", gtk_cell_renderer_text_new (), - "text", COL_NAME, + "text", 0, NULL); gtk_window_set_transient_for (GTK_WINDOW (chooser), @@ -793,7 +722,7 @@ input_chooser_new (GtkBuilder *main_builder) "active_input_sources"))))); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), - COL_NAME, GTK_SORT_ASCENDING); + 0, GTK_SORT_ASCENDING); gtk_tree_model_filter_set_visible_func (filtered_model, filter_func, diff --git a/panels/region/xkb-rules-db.c b/panels/region/xkb-rules-db.c new file mode 100644 index 000000000..9715eb592 --- /dev/null +++ b/panels/region/xkb-rules-db.c @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * Written by: Rui Matos <rmatos@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <X11/XKBlib.h> +#include <X11/extensions/XKBrules.h> + +#include <gdk/gdkx.h> + +#include "xkb-rules-db.h" + +#ifndef DFLT_XKB_CONFIG_ROOT +#define DFLT_XKB_CONFIG_ROOT "/usr/share/X11/xkb" +#endif +#ifndef DFLT_XKB_RULES_FILE +#define DFLT_XKB_RULES_FILE "base" +#endif +#ifndef DFLT_XKB_LAYOUT +#define DFLT_XKB_LAYOUT "us" +#endif +#ifndef DFLT_XKB_MODEL +#define DFLT_XKB_MODEL "pc105" +#endif + +typedef struct _Layout Layout; +struct _Layout +{ + gchar *id; + gchar *xkb_name; + gchar *short_id; + gboolean is_variant; + const Layout *main_layout; +}; + +static GHashTable *layouts_by_short_id = NULL; +static GHashTable *layouts_by_iso639 = NULL; +static GHashTable *layouts_table = NULL; +static Layout *current_parser_layout = NULL; +static Layout *current_parser_variant = NULL; +static gchar **current_parser_text = NULL; +static gchar *current_parser_iso639Id = NULL; + +static void +free_layout (gpointer data) +{ + Layout *layout = data; + + g_free (layout->id); + g_free (layout->xkb_name); + g_free (layout->short_id); + g_free (layout); +} + +static void +get_xkb_values (gchar **rules, + XkbRF_VarDefsRec *var_defs) +{ + Display *display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + *rules = NULL; + + /* Get it from the X property or fallback on defaults */ + if (!XkbRF_GetNamesProp (display, rules, var_defs) || !*rules) { + *rules = strdup (DFLT_XKB_RULES_FILE); + var_defs->model = strdup (DFLT_XKB_MODEL); + var_defs->layout = strdup (DFLT_XKB_LAYOUT); + var_defs->variant = NULL; + var_defs->options = NULL; + } +} + +static void +free_xkb_var_defs (XkbRF_VarDefsRec *p) +{ + if (p->model) + free (p->model); + if (p->layout) + free (p->layout); + if (p->variant) + free (p->variant); + if (p->options) + free (p->options); + free (p); +} + +static gchar * +get_rules_file_path (void) +{ + XkbRF_VarDefsRec *xkb_var_defs; + gchar *rules_file; + gchar *rules_path; + + xkb_var_defs = calloc (1, sizeof (XkbRF_VarDefsRec)); + get_xkb_values (&rules_file, xkb_var_defs); + + if (rules_file[0] == '/') + rules_path = g_strdup_printf ("%s.xml", rules_file); + else + rules_path = g_strdup_printf ("%s/rules/%s.xml", + DFLT_XKB_CONFIG_ROOT, + rules_file); + + free_xkb_var_defs (xkb_var_defs); + free (rules_file); + + return rules_path; +} + +static void +parse_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer data, + GError **error) +{ + if (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 (current_parser_variant) + current_parser_text = ¤t_parser_variant->xkb_name; + else if (current_parser_layout) + current_parser_text = ¤t_parser_layout->xkb_name; + } + else if (strcmp (element_name, "description") == 0) + { + if (current_parser_variant) + current_parser_text = ¤t_parser_variant->id; + else if (current_parser_layout) + current_parser_text = ¤t_parser_layout->id; + } + else if (strcmp (element_name, "shortDescription") == 0) + { + if (current_parser_variant) + current_parser_text = ¤t_parser_variant->short_id; + else if (current_parser_layout) + current_parser_text = ¤t_parser_layout->short_id; + } + else if (strcmp (element_name, "iso639Id") == 0) + { + current_parser_text = ¤t_parser_iso639Id; + } + else if (strcmp (element_name, "layout") == 0) + { + if (current_parser_layout) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'layout' elements can't be nested"); + return; + } + + current_parser_layout = g_new0 (Layout, 1); + } + else if (strcmp (element_name, "variant") == 0) + { + if (current_parser_variant) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'variant' elements can't be nested"); + return; + } + + if (!current_parser_layout) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'variant' elements must be inside 'layout' elements"); + return; + } + + current_parser_variant = g_new0 (Layout, 1); + current_parser_variant->is_variant = TRUE; + current_parser_variant->main_layout = current_parser_layout; + } +} + +static void +maybe_replace (GHashTable *table, + gchar *key, + Layout *new_layout) +{ + Layout *layout; + gboolean exists; + gboolean replace = TRUE; + + exists = g_hash_table_lookup_extended (table, key, NULL, (gpointer *)&layout); + if (exists) + replace = strlen (new_layout->id) < strlen (layout->id); + if (replace) + g_hash_table_replace (table, key, new_layout); +} + +static void +parse_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer data, + GError **error) +{ + if (strcmp (element_name, "layout") == 0) + { + if (!current_parser_layout->id || !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; + } + + if (current_parser_layout->short_id) + maybe_replace (layouts_by_short_id, + current_parser_layout->short_id, current_parser_layout); + + g_hash_table_replace (layouts_table, + current_parser_layout->id, + current_parser_layout); + current_parser_layout = NULL; + } + else if (strcmp (element_name, "variant") == 0) + { + if (!current_parser_variant->id || !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; + } + + if (current_parser_variant->short_id) + maybe_replace (layouts_by_short_id, + current_parser_variant->short_id, current_parser_variant); + + g_hash_table_replace (layouts_table, + current_parser_variant->id, + current_parser_variant); + current_parser_variant = NULL; + } + else if (strcmp (element_name, "iso639Id") == 0) + { + if (!current_parser_iso639Id) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'iso639Id' elements must enclose text"); + return; + } + + if (current_parser_layout) + maybe_replace (layouts_by_iso639, + current_parser_iso639Id, current_parser_layout); + else if (current_parser_variant) + maybe_replace (layouts_by_iso639, + current_parser_iso639Id, current_parser_variant); + else + g_free (current_parser_iso639Id); + + current_parser_iso639Id = NULL; + } +} + +static void +parse_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer data, + GError **error) +{ + if (current_parser_text) + { + *current_parser_text = g_strndup (text, text_len); + current_parser_text = NULL; + } +} + +static void +parse_error (GMarkupParseContext *context, + GError *error, + gpointer data) +{ + free_layout (current_parser_layout); + free_layout (current_parser_variant); + g_free (current_parser_iso639Id); +} + +static const GMarkupParser markup_parser = { + parse_start_element, + parse_end_element, + parse_text, + NULL, + parse_error +}; + +static void +parse_rules_file (void) +{ + gchar *buffer; + gsize length; + GMarkupParseContext *context; + GError *error = NULL; + gchar *full_path = get_rules_file_path (); + + g_file_get_contents (full_path, &buffer, &length, &error); + g_free (full_path); + if (error) + { + g_warning ("Failed to read XKB rules file: %s", error->message); + g_error_free (error); + return; + } + + layouts_by_short_id = g_hash_table_new (g_str_hash, g_str_equal); + layouts_by_iso639 = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + /* This is the "master" table so it assumes memory "ownership". */ + layouts_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_layout); + + context = g_markup_parse_context_new (&markup_parser, 0, NULL, NULL); + g_markup_parse_context_parse (context, buffer, length, &error); + g_markup_parse_context_free (context); + g_free (buffer); + if (error) + { + g_warning ("Failed to parse XKB rules file: %s", error->message); + g_error_free (error); + g_hash_table_destroy (layouts_by_short_id); + g_hash_table_destroy (layouts_by_iso639); + g_hash_table_destroy (layouts_table); + layouts_table = NULL; + return; + } +} + +static gboolean +ensure_rules_are_parsed (void) +{ + if (!layouts_table) + parse_rules_file (); + + return !!layouts_table; +} + +static void +add_name_to_list (gpointer key, + gpointer value, + gpointer data) +{ + GSList **list = data; + + *list = g_slist_prepend (*list, key); +} + +/** + * xkb_rules_db_get_all_layout_names: + * + * Returns a list of all layout names we know about. + * + * Return value: (transfer container): the list of layout names. The + * caller takes ownership of the #GSList but not of the strings + * themselves, those are internally allocated and must not be + * modified. + */ +GSList * +xkb_rules_db_get_all_layout_names (void) +{ + GSList *layout_names = NULL; + + if (!ensure_rules_are_parsed ()) + return NULL; + + g_hash_table_foreach (layouts_table, add_name_to_list, &layout_names); + + return layout_names; +} + +/** + * xkb_rules_db_get_layout_info: + * @name: layout's name about which to retrieve the info + * @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. 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. + */ +gboolean +xkb_rules_db_get_layout_info (const gchar *name, + const gchar **short_name, + const gchar **xkb_layout, + const gchar **xkb_variant) +{ + const Layout *layout; + + if (short_name) + *short_name = NULL; + if (xkb_layout) + *xkb_layout = NULL; + if (xkb_variant) + *xkb_variant = NULL; + + if (!ensure_rules_are_parsed ()) + return FALSE; + + if (!g_hash_table_lookup_extended (layouts_table, name, NULL, (gpointer *)&layout)) + return FALSE; + + if (!layout->is_variant) + { + if (short_name) + *short_name = layout->short_id ? layout->short_id : ""; + if (xkb_layout) + *xkb_layout = layout->xkb_name; + if (xkb_variant) + *xkb_variant = ""; + } + else + { + if (short_name) + *short_name = layout->short_id ? layout->short_id : + layout->main_layout->short_id ? layout->main_layout->short_id : ""; + if (xkb_layout) + *xkb_layout = layout->main_layout->xkb_name; + if (xkb_variant) + *xkb_variant = layout->xkb_name; + } + + return TRUE; +} + +/** + * xkb_rules_db_get_layout_info_for_language: + * @language: an ISO 639 code + * @name: (out) (allow-none) (transfer none): location to store the + * layout's 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 the layout that better fits @language. It also fetches + * information about that layout like xkb_rules_db_get_layout_info(). + * + * If the a layout can't be found the return value is %FALSE and all + * the (out) parameters are set to %NULL. + * + * Return value: %TRUE if a layout exists or %FALSE otherwise. + */ +gboolean +xkb_rules_db_get_layout_info_for_language (const gchar *language, + const gchar **name, + const gchar **short_name, + const gchar **xkb_layout, + const gchar **xkb_variant) +{ + const Layout *layout; + + if (name) + *name = NULL; + if (short_name) + *short_name = NULL; + if (xkb_layout) + *xkb_layout = NULL; + if (xkb_variant) + *xkb_variant = NULL; + + if (!ensure_rules_are_parsed ()) + return FALSE; + + if (!g_hash_table_lookup_extended (layouts_by_iso639, language, NULL, (gpointer *)&layout)) + if (!g_hash_table_lookup_extended (layouts_by_short_id, language, NULL, (gpointer *)&layout)) + return FALSE; + + if (name) + *name = layout->id; + + if (!layout->is_variant) + { + if (short_name) + *short_name = layout->short_id ? layout->short_id : ""; + if (xkb_layout) + *xkb_layout = layout->xkb_name; + if (xkb_variant) + *xkb_variant = ""; + } + else + { + if (short_name) + *short_name = layout->short_id ? layout->short_id : + layout->main_layout->short_id ? layout->main_layout->short_id : ""; + if (xkb_layout) + *xkb_layout = layout->main_layout->xkb_name; + if (xkb_variant) + *xkb_variant = layout->xkb_name; + } + + return TRUE; +} diff --git a/panels/region/xkb-rules-db.h b/panels/region/xkb-rules-db.h new file mode 100644 index 000000000..4387c45cb --- /dev/null +++ b/panels/region/xkb-rules-db.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * Written by: Rui Matos <rmatos@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef __XKB_RULES_DB_H__ +#define __XKB_RULES_DB_H__ + +#include <glib.h> + +GSList *xkb_rules_db_get_all_layout_names (void); +gboolean xkb_rules_db_get_layout_info (const gchar *name, + const gchar **short_name, + const gchar **xkb_layout, + const gchar **xkb_variant); +gboolean xkb_rules_db_get_layout_info_for_language (const gchar *language, + const gchar **name, + const gchar **short_name, + const gchar **xkb_layout, + const gchar **xkb_variant); + +#endif /* __XKB_RULES_DB_H__ */ |