diff options
author | Rui Matos <tiagomatos@gmail.com> | 2012-05-15 01:52:22 +0200 |
---|---|---|
committer | Rui Matos <tiagomatos@gmail.com> | 2012-05-15 01:58:11 +0200 |
commit | 0a1ca8584b73ce396dd3b9297c15079dda9a01a6 (patch) | |
tree | 72ce7c276cbf996ee17f8d1be6f44af52a8dac29 | |
parent | fbdac0e6dc39b32fc386f391f227e31a05a39670 (diff) | |
download | gnome-settings-daemon-wip/input-sources.tar.gz |
keyboard: Always add a latin and a UI language XKB layoutswip/input-sources
Toolkits need to know about both a latin layout to handle
accelerators which are usually defined like Ctrl+C and a
layout with the symbols for the language used in UI strings
to handle mnemonics like Alt+Ф, so we try to find and add
them in XKB group slots after the layout which the user
actually intends to type with.
-rw-r--r-- | plugins/keyboard/Makefile.am | 4 | ||||
-rw-r--r-- | plugins/keyboard/gsd-keyboard-manager.c | 89 | ||||
-rw-r--r-- | plugins/keyboard/xkb-rules-db.c | 529 | ||||
-rw-r--r-- | plugins/keyboard/xkb-rules-db.h | 38 |
4 files changed, 653 insertions, 7 deletions
diff --git a/plugins/keyboard/Makefile.am b/plugins/keyboard/Makefile.am index 2b90548f..077d12e9 100644 --- a/plugins/keyboard/Makefile.am +++ b/plugins/keyboard/Makefile.am @@ -20,6 +20,8 @@ libkeyboard_la_SOURCES = \ gsd-keyboard-plugin.c \ gsd-keyboard-manager.h \ gsd-keyboard-manager.c \ + xkb-rules-db.c \ + xkb-rules-db.h \ $(NULL) XKBCONFIGROOT=@XKBCONFIGROOT@ @@ -57,6 +59,8 @@ test_keyboard_SOURCES = \ test-keyboard.c \ gsd-keyboard-manager.h \ gsd-keyboard-manager.c \ + xkb-rules-db.c \ + xkb-rules-db.h \ $(NULL) test_keyboard_CFLAGS = $(libkeyboard_la_CFLAGS) diff --git a/plugins/keyboard/gsd-keyboard-manager.c b/plugins/keyboard/gsd-keyboard-manager.c index 5609520e..202ba68f 100644 --- a/plugins/keyboard/gsd-keyboard-manager.c +++ b/plugins/keyboard/gsd-keyboard-manager.c @@ -50,6 +50,7 @@ #include "gsd-keyboard-manager.h" #include "gsd-input-helper.h" #include "gsd-enums.h" +#include "xkb-rules-db.h" #define GSD_KEYBOARD_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_KEYBOARD_MANAGER, GsdKeyboardManagerPrivate)) @@ -296,6 +297,86 @@ upload_xkb_description (gchar *rules_file, g_warning ("Couldn't update the XKB root window property"); } +static gchar * +language_code_from_locale (const gchar *locale) +{ + if (!locale || !locale[0] || !locale[1]) + return NULL; + + if (!locale[2] || locale[2] == '_' || locale[2] == '.') + return g_strndup (locale, 2); + + if (!locale[3] || locale[3] == '_' || locale[3] == '.') + return g_strndup (locale, 3); + + return NULL; +} + +static void +replace_layout_and_variant (XkbRF_VarDefsRec *xkb_var_defs, + const gchar *layout, + const gchar *variant) +{ + /* Toolkits need to know about both a latin layout to handle + * accelerators which are usually defined like Ctrl+C and a + * layout with the symbols for the language used in UI strings + * to handle mnemonics like Alt+Ф, so we try to find and add + * them in XKB group slots after the layout which the user + * actually intends to type with. */ + const gchar *latin_layout = "us"; + const gchar *latin_variant = ""; + const gchar *locale_layout; + const gchar *locale_variant; + const gchar *locale = setlocale (LC_MESSAGES, NULL); + gchar *language = language_code_from_locale (locale); + + xkb_rules_db_get_layout_info_for_language (language, + NULL, + NULL, + &locale_layout, + &locale_variant); + g_free (language); + + if ((g_strcmp0 (latin_layout, locale_layout) == 0 && + g_strcmp0 (latin_variant, locale_variant) == 0) + || + (g_strcmp0 (latin_layout, layout) == 0 && + g_strcmp0 (latin_variant, variant) == 0)) { + latin_layout = NULL; + latin_variant = NULL; + } + + if (g_strcmp0 (locale_layout, layout) == 0 && + g_strcmp0 (locale_variant, variant) == 0) { + locale_layout = NULL; + locale_variant = NULL; + } + + if (xkb_var_defs->layout) + free (xkb_var_defs->layout); + + xkb_var_defs->layout = + locale_layout && latin_layout ? + g_strdup_printf ("%s,%s,%s", layout, locale_layout, latin_layout) : + locale_layout ? + g_strdup_printf ("%s,%s", layout, locale_layout) : + latin_layout ? + g_strdup_printf ("%s,%s", layout, latin_layout) : + g_strdup_printf ("%s", layout); + + if (xkb_var_defs->variant) + free (xkb_var_defs->variant); + + xkb_var_defs->variant = + locale_variant && latin_variant ? + g_strdup_printf ("%s,%s,%s", variant, locale_variant, latin_variant) : + locale_variant ? + g_strdup_printf ("%s,%s", variant, locale_variant) : + latin_variant ? + g_strdup_printf ("%s,%s", variant, latin_variant) : + g_strdup_printf ("%s", variant); +} + static void apply_xkb_layout (GsdKeyboardManager *manager, const gchar *layout, @@ -317,13 +398,7 @@ apply_xkb_layout (GsdKeyboardManager *manager, DFLT_XKB_CONFIG_ROOT, rules_file); - /* Replace the layout with our current setting */ - if (xkb_var_defs->layout) - free (xkb_var_defs->layout); - xkb_var_defs->layout = strdup (layout); - if (xkb_var_defs->variant) - free (xkb_var_defs->variant); - xkb_var_defs->variant = strdup (variant); + replace_layout_and_variant (xkb_var_defs, layout, variant); xkb_rules = XkbRF_Load (rules_path, "C", True, True); if (xkb_rules) { diff --git a/plugins/keyboard/xkb-rules-db.c b/plugins/keyboard/xkb-rules-db.c new file mode 100644 index 00000000..9715eb59 --- /dev/null +++ b/plugins/keyboard/xkb-rules-db.c @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * Written by: Rui Matos <rmatos@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <X11/XKBlib.h> +#include <X11/extensions/XKBrules.h> + +#include <gdk/gdkx.h> + +#include "xkb-rules-db.h" + +#ifndef DFLT_XKB_CONFIG_ROOT +#define DFLT_XKB_CONFIG_ROOT "/usr/share/X11/xkb" +#endif +#ifndef DFLT_XKB_RULES_FILE +#define DFLT_XKB_RULES_FILE "base" +#endif +#ifndef DFLT_XKB_LAYOUT +#define DFLT_XKB_LAYOUT "us" +#endif +#ifndef DFLT_XKB_MODEL +#define DFLT_XKB_MODEL "pc105" +#endif + +typedef struct _Layout Layout; +struct _Layout +{ + gchar *id; + gchar *xkb_name; + gchar *short_id; + gboolean is_variant; + const Layout *main_layout; +}; + +static GHashTable *layouts_by_short_id = NULL; +static GHashTable *layouts_by_iso639 = NULL; +static GHashTable *layouts_table = NULL; +static Layout *current_parser_layout = NULL; +static Layout *current_parser_variant = NULL; +static gchar **current_parser_text = NULL; +static gchar *current_parser_iso639Id = NULL; + +static void +free_layout (gpointer data) +{ + Layout *layout = data; + + g_free (layout->id); + g_free (layout->xkb_name); + g_free (layout->short_id); + g_free (layout); +} + +static void +get_xkb_values (gchar **rules, + XkbRF_VarDefsRec *var_defs) +{ + Display *display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + *rules = NULL; + + /* Get it from the X property or fallback on defaults */ + if (!XkbRF_GetNamesProp (display, rules, var_defs) || !*rules) { + *rules = strdup (DFLT_XKB_RULES_FILE); + var_defs->model = strdup (DFLT_XKB_MODEL); + var_defs->layout = strdup (DFLT_XKB_LAYOUT); + var_defs->variant = NULL; + var_defs->options = NULL; + } +} + +static void +free_xkb_var_defs (XkbRF_VarDefsRec *p) +{ + if (p->model) + free (p->model); + if (p->layout) + free (p->layout); + if (p->variant) + free (p->variant); + if (p->options) + free (p->options); + free (p); +} + +static gchar * +get_rules_file_path (void) +{ + XkbRF_VarDefsRec *xkb_var_defs; + gchar *rules_file; + gchar *rules_path; + + xkb_var_defs = calloc (1, sizeof (XkbRF_VarDefsRec)); + get_xkb_values (&rules_file, xkb_var_defs); + + if (rules_file[0] == '/') + rules_path = g_strdup_printf ("%s.xml", rules_file); + else + rules_path = g_strdup_printf ("%s/rules/%s.xml", + DFLT_XKB_CONFIG_ROOT, + rules_file); + + free_xkb_var_defs (xkb_var_defs); + free (rules_file); + + return rules_path; +} + +static void +parse_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer data, + GError **error) +{ + if (current_parser_text) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Expected character data but got element '%s'", element_name); + return; + } + + if (strcmp (element_name, "name") == 0) + { + if (current_parser_variant) + current_parser_text = ¤t_parser_variant->xkb_name; + else if (current_parser_layout) + current_parser_text = ¤t_parser_layout->xkb_name; + } + else if (strcmp (element_name, "description") == 0) + { + if (current_parser_variant) + current_parser_text = ¤t_parser_variant->id; + else if (current_parser_layout) + current_parser_text = ¤t_parser_layout->id; + } + else if (strcmp (element_name, "shortDescription") == 0) + { + if (current_parser_variant) + current_parser_text = ¤t_parser_variant->short_id; + else if (current_parser_layout) + current_parser_text = ¤t_parser_layout->short_id; + } + else if (strcmp (element_name, "iso639Id") == 0) + { + current_parser_text = ¤t_parser_iso639Id; + } + else if (strcmp (element_name, "layout") == 0) + { + if (current_parser_layout) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'layout' elements can't be nested"); + return; + } + + current_parser_layout = g_new0 (Layout, 1); + } + else if (strcmp (element_name, "variant") == 0) + { + if (current_parser_variant) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'variant' elements can't be nested"); + return; + } + + if (!current_parser_layout) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'variant' elements must be inside 'layout' elements"); + return; + } + + current_parser_variant = g_new0 (Layout, 1); + current_parser_variant->is_variant = TRUE; + current_parser_variant->main_layout = current_parser_layout; + } +} + +static void +maybe_replace (GHashTable *table, + gchar *key, + Layout *new_layout) +{ + Layout *layout; + gboolean exists; + gboolean replace = TRUE; + + exists = g_hash_table_lookup_extended (table, key, NULL, (gpointer *)&layout); + if (exists) + replace = strlen (new_layout->id) < strlen (layout->id); + if (replace) + g_hash_table_replace (table, key, new_layout); +} + +static void +parse_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer data, + GError **error) +{ + if (strcmp (element_name, "layout") == 0) + { + if (!current_parser_layout->id || !current_parser_layout->xkb_name) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'layout' elements must enclose 'description' and 'name' elements"); + return; + } + + if (current_parser_layout->short_id) + maybe_replace (layouts_by_short_id, + current_parser_layout->short_id, current_parser_layout); + + g_hash_table_replace (layouts_table, + current_parser_layout->id, + current_parser_layout); + current_parser_layout = NULL; + } + else if (strcmp (element_name, "variant") == 0) + { + if (!current_parser_variant->id || !current_parser_variant->xkb_name) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'variant' elements must enclose 'description' and 'name' elements"); + return; + } + + if (current_parser_variant->short_id) + maybe_replace (layouts_by_short_id, + current_parser_variant->short_id, current_parser_variant); + + g_hash_table_replace (layouts_table, + current_parser_variant->id, + current_parser_variant); + current_parser_variant = NULL; + } + else if (strcmp (element_name, "iso639Id") == 0) + { + if (!current_parser_iso639Id) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "'iso639Id' elements must enclose text"); + return; + } + + if (current_parser_layout) + maybe_replace (layouts_by_iso639, + current_parser_iso639Id, current_parser_layout); + else if (current_parser_variant) + maybe_replace (layouts_by_iso639, + current_parser_iso639Id, current_parser_variant); + else + g_free (current_parser_iso639Id); + + current_parser_iso639Id = NULL; + } +} + +static void +parse_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer data, + GError **error) +{ + if (current_parser_text) + { + *current_parser_text = g_strndup (text, text_len); + current_parser_text = NULL; + } +} + +static void +parse_error (GMarkupParseContext *context, + GError *error, + gpointer data) +{ + free_layout (current_parser_layout); + free_layout (current_parser_variant); + g_free (current_parser_iso639Id); +} + +static const GMarkupParser markup_parser = { + parse_start_element, + parse_end_element, + parse_text, + NULL, + parse_error +}; + +static void +parse_rules_file (void) +{ + gchar *buffer; + gsize length; + GMarkupParseContext *context; + GError *error = NULL; + gchar *full_path = get_rules_file_path (); + + g_file_get_contents (full_path, &buffer, &length, &error); + g_free (full_path); + if (error) + { + g_warning ("Failed to read XKB rules file: %s", error->message); + g_error_free (error); + return; + } + + layouts_by_short_id = g_hash_table_new (g_str_hash, g_str_equal); + layouts_by_iso639 = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + /* This is the "master" table so it assumes memory "ownership". */ + layouts_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_layout); + + context = g_markup_parse_context_new (&markup_parser, 0, NULL, NULL); + g_markup_parse_context_parse (context, buffer, length, &error); + g_markup_parse_context_free (context); + g_free (buffer); + if (error) + { + g_warning ("Failed to parse XKB rules file: %s", error->message); + g_error_free (error); + g_hash_table_destroy (layouts_by_short_id); + g_hash_table_destroy (layouts_by_iso639); + g_hash_table_destroy (layouts_table); + layouts_table = NULL; + return; + } +} + +static gboolean +ensure_rules_are_parsed (void) +{ + if (!layouts_table) + parse_rules_file (); + + return !!layouts_table; +} + +static void +add_name_to_list (gpointer key, + gpointer value, + gpointer data) +{ + GSList **list = data; + + *list = g_slist_prepend (*list, key); +} + +/** + * xkb_rules_db_get_all_layout_names: + * + * Returns a list of all layout names we know about. + * + * Return value: (transfer container): the list of layout names. The + * caller takes ownership of the #GSList but not of the strings + * themselves, those are internally allocated and must not be + * modified. + */ +GSList * +xkb_rules_db_get_all_layout_names (void) +{ + GSList *layout_names = NULL; + + if (!ensure_rules_are_parsed ()) + return NULL; + + g_hash_table_foreach (layouts_table, add_name_to_list, &layout_names); + + return layout_names; +} + +/** + * xkb_rules_db_get_layout_info: + * @name: layout's name about which to retrieve the info + * @short_name: (out) (allow-none) (transfer none): location to store + * the layout's short name, or %NULL + * @xkb_layout: (out) (allow-none) (transfer none): location to store + * the layout's XKB name, or %NULL + * @xkb_variant: (out) (allow-none) (transfer none): location to store + * the layout's XKB variant, or %NULL + * + * Retrieves information about a layout. Some layouts don't provide a + * short name (2 or 3 letters) or don't specify a XKB variant, in + * those cases @short_name or @xkb_variant are empty strings, i.e. "". + * + * If the given layout doesn't exist the return value is %FALSE and + * all the (out) parameters are set to %NULL. + * + * Return value: %TRUE if the layout exists or %FALSE otherwise. + */ +gboolean +xkb_rules_db_get_layout_info (const gchar *name, + const gchar **short_name, + const gchar **xkb_layout, + const gchar **xkb_variant) +{ + const Layout *layout; + + if (short_name) + *short_name = NULL; + if (xkb_layout) + *xkb_layout = NULL; + if (xkb_variant) + *xkb_variant = NULL; + + if (!ensure_rules_are_parsed ()) + return FALSE; + + if (!g_hash_table_lookup_extended (layouts_table, name, NULL, (gpointer *)&layout)) + return FALSE; + + if (!layout->is_variant) + { + if (short_name) + *short_name = layout->short_id ? layout->short_id : ""; + if (xkb_layout) + *xkb_layout = layout->xkb_name; + if (xkb_variant) + *xkb_variant = ""; + } + else + { + if (short_name) + *short_name = layout->short_id ? layout->short_id : + layout->main_layout->short_id ? layout->main_layout->short_id : ""; + if (xkb_layout) + *xkb_layout = layout->main_layout->xkb_name; + if (xkb_variant) + *xkb_variant = layout->xkb_name; + } + + return TRUE; +} + +/** + * xkb_rules_db_get_layout_info_for_language: + * @language: an ISO 639 code + * @name: (out) (allow-none) (transfer none): location to store the + * layout's name, or %NULL + * @short_name: (out) (allow-none) (transfer none): location to store + * the layout's short name, or %NULL + * @xkb_layout: (out) (allow-none) (transfer none): location to store + * the layout's XKB name, or %NULL + * @xkb_variant: (out) (allow-none) (transfer none): location to store + * the layout's XKB variant, or %NULL + * + * Retrieves the layout that better fits @language. It also fetches + * information about that layout like xkb_rules_db_get_layout_info(). + * + * If the a layout can't be found the return value is %FALSE and all + * the (out) parameters are set to %NULL. + * + * Return value: %TRUE if a layout exists or %FALSE otherwise. + */ +gboolean +xkb_rules_db_get_layout_info_for_language (const gchar *language, + const gchar **name, + const gchar **short_name, + const gchar **xkb_layout, + const gchar **xkb_variant) +{ + const Layout *layout; + + if (name) + *name = NULL; + if (short_name) + *short_name = NULL; + if (xkb_layout) + *xkb_layout = NULL; + if (xkb_variant) + *xkb_variant = NULL; + + if (!ensure_rules_are_parsed ()) + return FALSE; + + if (!g_hash_table_lookup_extended (layouts_by_iso639, language, NULL, (gpointer *)&layout)) + if (!g_hash_table_lookup_extended (layouts_by_short_id, language, NULL, (gpointer *)&layout)) + return FALSE; + + if (name) + *name = layout->id; + + if (!layout->is_variant) + { + if (short_name) + *short_name = layout->short_id ? layout->short_id : ""; + if (xkb_layout) + *xkb_layout = layout->xkb_name; + if (xkb_variant) + *xkb_variant = ""; + } + else + { + if (short_name) + *short_name = layout->short_id ? layout->short_id : + layout->main_layout->short_id ? layout->main_layout->short_id : ""; + if (xkb_layout) + *xkb_layout = layout->main_layout->xkb_name; + if (xkb_variant) + *xkb_variant = layout->xkb_name; + } + + return TRUE; +} diff --git a/plugins/keyboard/xkb-rules-db.h b/plugins/keyboard/xkb-rules-db.h new file mode 100644 index 00000000..4387c45c --- /dev/null +++ b/plugins/keyboard/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__ */ |