[gnome-desktop] xkbinfo: use libxkbregistry to parse the rules files for us



commit 4f6bec60bfc781c59d5afb6f968fc94ad859e5b9
Author: Peter Hutterer <peter hutterer who-t net>
Date:   Wed May 13 15:59:49 2020 +1000

    xkbinfo: use libxkbregistry to parse the rules files for us
    
    Available in libxkbcommon 1.0.0 and later, libxkbregistry is a library wrapper
    around the evdev.xml rules file that we used to parse directly here. It
    provides a basic iteration API - load the evdev ruleset, then iterate through
    the layouts and options and copy the values over into our data structures as
    needed. This removes the need for XML parsing and error-checking, we can now
    rely on libxkbregistry to do this for us.
    
    The side-effect of this (and motivation for libxkbregistry) is that we
    automatically load user-specific XKB RMLVO as well where they are present.
    Together with mutter commit f71238732508d91bdfcb581c84697a516499a1eb this
    allows a user to drop up their custom XKB layouts in
    $XDG_CONFIG_DIR/xkb and have them both listed in the GUIs and working.

 .gitlab-ci.yml                    |   2 +-
 libgnome-desktop/gnome-xkb-info.c | 485 +++++++++-----------------------------
 libgnome-desktop/meson.build      |   1 +
 meson.build                       |   1 +
 4 files changed, 119 insertions(+), 370 deletions(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b1a5d8e4..6fe953d9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ image: fedora:rawhide
 variables:
   LAST_ABI_BREAK: 9d01763ba2a3f71b7c0aade04d2ffa6a883e308d
   DEPENDENCIES: gtk3-devel gsettings-desktop-schemas-devel gettext
-                gtk-doc xkeyboard-config-devel itstool
+                gtk-doc libxkbcommon-devel xkeyboard-config-devel itstool
                 gobject-introspection-devel systemd-devel iso-codes-devel
                 libseccomp-devel gcc gcc-c++ glibc-devel
                 meson redhat-rpm-config
diff --git a/libgnome-desktop/gnome-xkb-info.c b/libgnome-desktop/gnome-xkb-info.c
index f2f51abb..178c6580 100644
--- a/libgnome-desktop/gnome-xkb-info.c
+++ b/libgnome-desktop/gnome-xkb-info.c
@@ -21,6 +21,8 @@
 
 #include <config.h>
 
+#include <xkbcommon/xkbregistry.h>
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -73,15 +75,6 @@ struct _GnomeXkbInfoPrivate
   GHashTable *layouts_by_country;
   GHashTable *layouts_by_language;
   GHashTable *layouts_table;
-
-  /* Only used while parsing */
-  XkbOptionGroup *current_parser_group;
-  XkbOption *current_parser_option;
-  Layout *current_parser_layout;
-  Layout *current_parser_variant;
-  gchar  *current_parser_iso639Id;
-  gchar  *current_parser_iso3166Id;
-  gchar **current_parser_text;
 };
 
 G_DEFINE_TYPE_WITH_CODE (GnomeXkbInfo, gnome_xkb_info, G_TYPE_OBJECT,
@@ -128,164 +121,6 @@ free_option_group (gpointer data)
   g_slice_free (XkbOptionGroup, group);
 }
 
-static gchar *
-get_xml_rules_file_path (const gchar *suffix)
-{
-  const gchar *base_path;
-  gchar *rules_file;
-  gchar *xml_rules_file;
-
-  base_path = g_getenv ("XKB_CONFIG_ROOT");
-  if (!base_path)
-    base_path = XKB_BASE;
-
-  rules_file = g_build_filename (base_path, "rules", XKB_RULES_FILE, NULL);
-  xml_rules_file = g_strdup_printf ("%s%s", rules_file, suffix);
-  g_free (rules_file);
-
-  return xml_rules_file;
-}
-
-static void
-parse_start_element (GMarkupParseContext  *context,
-                     const gchar          *element_name,
-                     const gchar         **attribute_names,
-                     const gchar         **attribute_values,
-                     gpointer              data,
-                     GError              **error)
-{
-  GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv;
-
-  if (priv->current_parser_text)
-    {
-      g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                   "Expected character data but got element '%s'", element_name);
-      return;
-    }
-
-  if (strcmp (element_name, "name") == 0)
-    {
-      if (priv->current_parser_variant)
-        priv->current_parser_text = &priv->current_parser_variant->xkb_name;
-      else if (priv->current_parser_layout)
-        priv->current_parser_text = &priv->current_parser_layout->xkb_name;
-      else if (priv->current_parser_option)
-        priv->current_parser_text = &priv->current_parser_option->id;
-      else if (priv->current_parser_group)
-        priv->current_parser_text = &priv->current_parser_group->id;
-    }
-  else if (strcmp (element_name, "description") == 0)
-    {
-      if (priv->current_parser_variant)
-        priv->current_parser_text = &priv->current_parser_variant->description;
-      else if (priv->current_parser_layout)
-        priv->current_parser_text = &priv->current_parser_layout->description;
-      else if (priv->current_parser_option)
-        priv->current_parser_text = &priv->current_parser_option->description;
-      else if (priv->current_parser_group)
-        priv->current_parser_text = &priv->current_parser_group->description;
-    }
-  else if (strcmp (element_name, "shortDescription") == 0)
-    {
-      if (priv->current_parser_variant)
-        priv->current_parser_text = &priv->current_parser_variant->short_desc;
-      else if (priv->current_parser_layout)
-        priv->current_parser_text = &priv->current_parser_layout->short_desc;
-    }
-  else if (strcmp (element_name, "iso639Id") == 0)
-    {
-      priv->current_parser_text = &priv->current_parser_iso639Id;
-    }
-  else if (strcmp (element_name, "iso3166Id") == 0)
-    {
-      priv->current_parser_text = &priv->current_parser_iso3166Id;
-    }
-  else if (strcmp (element_name, "layout") == 0)
-    {
-      if (priv->current_parser_layout)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'layout' elements can't be nested");
-          return;
-        }
-
-      priv->current_parser_layout = g_slice_new0 (Layout);
-    }
-  else if (strcmp (element_name, "variant") == 0)
-    {
-      Layout *layout;
-
-      if (priv->current_parser_variant)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'variant' elements can't be nested");
-          return;
-        }
-
-      if (!priv->current_parser_layout)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'variant' elements must be inside 'layout' elements");
-          return;
-        }
-
-      if (!priv->current_parser_layout->xkb_name)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'variant' elements must be inside named 'layout' elements");
-          return;
-        }
-
-      layout = g_hash_table_lookup (priv->layouts_table, priv->current_parser_layout->xkb_name);
-      if (!layout)
-        layout = priv->current_parser_layout;
-
-      priv->current_parser_variant = g_slice_new0 (Layout);
-      priv->current_parser_variant->is_variant = TRUE;
-      priv->current_parser_variant->main_layout = layout;
-    }
-  else if (strcmp (element_name, "group") == 0)
-    {
-      if (priv->current_parser_group)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'group' elements can't be nested");
-          return;
-        }
-
-      priv->current_parser_group = g_slice_new0 (XkbOptionGroup);
-      /* Maps option ids to XkbOption structs. Owns the XkbOption structs. */
-      priv->current_parser_group->options_table = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                                         NULL, free_option);
-      g_markup_collect_attributes (element_name,
-                                   attribute_names,
-                                   attribute_values,
-                                   error,
-                                   G_MARKUP_COLLECT_BOOLEAN | G_MARKUP_COLLECT_OPTIONAL,
-                                   "allowMultipleSelection",
-                                   &priv->current_parser_group->allow_multiple_selection,
-                                   G_MARKUP_COLLECT_INVALID);
-    }
-  else if (strcmp (element_name, "option") == 0)
-    {
-      if (priv->current_parser_option)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'option' elements can't be nested");
-          return;
-        }
-
-      if (!priv->current_parser_group)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'option' elements must be inside 'group' elements");
-          return;
-        }
-
-      priv->current_parser_option = g_slice_new0 (XkbOption);
-    }
-}
-
 static void
 add_layout_to_table (GHashTable  *table,
                      const gchar *key,
@@ -350,205 +185,136 @@ add_layout_to_locale_tables (Layout     *layout,
     }
 }
 
-static void
-add_iso639 (Layout *layout,
-            gchar  *id)
-{
-  layout->iso639Ids = g_slist_prepend (layout->iso639Ids, id);
-}
-
-static void
-add_iso3166 (Layout *layout,
-             gchar  *id)
-{
-  layout->iso3166Ids = g_slist_prepend (layout->iso3166Ids, id);
-}
+enum layout_subsets {
+    ONLY_MAIN_LAYOUTS,
+    ONLY_VARIANTS,
+};
 
 static void
-parse_end_element (GMarkupParseContext  *context,
-                   const gchar          *element_name,
-                   gpointer              data,
-                   GError              **error)
+add_layouts (GnomeXkbInfo  *self, struct rxkb_context *ctx,
+            enum layout_subsets which)
 {
-  GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv;
+  GnomeXkbInfoPrivate *priv = self->priv;
+  struct rxkb_layout *layout;
 
-  if (strcmp (element_name, "layout") == 0)
+  for (layout = rxkb_layout_first (ctx);
+       layout;
+       layout = rxkb_layout_next (layout))
     {
-      if (!priv->current_parser_layout->description || !priv->current_parser_layout->xkb_name)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'layout' elements must enclose 'description' and 'name' elements");
-          return;
-        }
+      struct rxkb_iso639_code *iso639;
+      struct rxkb_iso3166_code *iso3166;
+      const char *name, *variant;
+      Layout *l;
 
-      priv->current_parser_layout->id = g_strdup (priv->current_parser_layout->xkb_name);
-
-      if (g_hash_table_contains (priv->layouts_table, priv->current_parser_layout->id))
-        {
-          g_clear_pointer (&priv->current_parser_layout, free_layout);
-          return;
-        }
-
-      g_hash_table_replace (priv->layouts_table,
-                            priv->current_parser_layout->id,
-                            priv->current_parser_layout);
-      add_layout_to_locale_tables (priv->current_parser_layout,
-                                   priv->layouts_by_language,
-                                   priv->layouts_by_country);
-      priv->current_parser_layout = NULL;
-    }
-  else if (strcmp (element_name, "variant") == 0)
-    {
-      if (!priv->current_parser_variant->description || !priv->current_parser_variant->xkb_name)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'variant' elements must enclose 'description' and 'name' elements");
-          return;
-        }
+      name = rxkb_layout_get_name (layout);
+      variant = rxkb_layout_get_variant (layout);
 
-      priv->current_parser_variant->id = g_strjoin ("+",
-                                                    priv->current_parser_layout->xkb_name,
-                                                    priv->current_parser_variant->xkb_name,
-                                                    NULL);
+      if ((which == ONLY_VARIANTS && variant == NULL) ||
+          (which == ONLY_MAIN_LAYOUTS && variant != NULL))
+          continue;
 
-      if (g_hash_table_contains (priv->layouts_table, priv->current_parser_variant->id))
+      l = g_slice_new0 (Layout);
+      if (variant)
         {
-          g_clear_pointer (&priv->current_parser_variant, free_layout);
-          return;
+          /* This relies on the main layouts being added first */
+          l->main_layout = g_hash_table_lookup (priv->layouts_table, name);
+          if (l->main_layout == NULL)
+           {
+               /* This is a bug in libxkbregistry */
+               g_warning ("Ignoring variant '%s(%s)' without a main layout",
+                          name, variant);
+               g_free (l);
+               continue;
+           }
+
+          l->xkb_name = g_strdup (variant);
+          l->is_variant = TRUE;
+          l->id = g_strjoin ("+", name, variant, NULL);
         }
-
-      g_hash_table_replace (priv->layouts_table,
-                            priv->current_parser_variant->id,
-                            priv->current_parser_variant);
-      add_layout_to_locale_tables (priv->current_parser_variant,
-                                   priv->layouts_by_language,
-                                   priv->layouts_by_country);
-      priv->current_parser_variant = NULL;
-    }
-  else if (strcmp (element_name, "iso639Id") == 0)
-    {
-      if (!priv->current_parser_iso639Id)
+      else
         {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'iso639Id' elements must enclose text");
-          return;
+          l->xkb_name = g_strdup (name);
+          l->id = g_strdup (name);
         }
-
-      if (priv->current_parser_variant)
-        add_iso639 (priv->current_parser_variant, priv->current_parser_iso639Id);
-      else if (priv->current_parser_layout)
-        add_iso639 (priv->current_parser_layout, priv->current_parser_iso639Id);
-
-      priv->current_parser_iso639Id = NULL;
-    }
-  else if (strcmp (element_name, "iso3166Id") == 0)
-    {
-      if (!priv->current_parser_iso3166Id)
+      l->description = g_strdup (rxkb_layout_get_description (layout));
+      l->short_desc = g_strdup (rxkb_layout_get_brief (layout));
+      for (iso639 = rxkb_layout_get_iso639_first (layout);
+           iso639;
+           iso639 = rxkb_iso639_code_next (iso639))
         {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'iso3166Id' elements must enclose text");
-          return;
+          char *id = g_strdup (rxkb_iso639_code_get_code (iso639));
+          l->iso3166Ids = g_slist_prepend (l->iso3166Ids, id);
         }
-
-      if (priv->current_parser_variant)
-        add_iso3166 (priv->current_parser_variant, priv->current_parser_iso3166Id);
-      else if (priv->current_parser_layout)
-        add_iso3166 (priv->current_parser_layout, priv->current_parser_iso3166Id);
-
-      priv->current_parser_iso3166Id = NULL;
-    }
-  else if (strcmp (element_name, "group") == 0)
-    {
-      if (!priv->current_parser_group->description || !priv->current_parser_group->id)
+      for (iso3166 = rxkb_layout_get_iso3166_first (layout);
+           iso3166;
+           iso3166 = rxkb_iso3166_code_next (iso3166))
         {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'group' elements must enclose 'description' and 'name' elements");
-          return;
+          char *id = g_strdup (rxkb_iso3166_code_get_code (iso3166));
+          l->iso3166Ids = g_slist_prepend (l->iso3166Ids, id);
         }
 
-      g_hash_table_replace (priv->option_groups_table,
-                            priv->current_parser_group->id,
-                            priv->current_parser_group);
-      priv->current_parser_group = NULL;
-    }
-  else if (strcmp (element_name, "option") == 0)
-    {
-      if (!priv->current_parser_option->description || !priv->current_parser_option->id)
-        {
-          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
-                       "'option' elements must enclose 'description' and 'name' elements");
-          return;
-        }
+      g_hash_table_replace (priv->layouts_table, l->id, l);
+      add_layout_to_locale_tables (l,
+                                   priv->layouts_by_language,
+                                   priv->layouts_by_country);
 
-      g_hash_table_replace (priv->current_parser_group->options_table,
-                            priv->current_parser_option->id,
-                            priv->current_parser_option);
-      priv->current_parser_option = NULL;
-    }
+   }
 }
-
-static void
-parse_text (GMarkupParseContext  *context,
-            const gchar          *text,
-            gsize                 text_len,
-            gpointer              data,
-            GError              **error)
+static bool
+parse_rules_file (GnomeXkbInfo  *self, const char *ruleset,
+                  bool include_extras)
 {
-  GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv;
-
-  if (priv->current_parser_text)
+  GnomeXkbInfoPrivate *priv = self->priv;
+  struct rxkb_context *ctx;
+  struct rxkb_option_group *group;
+  enum rxkb_context_flags flags = RXKB_CONTEXT_NO_FLAGS;
+
+  if (include_extras)
+      flags |= RXKB_CONTEXT_LOAD_EXOTIC_RULES;
+
+  ctx = rxkb_context_new (flags);
+  if (!rxkb_context_parse (ctx, ruleset)) {
+      rxkb_context_unref (ctx);
+      return FALSE;
+  }
+
+  /* libxkbregistry doesn't guarantee a sorting order of the layouts but we
+   * want to reference the main layout from the variants. So populate with
+   * the main layouts first, then add the variants */
+  add_layouts (self, ctx, ONLY_MAIN_LAYOUTS);
+  add_layouts (self, ctx, ONLY_VARIANTS);
+
+  for (group = rxkb_option_group_first (ctx);
+       group;
+       group = rxkb_option_group_next (group))
     {
-      *priv->current_parser_text = g_strndup (text, text_len);
-      priv->current_parser_text = NULL;
+        XkbOptionGroup *g;
+        struct rxkb_option *option;
+
+        g = g_slice_new (XkbOptionGroup);
+        g->id = g_strdup (rxkb_option_group_get_name (group));
+        g->description = g_strdup (rxkb_option_group_get_description (group));
+        g->options_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                  NULL, free_option);
+        g->allow_multiple_selection = rxkb_option_group_allows_multiple (group);
+        g_hash_table_replace (priv->option_groups_table, g->id, g);
+
+        for (option = rxkb_option_first (group);
+             option;
+             option = rxkb_option_next (option))
+          {
+            XkbOption *o;
+
+            o = g_slice_new (XkbOption);
+            o->id = g_strdup (rxkb_option_get_name (option));
+            o->description = g_strdup(rxkb_option_get_description (option));
+            g_hash_table_replace (g->options_table, o->id, o);
+          }
     }
-}
-
-static void
-parse_error (GMarkupParseContext *context,
-             GError              *error,
-             gpointer             data)
-{
-  GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv;
-
-  free_option_group (priv->current_parser_group);
-  free_option (priv->current_parser_option);
-  free_layout (priv->current_parser_layout);
-  free_layout (priv->current_parser_variant);
-  g_free (priv->current_parser_iso639Id);
-  g_free (priv->current_parser_iso3166Id);
-}
-
-static const GMarkupParser markup_parser = {
-  parse_start_element,
-  parse_end_element,
-  parse_text,
-  NULL,
-  parse_error
-};
 
-static void
-parse_rules_file (GnomeXkbInfo  *self,
-                  const gchar   *path,
-                  GError       **error)
-{
-  gchar *buffer;
-  gsize length;
-  GMarkupParseContext *context;
-  GError *sub_error = NULL;
+  rxkb_context_unref (ctx);
 
-  g_file_get_contents (path, &buffer, &length, &sub_error);
-  if (sub_error)
-    {
-      g_propagate_error (error, sub_error);
-      return;
-    }
-
-  context = g_markup_parse_context_new (&markup_parser, 0, self, NULL);
-  g_markup_parse_context_parse (context, buffer, length, &sub_error);
-  g_markup_parse_context_free (context);
-  g_free (buffer);
-  if (sub_error)
-    g_propagate_error (error, sub_error);
+  return TRUE;
 }
 
 static void
@@ -557,8 +323,6 @@ parse_rules (GnomeXkbInfo *self)
   GnomeXkbInfoPrivate *priv = self->priv;
   GSettings *settings;
   gboolean show_all_sources;
-  gchar *file_path;
-  GError *error = NULL;
 
   /* Make sure the translated strings we get from XKEYBOARD_CONFIG() are
    * in UTF-8 and not in the current locale */
@@ -581,35 +345,18 @@ parse_rules (GnomeXkbInfo *self)
   /* Maps layout ids to Layout structs. Owns the Layout structs. */
   priv->layouts_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_layout);
 
-  file_path = get_xml_rules_file_path (".xml");
-  parse_rules_file (self, file_path, &error);
-  if (error)
-    goto cleanup;
-  g_free (file_path);
-
   settings = g_settings_new ("org.gnome.desktop.input-sources");
   show_all_sources = g_settings_get_boolean (settings, "show-all-sources");
   g_object_unref (settings);
 
-  if (!show_all_sources)
-    return;
-
-  file_path = get_xml_rules_file_path (".extras.xml");
-  parse_rules_file (self, file_path, &error);
-  if (error)
-    goto cleanup;
-  g_free (file_path);
-
-  return;
-
- cleanup:
-  g_warning ("Failed to load XKB rules file %s: %s", file_path, error->message);
-  g_clear_pointer (&error, g_error_free);
-  g_clear_pointer (&file_path, g_free);
-  g_clear_pointer (&priv->option_groups_table, g_hash_table_destroy);
-  g_clear_pointer (&priv->layouts_by_country, g_hash_table_destroy);
-  g_clear_pointer (&priv->layouts_by_language, g_hash_table_destroy);
-  g_clear_pointer (&priv->layouts_table, g_hash_table_destroy);
+  if (!parse_rules_file (self, XKB_RULES_FILE, show_all_sources))
+    {
+      g_warning ("Failed to load '%s' XKB layouts", XKB_RULES_FILE);
+      g_clear_pointer (&priv->option_groups_table, g_hash_table_destroy);
+      g_clear_pointer (&priv->layouts_by_country, g_hash_table_destroy);
+      g_clear_pointer (&priv->layouts_by_language, g_hash_table_destroy);
+      g_clear_pointer (&priv->layouts_table, g_hash_table_destroy);
+    }
 }
 
 static gboolean
diff --git a/libgnome-desktop/meson.build b/libgnome-desktop/meson.build
index ca1e4b2a..b10a18df 100644
--- a/libgnome-desktop/meson.build
+++ b/libgnome-desktop/meson.build
@@ -80,6 +80,7 @@ gnome_desktop_deps = [
   libsystemd_dep,
   schemas_dep,
   xkb_config_dep,
+  xkbregistry_dep,
   iso_codes_dep,
   udev_dep,
   seccomp_dep
diff --git a/meson.build b/meson.build
index f18f83fc..166b0259 100644
--- a/meson.build
+++ b/meson.build
@@ -47,6 +47,7 @@ gio_unix_dep = dependency('gio-unix-2.0', version: glib_req)
 schemas_dep = dependency('gsettings-desktop-schemas', version: schemas_req)
 fontconfig_dep = dependency('fontconfig')
 xkb_config_dep = dependency('xkeyboard-config')
+xkbregistry_dep = dependency('xkbregistry')
 iso_codes_dep = dependency('iso-codes')
 
 libsystemd_dep = dependency('libsystemd', required: get_option('systemd'))


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