[gnome-control-center/wip/input-sources: 15/15] region: Get the available XKB layouts from the XKB rules file



commit d47beb45612eb2b134f4d30a7872f39ee84b1ae3
Author: Rui Matos <tiagomatos gmail com>
Date:   Fri May 11 01:15:23 2012 +0200

    region: Get the available XKB layouts from the XKB rules file
    
    Instead of having just a handful of harcoded ones. The IBus input
    sources that we claim to support are still hardcoded for now.

 configure.ac                             |   10 +-
 panels/region/Makefile.am                |    7 +-
 panels/region/gnome-region-panel-input.c |  241 +++++---------
 panels/region/xkb-rules-db.c             |  529 ++++++++++++++++++++++++++++++
 panels/region/xkb-rules-db.h             |   38 +++
 5 files changed, 667 insertions(+), 158 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 4f2a50e..3561ab4 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 00f8207..0bb080a 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 7157b3c..7a6d418 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 0000000..9715eb5
--- /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 0000000..4387c45
--- /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__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]