summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRui Matos <tiagomatos@gmail.com>2012-05-11 01:15:23 +0200
committerRui Matos <tiagomatos@gmail.com>2012-05-15 02:17:01 +0200
commitd47beb45612eb2b134f4d30a7872f39ee84b1ae3 (patch)
treeea7ea27d531adb4c6063893acfbe8e8cd43405b4
parentad2a238420dc77b99301f4fe9f40022a8c4a3b4c (diff)
downloadgnome-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.ac10
-rw-r--r--panels/region/Makefile.am7
-rw-r--r--panels/region/gnome-region-panel-input.c241
-rw-r--r--panels/region/xkb-rules-db.c529
-rw-r--r--panels/region/xkb-rules-db.h38
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 = &current_parser_variant->xkb_name;
+ else if (current_parser_layout)
+ current_parser_text = &current_parser_layout->xkb_name;
+ }
+ else if (strcmp (element_name, "description") == 0)
+ {
+ if (current_parser_variant)
+ current_parser_text = &current_parser_variant->id;
+ else if (current_parser_layout)
+ current_parser_text = &current_parser_layout->id;
+ }
+ else if (strcmp (element_name, "shortDescription") == 0)
+ {
+ if (current_parser_variant)
+ current_parser_text = &current_parser_variant->short_id;
+ else if (current_parser_layout)
+ current_parser_text = &current_parser_layout->short_id;
+ }
+ else if (strcmp (element_name, "iso639Id") == 0)
+ {
+ current_parser_text = &current_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__ */