summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRui Matos <tiagomatos@gmail.com>2012-05-21 17:25:47 +0200
committerBastien Nocera <hadess@hadess.net>2012-06-01 18:03:14 +0100
commitdb4d37b36c3140b1e9d83dbf58cc98fc6e6aa4ad (patch)
treecc983c2feb57b7bdf4503be024bfc844d4bc937d
parentbff570509478bafa5ac1a6744a7583b6f4d825bb (diff)
downloadgnome-desktop-db4d37b36c3140b1e9d83dbf58cc98fc6e6aa4ad.tar.gz
gnome-xkb-info: Added to parse and make XKB xml descriptions available
xkeyboard-config's xml descriptions contain useful information about XKB layouts. This class makes it easily available to several core components in the desktop. https://bugzilla.gnome.org/show_bug.cgi?id=676583
-rw-r--r--configure.ac13
-rw-r--r--libgnome-desktop/Makefile.am5
-rw-r--r--libgnome-desktop/gnome-xkb-info.c674
-rw-r--r--libgnome-desktop/gnome-xkb-info.h84
4 files changed, 774 insertions, 2 deletions
diff --git a/configure.ac b/configure.ac
index be495d4d..d3c1a623 100644
--- a/configure.ac
+++ b/configure.ac
@@ -144,7 +144,18 @@ AC_SUBST(XLIB_LIBS)
dnl pkg-config dependency checks
-PKG_CHECK_MODULES(GNOME_DESKTOP, gdk-pixbuf-2.0 >= $GDK_PIXBUF_REQUIRED gtk+-3.0 >= $GTK_REQUIRED glib-2.0 >= $GLIB_REQUIRED gio-2.0 >= $GLIB_REQUIRED gsettings-desktop-schemas >= $GSETTINGS_DESKTOP_SCHEMAS_REQUIRED xrandr >= $XRANDR_REQUIRED xext >= $XEXT_REQUIRED)
+PKG_CHECK_MODULES(GNOME_DESKTOP, gdk-pixbuf-2.0 >= $GDK_PIXBUF_REQUIRED
+ gtk+-3.0 >= $GTK_REQUIRED
+ glib-2.0 >= $GLIB_REQUIRED
+ gio-2.0 >= $GLIB_REQUIRED
+ gsettings-desktop-schemas >= $GSETTINGS_DESKTOP_SCHEMAS_REQUIRED
+ xrandr >= $XRANDR_REQUIRED
+ xext >= $XEXT_REQUIRED
+ xkeyboard-config
+ xkbfile)
+
+XKB_BASE=$($PKG_CONFIG --variable xkb_base xkeyboard-config)
+AC_SUBST(XKB_BASE)
AC_CACHE_CHECK(for timerfd_create(2) system call,
gnome_cv_timerfd,AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
diff --git a/libgnome-desktop/Makefile.am b/libgnome-desktop/Makefile.am
index 800d36c7..251f8f67 100644
--- a/libgnome-desktop/Makefile.am
+++ b/libgnome-desktop/Makefile.am
@@ -9,6 +9,7 @@ AM_CPPFLAGS = \
-DG_LOG_DOMAIN=\"GnomeDesktop\" \
-DGNOMELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale\"" \
-DPNP_IDS=\""$(PNP_IDS)"\" \
+ -DXKB_BASE=\""$(XKB_BASE)"\" \
$(DISABLE_DEPRECATED_CFLAGS)
AM_CFLAGS = $(WARN_CFLAGS)
@@ -26,6 +27,7 @@ introspection_sources = \
gnome-rr-labeler.c \
gnome-pnp-ids.c \
gnome-wall-clock.c \
+ gnome-xkb-info.c \
edid-parse.c
libgnome_desktop_3_la_SOURCES = \
@@ -60,7 +62,8 @@ libgnome_desktop_HEADERS = \
gnome-rr-config.h \
gnome-rr-labeler.h \
gnome-pnp-ids.h \
- gnome-wall-clock.h
+ gnome-wall-clock.h \
+ gnome-xkb-info.h
if USE_INTERNAL_PNP_IDS
pnpdatadir = $(datadir)/libgnome-desktop-3.0
diff --git a/libgnome-desktop/gnome-xkb-info.c b/libgnome-desktop/gnome-xkb-info.c
new file mode 100644
index 00000000..7fd73c75
--- /dev/null
+++ b/libgnome-desktop/gnome-xkb-info.c
@@ -0,0 +1,674 @@
+/*
+ * 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 <glib/gi18n-lib.h>
+#define XKEYBOARD_CONFIG_(String) ((char *) g_dgettext ("xkeyboard-config", String))
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-xkb-info.h"
+
+#ifndef XKB_RULES_FILE
+#define XKB_RULES_FILE "evdev"
+#endif
+#ifndef XKB_LAYOUT
+#define XKB_LAYOUT "us"
+#endif
+#ifndef XKB_MODEL
+#define XKB_MODEL "pc105+inet"
+#endif
+
+typedef struct _Layout Layout;
+struct _Layout
+{
+ gchar *id;
+ gchar *xkb_name;
+ gchar *short_desc;
+ gchar *description;
+ gboolean is_variant;
+ const Layout *main_layout;
+};
+
+struct _GnomeXkbInfoPrivate
+{
+ GHashTable *layouts_by_short_desc;
+ GHashTable *layouts_by_iso639;
+ GHashTable *layouts_table;
+
+ /* Only used while parsing */
+ Layout *current_parser_layout;
+ Layout *current_parser_variant;
+ gchar *current_parser_iso639Id;
+ gchar **current_parser_text;
+};
+
+G_DEFINE_TYPE (GnomeXkbInfo, gnome_xkb_info, G_TYPE_OBJECT);
+
+static void
+free_layout (gpointer data)
+{
+ Layout *layout = data;
+
+ g_return_if_fail (layout != NULL);
+
+ g_free (layout->id);
+ g_free (layout->xkb_name);
+ g_free (layout->short_desc);
+ g_free (layout->description);
+ g_free (layout);
+}
+
+/**
+ * gnome_xkb_info_get_var_defs: (skip)
+ * @rules: (out) (transfer full): location to store the rules file
+ * path. Use g_free() when it's no longer needed
+ * @var_defs: (out) (transfer full): location to store a
+ * #XkbRF_VarDefsRec pointer. Use gnome_xkb_info_free_var_defs() to
+ * free it
+ *
+ * Gets both the XKB rules file path and the current XKB parameters in
+ * use by the X server.
+ *
+ * Since: 3.6
+ */
+void
+gnome_xkb_info_get_var_defs (gchar **rules,
+ XkbRF_VarDefsRec **var_defs)
+{
+ Display *display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ char *tmp;
+
+ g_return_if_fail (rules != NULL);
+ g_return_if_fail (var_defs != NULL);
+
+ *rules = NULL;
+ *var_defs = g_new0 (XkbRF_VarDefsRec, 1);
+
+ gdk_error_trap_push ();
+
+ /* Get it from the X property or fallback on defaults */
+ if (!XkbRF_GetNamesProp (display, rules, *var_defs) || !*rules)
+ {
+ *rules = strdup (XKB_RULES_FILE);
+ (*var_defs)->model = strdup (XKB_MODEL);
+ (*var_defs)->layout = strdup (XKB_LAYOUT);
+ (*var_defs)->variant = NULL;
+ (*var_defs)->options = NULL;
+ }
+
+ gdk_error_trap_pop_ignored ();
+
+ tmp = *rules;
+
+ if (*rules[0] == '/')
+ *rules = g_strdup (*rules);
+ else
+ *rules = g_build_filename (XKB_BASE, "rules", *rules, NULL);
+
+ free (tmp);
+}
+
+/**
+ * gnome_xkb_info_free_var_defs: (skip)
+ * @var_defs: #XkbRF_VarDefsRec instance to free
+ *
+ * Frees an #XkbRF_VarDefsRec instance allocated by
+ * gnome_xkb_info_get_var_defs().
+ *
+ * Since: 3.6
+ */
+void
+gnome_xkb_info_free_var_defs (XkbRF_VarDefsRec *var_defs)
+{
+ g_return_if_fail (var_defs != NULL);
+
+ free (var_defs->model);
+ free (var_defs->layout);
+ free (var_defs->variant);
+ free (var_defs->options);
+
+ g_free (var_defs);
+}
+
+static gchar *
+get_xml_rules_file_path (void)
+{
+ XkbRF_VarDefsRec *xkb_var_defs;
+ gchar *rules_file;
+ gchar *xml_rules_file;
+
+ gnome_xkb_info_get_var_defs (&rules_file, &xkb_var_defs);
+ gnome_xkb_info_free_var_defs (xkb_var_defs);
+
+ xml_rules_file = g_strdup_printf ("%s.xml", rules_file);
+ 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 (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 (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, "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_new0 (Layout, 1);
+ }
+ else if (strcmp (element_name, "variant") == 0)
+ {
+ 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;
+ }
+
+ priv->current_parser_variant = g_new0 (Layout, 1);
+ priv->current_parser_variant->is_variant = TRUE;
+ priv->current_parser_variant->main_layout = priv->current_parser_layout;
+ }
+}
+
+static void
+maybe_replace (GHashTable *table,
+ gchar *key,
+ Layout *new_layout)
+{
+ /* There might be multiple layouts for the same language. In that
+ * case considering the "canonical" layout to be the one with the
+ * shorter description seems to be good enough. */
+ Layout *layout;
+ gboolean exists;
+ gboolean replace = TRUE;
+
+ exists = g_hash_table_lookup_extended (table, key, NULL, (gpointer *)&layout);
+ if (exists)
+ replace = strlen (new_layout->description) < strlen (layout->description);
+ 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)
+{
+ GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv;
+
+ if (strcmp (element_name, "layout") == 0)
+ {
+ if (!priv->current_parser_layout->description || !priv->current_parser_layout->xkb_name)
+ {
+ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+ "'layout' elements must enclose 'description' and 'name' elements");
+ return;
+ }
+
+ priv->current_parser_layout->id = g_strdup (priv->current_parser_layout->xkb_name);
+
+ if (priv->current_parser_layout->short_desc)
+ maybe_replace (priv->layouts_by_short_desc,
+ priv->current_parser_layout->short_desc, priv->current_parser_layout);
+
+ g_hash_table_replace (priv->layouts_table,
+ priv->current_parser_layout->id,
+ priv->current_parser_layout);
+ priv->current_parser_layout = NULL;
+ }
+ else if (strcmp (element_name, "variant") == 0)
+ {
+ if (!priv->current_parser_variant->description || !priv->current_parser_variant->xkb_name)
+ {
+ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+ "'variant' elements must enclose 'description' and 'name' elements");
+ return;
+ }
+
+ priv->current_parser_variant->id = g_strjoin ("+",
+ priv->current_parser_layout->xkb_name,
+ priv->current_parser_variant->xkb_name,
+ NULL);
+
+ if (priv->current_parser_variant->short_desc)
+ maybe_replace (priv->layouts_by_short_desc,
+ priv->current_parser_variant->short_desc, priv->current_parser_variant);
+
+ g_hash_table_replace (priv->layouts_table,
+ priv->current_parser_variant->id,
+ priv->current_parser_variant);
+ priv->current_parser_variant = NULL;
+ }
+ else if (strcmp (element_name, "iso639Id") == 0)
+ {
+ if (!priv->current_parser_iso639Id)
+ {
+ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+ "'iso639Id' elements must enclose text");
+ return;
+ }
+
+ if (priv->current_parser_layout)
+ maybe_replace (priv->layouts_by_iso639,
+ priv->current_parser_iso639Id, priv->current_parser_layout);
+ else if (priv->current_parser_variant)
+ maybe_replace (priv->layouts_by_iso639,
+ priv->current_parser_iso639Id, priv->current_parser_variant);
+ else
+ g_free (priv->current_parser_iso639Id);
+
+ priv->current_parser_iso639Id = NULL;
+ }
+}
+
+static void
+parse_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer data,
+ GError **error)
+{
+ GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv;
+
+ if (priv->current_parser_text)
+ {
+ *priv->current_parser_text = g_strndup (text, text_len);
+ priv->current_parser_text = NULL;
+ }
+}
+
+static void
+parse_error (GMarkupParseContext *context,
+ GError *error,
+ gpointer data)
+{
+ GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (data)->priv;
+
+ free_layout (priv->current_parser_layout);
+ free_layout (priv->current_parser_variant);
+ g_free (priv->current_parser_iso639Id);
+}
+
+static const GMarkupParser markup_parser = {
+ parse_start_element,
+ parse_end_element,
+ parse_text,
+ NULL,
+ parse_error
+};
+
+static void
+parse_rules_file (GnomeXkbInfo *self)
+{
+ GnomeXkbInfoPrivate *priv = self->priv;
+ gchar *buffer;
+ gsize length;
+ GMarkupParseContext *context;
+ GError *error = NULL;
+ gchar *file_path = get_xml_rules_file_path ();
+
+ g_file_get_contents (file_path, &buffer, &length, &error);
+ g_free (file_path);
+ if (error)
+ {
+ g_warning ("Failed to read XKB rules file: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ priv->layouts_by_short_desc = g_hash_table_new (g_str_hash, g_str_equal);
+ priv->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". */
+ priv->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, self, 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 (priv->layouts_by_short_desc);
+ priv->layouts_by_short_desc = NULL;
+ g_hash_table_destroy (priv->layouts_by_iso639);
+ priv->layouts_by_iso639 = NULL;
+ g_hash_table_destroy (priv->layouts_table);
+ priv->layouts_table = NULL;
+ }
+}
+
+static gboolean
+ensure_rules_are_parsed (GnomeXkbInfo *self)
+{
+ GnomeXkbInfoPrivate *priv = self->priv;
+
+ if (!priv->layouts_table)
+ parse_rules_file (self);
+
+ return !!priv->layouts_table;
+}
+
+static void
+gnome_xkb_info_init (GnomeXkbInfo *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GNOME_TYPE_XKB_INFO, GnomeXkbInfoPrivate);
+}
+
+static void
+gnome_xkb_info_finalize (GObject *self)
+{
+ GnomeXkbInfoPrivate *priv = GNOME_XKB_INFO (self)->priv;
+
+ if (priv->layouts_by_short_desc)
+ g_hash_table_destroy (priv->layouts_by_short_desc);
+ if (priv->layouts_by_iso639)
+ g_hash_table_destroy (priv->layouts_by_iso639);
+ if (priv->layouts_table)
+ g_hash_table_destroy (priv->layouts_table);
+
+ G_OBJECT_CLASS (gnome_xkb_info_parent_class)->finalize (self);
+}
+
+static void
+gnome_xkb_info_class_init (GnomeXkbInfoClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gnome_xkb_info_finalize;
+
+ g_type_class_add_private (gobject_class, sizeof (GnomeXkbInfoPrivate));
+}
+
+/**
+ * gnome_xkb_info_new:
+ *
+ * Returns: (transfer full): a new #GnomeXkbInfo instance.
+ */
+GnomeXkbInfo *
+gnome_xkb_info_new (void)
+{
+ return g_object_new (GNOME_TYPE_XKB_INFO, NULL);
+}
+
+/**
+ * gnome_xkb_info_get_all_layouts:
+ * @self: a #GnomeXkbInfo
+ *
+ * Returns a list of all layout identifiers we know about.
+ *
+ * Return value: (transfer container) (element-type gchar): the list
+ * of layout names. The caller takes ownership of the #GList but not
+ * of the strings themselves, those are internally allocated and must
+ * not be modified.
+ *
+ * Since: 3.6
+ */
+GList *
+gnome_xkb_info_get_all_layouts (GnomeXkbInfo *self)
+{
+ GnomeXkbInfoPrivate *priv;
+
+ g_return_val_if_fail (GNOME_IS_XKB_INFO (self), NULL);
+
+ priv = self->priv;
+
+ if (!ensure_rules_are_parsed (self))
+ return NULL;
+
+ return g_hash_table_get_keys (priv->layouts_table);
+}
+
+/**
+ * gnome_xkb_info_get_layout_info:
+ * @self: a #GnomeXkbInfo
+ * @id: layout's identifier about which to retrieve the info
+ * @display_name: (out) (allow-none) (transfer none): location to store
+ * the layout's display name, or %NULL
+ * @short_name: (out) (allow-none) (transfer none): location to store
+ * the layout's short name, or %NULL
+ * @xkb_layout: (out) (allow-none) (transfer none): location to store
+ * the layout's XKB name, or %NULL
+ * @xkb_variant: (out) (allow-none) (transfer none): location to store
+ * the layout's XKB variant, or %NULL
+ *
+ * Retrieves information about a layout. Both @display_name and
+ * @short_name are suitable to show in UIs and might be localized if
+ * translations are available.
+ *
+ * Some layouts don't provide a short name (2 or 3 letters) or don't
+ * specify a XKB variant, in those cases @short_name or @xkb_variant
+ * are empty strings, i.e. "".
+ *
+ * If the given layout doesn't exist the return value is %FALSE and
+ * all the (out) parameters are set to %NULL.
+ *
+ * Return value: %TRUE if the layout exists or %FALSE otherwise.
+ *
+ * Since: 3.6
+ */
+gboolean
+gnome_xkb_info_get_layout_info (GnomeXkbInfo *self,
+ const gchar *id,
+ const gchar **display_name,
+ const gchar **short_name,
+ const gchar **xkb_layout,
+ const gchar **xkb_variant)
+{
+ GnomeXkbInfoPrivate *priv;
+ const Layout *layout;
+
+ if (display_name)
+ *display_name = NULL;
+ if (short_name)
+ *short_name = NULL;
+ if (xkb_layout)
+ *xkb_layout = NULL;
+ if (xkb_variant)
+ *xkb_variant = NULL;
+
+ g_return_val_if_fail (GNOME_IS_XKB_INFO (self), FALSE);
+
+ priv = self->priv;
+
+ if (!ensure_rules_are_parsed (self))
+ return FALSE;
+
+ if (!g_hash_table_lookup_extended (priv->layouts_table, id, NULL, (gpointer *)&layout))
+ return FALSE;
+
+ if (display_name)
+ *display_name = XKEYBOARD_CONFIG_(layout->description);
+
+ if (!layout->is_variant)
+ {
+ if (short_name)
+ *short_name = XKEYBOARD_CONFIG_(layout->short_desc ? layout->short_desc : "");
+ if (xkb_layout)
+ *xkb_layout = layout->xkb_name;
+ if (xkb_variant)
+ *xkb_variant = "";
+ }
+ else
+ {
+ if (short_name)
+ *short_name = XKEYBOARD_CONFIG_(layout->short_desc ? layout->short_desc :
+ layout->main_layout->short_desc ? layout->main_layout->short_desc : "");
+ if (xkb_layout)
+ *xkb_layout = layout->main_layout->xkb_name;
+ if (xkb_variant)
+ *xkb_variant = layout->xkb_name;
+ }
+
+ return TRUE;
+}
+
+/**
+ * gnome_xkb_info_get_layout_info_for_language:
+ * @self: a #GnomeXkbInfo
+ * @language: an ISO 639 code
+ * @id: (out) (allow-none) (transfer none): location to store the
+ * layout's indentifier, or %NULL
+ * @display_name: (out) (allow-none) (transfer none): location to store
+ * the layout's display name, or %NULL
+ * @short_name: (out) (allow-none) (transfer none): location to store
+ * the layout's short name, or %NULL
+ * @xkb_layout: (out) (allow-none) (transfer none): location to store
+ * the layout's XKB name, or %NULL
+ * @xkb_variant: (out) (allow-none) (transfer none): location to store
+ * the layout's XKB variant, or %NULL
+ *
+ * Retrieves the layout that better fits @language. It also fetches
+ * information about that layout like gnome_xkb_info_get_layout_info().
+ *
+ * If 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.
+ *
+ * Since: 3.6
+ */
+gboolean
+gnome_xkb_info_get_layout_info_for_language (GnomeXkbInfo *self,
+ const gchar *language,
+ const gchar **id,
+ const gchar **display_name,
+ const gchar **short_name,
+ const gchar **xkb_layout,
+ const gchar **xkb_variant)
+{
+ GnomeXkbInfoPrivate *priv;
+ const Layout *layout;
+
+ if (id)
+ *id = NULL;
+ if (display_name)
+ *display_name = NULL;
+ if (short_name)
+ *short_name = NULL;
+ if (xkb_layout)
+ *xkb_layout = NULL;
+ if (xkb_variant)
+ *xkb_variant = NULL;
+
+ g_return_val_if_fail (GNOME_IS_XKB_INFO (self), FALSE);
+
+ priv = self->priv;
+
+ if (!ensure_rules_are_parsed (self))
+ return FALSE;
+
+ /* First look in the proper language codes index, if we can't find
+ * it there try again on the (untranslated) short descriptions since
+ * sometimes those will give us a good match. */
+ if (!g_hash_table_lookup_extended (priv->layouts_by_iso639, language, NULL, (gpointer *)&layout))
+ if (!g_hash_table_lookup_extended (priv->layouts_by_short_desc, language, NULL, (gpointer *)&layout))
+ return FALSE;
+
+ if (id)
+ *id = layout->id;
+ if (display_name)
+ *display_name = XKEYBOARD_CONFIG_(layout->description);
+
+ if (!layout->is_variant)
+ {
+ if (short_name)
+ *short_name = XKEYBOARD_CONFIG_(layout->short_desc ? layout->short_desc : "");
+ if (xkb_layout)
+ *xkb_layout = layout->xkb_name;
+ if (xkb_variant)
+ *xkb_variant = "";
+ }
+ else
+ {
+ if (short_name)
+ *short_name = XKEYBOARD_CONFIG_(layout->short_desc ? layout->short_desc :
+ layout->main_layout->short_desc ? layout->main_layout->short_desc : "");
+ if (xkb_layout)
+ *xkb_layout = layout->main_layout->xkb_name;
+ if (xkb_variant)
+ *xkb_variant = layout->xkb_name;
+ }
+
+ return TRUE;
+}
diff --git a/libgnome-desktop/gnome-xkb-info.h b/libgnome-desktop/gnome-xkb-info.h
new file mode 100644
index 00000000..38af9df8
--- /dev/null
+++ b/libgnome-desktop/gnome-xkb-info.h
@@ -0,0 +1,84 @@
+/*
+ * 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 __GNOME_XKB_INFO_H__
+#define __GNOME_XKB_INFO_H__
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#error This is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including gnome-xkb-info.h
+#endif
+
+#include <stdio.h>
+
+#include <glib-object.h>
+
+#include <X11/XKBlib.h>
+#include <X11/extensions/XKBrules.h>
+
+G_BEGIN_DECLS
+
+#define GNOME_TYPE_XKB_INFO (gnome_xkb_info_get_type ())
+#define GNOME_XKB_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_XKB_INFO, GnomeXkbInfo))
+#define GNOME_XKB_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_XKB_INFO, GnomeXkbInfoClass))
+#define GNOME_IS_XKB_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_XKB_INFO))
+#define GNOME_IS_XKB_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_XKB_INFO))
+#define GNOME_XKB_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_XKB_INFO, GnomeXkbInfoClass))
+
+typedef struct _GnomeXkbInfoPrivate GnomeXkbInfoPrivate;
+typedef struct _GnomeXkbInfo GnomeXkbInfo;
+typedef struct _GnomeXkbInfoClass GnomeXkbInfoClass;
+
+struct _GnomeXkbInfo
+{
+ GObject parent_object;
+
+ GnomeXkbInfoPrivate *priv;
+};
+
+struct _GnomeXkbInfoClass
+{
+ GObjectClass parent_class;
+};
+
+GType gnome_xkb_info_get_type (void);
+GnomeXkbInfo *gnome_xkb_info_new (void);
+GList *gnome_xkb_info_get_all_layouts (GnomeXkbInfo *self);
+gboolean gnome_xkb_info_get_layout_info (GnomeXkbInfo *self,
+ const gchar *id,
+ const gchar **display_name,
+ const gchar **short_name,
+ const gchar **xkb_layout,
+ const gchar **xkb_variant);
+gboolean gnome_xkb_info_get_layout_info_for_language (GnomeXkbInfo *self,
+ const gchar *language,
+ const gchar **id,
+ const gchar **display_name,
+ const gchar **short_name,
+ const gchar **xkb_layout,
+ const gchar **xkb_variant);
+
+void gnome_xkb_info_get_var_defs (gchar **rules,
+ XkbRF_VarDefsRec **var_defs);
+void gnome_xkb_info_free_var_defs (XkbRF_VarDefsRec *var_defs);
+
+G_END_DECLS
+
+#endif /* __GNOME_XKB_INFO_H__ */