summaryrefslogtreecommitdiffstats
path: root/panels/keyboard/keyboard-shortcuts.c
diff options
context:
space:
mode:
Diffstat (limited to 'panels/keyboard/keyboard-shortcuts.c')
-rw-r--r--panels/keyboard/keyboard-shortcuts.c450
1 files changed, 450 insertions, 0 deletions
diff --git a/panels/keyboard/keyboard-shortcuts.c b/panels/keyboard/keyboard-shortcuts.c
new file mode 100644
index 0000000..cfaa11e
--- /dev/null
+++ b/panels/keyboard/keyboard-shortcuts.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2010 Intel, Inc
+ * Copyright (C) 2014 Red Hat, Inc
+ *
+ * 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 of the License, 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Thomas Wood <thomas.wood@intel.com>
+ * Rodrigo Moya <rodrigo@gnome.org>
+ * Christophe Fergeau <cfergeau@redhat.com>
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include "keyboard-shortcuts.h"
+
+#define CUSTOM_KEYS_BASENAME "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings"
+
+static char *
+replace_pictures_folder (const char *description)
+{
+ g_autoptr(GRegex) pictures_regex = NULL;
+ const char *path;
+ g_autofree gchar *dirname = NULL;
+ g_autofree gchar *ret = NULL;
+
+ if (description == NULL)
+ return NULL;
+
+ if (strstr (description, "$PICTURES") == NULL)
+ return g_strdup (description);
+
+ pictures_regex = g_regex_new ("\\$PICTURES", 0, 0, NULL);
+ path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+ dirname = g_filename_display_basename (path);
+ ret = g_regex_replace (pictures_regex, description, -1,
+ 0, dirname, 0, NULL);
+
+ if (ret == NULL)
+ return g_strdup (description);
+
+ return g_steal_pointer (&ret);
+}
+
+static void
+parse_start_tag (GMarkupParseContext *ctx,
+ const gchar *element_name,
+ const gchar **attr_names,
+ const gchar **attr_values,
+ gpointer user_data,
+ GError **error)
+{
+ KeyList *keylist = (KeyList *) user_data;
+ KeyListEntry key;
+ const char *name, *schema, *description, *package, *context, *orig_description, *reverse_entry;
+ gboolean is_reversed, hidden;
+
+ name = NULL;
+ schema = NULL;
+ package = NULL;
+ context = NULL;
+
+ /* The top-level element, names the section in the tree */
+ if (g_str_equal (element_name, "KeyListEntries"))
+ {
+ const char *wm_name = NULL;
+ const char *group = NULL;
+
+ while (*attr_names && *attr_values)
+ {
+ if (g_str_equal (*attr_names, "name"))
+ {
+ if (**attr_values)
+ name = *attr_values;
+ } else if (g_str_equal (*attr_names, "group")) {
+ if (**attr_values)
+ group = *attr_values;
+ } else if (g_str_equal (*attr_names, "wm_name")) {
+ if (**attr_values)
+ wm_name = *attr_values;
+ } else if (g_str_equal (*attr_names, "schema")) {
+ if (**attr_values)
+ schema = *attr_values;
+ } else if (g_str_equal (*attr_names, "package")) {
+ if (**attr_values)
+ package = *attr_values;
+ }
+ ++attr_names;
+ ++attr_values;
+ }
+
+ if (name)
+ {
+ if (keylist->name)
+ g_warning ("Duplicate section name");
+ g_free (keylist->name);
+ keylist->name = g_strdup (name);
+ }
+ if (wm_name)
+ {
+ if (keylist->wm_name)
+ g_warning ("Duplicate window manager name");
+ g_free (keylist->wm_name);
+ keylist->wm_name = g_strdup (wm_name);
+ }
+ if (package)
+ {
+ if (keylist->package)
+ g_warning ("Duplicate gettext package name");
+ g_free (keylist->package);
+ keylist->package = g_strdup (package);
+ bind_textdomain_codeset (keylist->package, "UTF-8");
+ }
+ if (group)
+ {
+ if (keylist->group)
+ g_warning ("Duplicate group");
+ g_free (keylist->group);
+ keylist->group = g_strdup (group);
+ }
+ if (schema)
+ {
+ if (keylist->schema)
+ g_warning ("Duplicate schema");
+ g_free (keylist->schema);
+ keylist->schema = g_strdup (schema);
+ }
+ return;
+ }
+
+ if (!g_str_equal (element_name, "KeyListEntry")
+ || attr_names == NULL
+ || attr_values == NULL)
+ return;
+
+ schema = NULL;
+ description = NULL;
+ context = NULL;
+ orig_description = NULL;
+ reverse_entry = NULL;
+ is_reversed = FALSE;
+ hidden = FALSE;
+
+ while (*attr_names && *attr_values)
+ {
+ if (g_str_equal (*attr_names, "name"))
+ {
+ /* skip if empty */
+ if (**attr_values)
+ name = *attr_values;
+ } else if (g_str_equal (*attr_names, "schema")) {
+ if (**attr_values) {
+ schema = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "description")) {
+ if (**attr_values)
+ orig_description = *attr_values;
+ } else if (g_str_equal (*attr_names, "msgctxt")) {
+ if (**attr_values)
+ context = *attr_values;
+ } else if (g_str_equal (*attr_names, "reverse-entry")) {
+ if (**attr_values)
+ reverse_entry = *attr_values;
+ } else if (g_str_equal (*attr_names, "is-reversed")) {
+ if (g_str_equal (*attr_values, "true"))
+ is_reversed = TRUE;
+ } else if (g_str_equal (*attr_names, "hidden")) {
+ if (g_str_equal (*attr_values, "true"))
+ hidden = TRUE;
+ }
+
+ ++attr_names;
+ ++attr_values;
+ }
+
+ if (name == NULL)
+ return;
+
+ if (schema == NULL &&
+ keylist->schema == NULL) {
+ g_debug ("Ignored GConf keyboard shortcut '%s'", name);
+ return;
+ }
+
+ if (context != NULL)
+ description = g_dpgettext2 (keylist->package, context, orig_description);
+ else
+ description = dgettext (keylist->package, orig_description);
+
+ key.name = g_strdup (name);
+ key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS;
+ key.description = replace_pictures_folder (description);
+ key.schema = schema ? g_strdup (schema) : g_strdup (keylist->schema);
+ key.reverse_entry = g_strdup (reverse_entry);
+ key.is_reversed = is_reversed;
+ key.hidden = hidden;
+ g_array_append_val (keylist->entries, key);
+}
+
+static const guint forbidden_keyvals[] = {
+ /* Navigation keys */
+ GDK_KEY_Home,
+ GDK_KEY_Left,
+ GDK_KEY_Up,
+ GDK_KEY_Right,
+ GDK_KEY_Down,
+ GDK_KEY_Page_Up,
+ GDK_KEY_Page_Down,
+ GDK_KEY_End,
+ GDK_KEY_Tab,
+
+ /* Return */
+ GDK_KEY_KP_Enter,
+ GDK_KEY_Return,
+
+ GDK_KEY_Mode_switch
+};
+
+static gboolean
+keyval_is_forbidden (guint keyval)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) {
+ if (keyval == forbidden_keyvals[i])
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+is_valid_binding (const CcKeyCombo *combo)
+{
+ if ((combo->mask == 0 || combo->mask == GDK_SHIFT_MASK) && combo->keycode != 0)
+ {
+ guint keyval = combo->keyval;
+
+ if ((keyval >= GDK_KEY_a && keyval <= GDK_KEY_z)
+ || (keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z)
+ || (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9)
+ || (keyval >= GDK_KEY_kana_fullstop && keyval <= GDK_KEY_semivoicedsound)
+ || (keyval >= GDK_KEY_Arabic_comma && keyval <= GDK_KEY_Arabic_sukun)
+ || (keyval >= GDK_KEY_Serbian_dje && keyval <= GDK_KEY_Cyrillic_HARDSIGN)
+ || (keyval >= GDK_KEY_Greek_ALPHAaccent && keyval <= GDK_KEY_Greek_omega)
+ || (keyval >= GDK_KEY_hebrew_doublelowline && keyval <= GDK_KEY_hebrew_taf)
+ || (keyval >= GDK_KEY_Thai_kokai && keyval <= GDK_KEY_Thai_lekkao)
+ || (keyval >= GDK_KEY_Hangul_Kiyeog && keyval <= GDK_KEY_Hangul_J_YeorinHieuh)
+ || (keyval == GDK_KEY_space && combo->mask == 0)
+ || keyval_is_forbidden (keyval)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+gboolean
+is_empty_binding (const CcKeyCombo *combo)
+{
+ if (combo->keyval == 0 &&
+ combo->mask == 0 &&
+ combo->keycode == 0)
+ return TRUE;
+ return FALSE;
+}
+
+gboolean
+is_valid_accel (const CcKeyCombo *combo)
+{
+ /* Unlike gtk_accelerator_valid(), we want to allow Tab when combined
+ * with some modifiers (Alt+Tab and friends)
+ */
+ return gtk_accelerator_valid (combo->keyval, combo->mask) ||
+ (combo->keyval == GDK_KEY_Tab && combo->mask != 0);
+}
+
+gchar*
+find_free_settings_path (GSettings *settings)
+{
+ g_auto(GStrv) used_names = NULL;
+ g_autofree gchar *dir = NULL;
+ int i, num, n_names;
+
+ used_names = g_settings_get_strv (settings, "custom-keybindings");
+ n_names = g_strv_length (used_names);
+
+ for (num = 0; dir == NULL; num++)
+ {
+ g_autofree gchar *tmp = NULL;
+ gboolean found = FALSE;
+
+ tmp = g_strdup_printf ("%s/custom%d/", CUSTOM_KEYS_BASENAME, num);
+ for (i = 0; i < n_names && !found; i++)
+ found = strcmp (used_names[i], tmp) == 0;
+
+ if (!found)
+ dir = g_steal_pointer (&tmp);
+ }
+
+ return g_steal_pointer (&dir);
+}
+
+KeyList*
+parse_keylist_from_file (const gchar *path)
+{
+ KeyList *keylist;
+ g_autoptr(GError) err = NULL;
+ g_autofree gchar *buf = NULL;
+ gsize buf_len;
+ guint i;
+
+ g_autoptr(GMarkupParseContext) ctx = NULL;
+ GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL };
+
+ /* Parse file */
+ if (!g_file_get_contents (path, &buf, &buf_len, &err))
+ return NULL;
+
+ keylist = g_new0 (KeyList, 1);
+ keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
+ ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL);
+
+ if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err))
+ {
+ g_warning ("Failed to parse '%s': '%s'", path, err->message);
+ g_free (keylist->name);
+ g_free (keylist->package);
+ g_free (keylist->wm_name);
+
+ for (i = 0; i < keylist->entries->len; i++)
+ g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name);
+
+ g_array_free (keylist->entries, TRUE);
+ g_free (keylist);
+ return NULL;
+ }
+
+ return keylist;
+}
+
+/*
+ * Stolen from GtkCellRendererAccel:
+ * https://git.gnome.org/browse/gtk+/tree/gtk/gtkcellrendereraccel.c#n261
+ */
+gchar*
+convert_keysym_state_to_string (const CcKeyCombo *combo)
+{
+ gchar *name;
+
+ if (combo->keyval == 0 && combo->keycode == 0)
+ {
+ /* This label is displayed in a treeview cell displaying
+ * a disabled accelerator key combination.
+ */
+ name = g_strdup (_("Disabled"));
+ }
+ else
+ {
+ name = gtk_accelerator_get_label_with_keycode (NULL, combo->keyval, combo->keycode, combo->mask);
+
+ if (name == NULL)
+ name = gtk_accelerator_name_with_keycode (NULL, combo->keyval, combo->keycode, combo->mask);
+ }
+
+ return name;
+}
+
+/* This adjusts the keyval and modifiers such that it matches how
+ * gnome-shell detects shortcuts, which works as follows:
+ * First for the non-modifier key, the keycode that generates this
+ * keyval at the lowest shift level is determined, which might be a
+ * level > 0, such as for numbers in the num-row in AZERTY.
+ * Next it checks if all the specified modifiers were pressed.
+ */
+void
+normalize_keyval_and_mask (guint keycode,
+ GdkModifierType mask,
+ guint group,
+ guint *out_keyval,
+ GdkModifierType *out_mask)
+{
+ guint unmodified_keyval;
+ guint shifted_keyval;
+ GdkModifierType explicit_modifiers;
+ GdkModifierType used_modifiers;
+
+ /* We want shift to always be included as explicit modifier for
+ * gnome-shell shortcuts. That's because users usually think of
+ * shortcuts as including the shift key rather than being defined
+ * for the shifted keyval.
+ * This helps with num-row keys which have different keyvals on
+ * different layouts for example, but also with keys that have
+ * explicit key codes at shift level 0, that gnome-shell would prefer
+ * over shifted ones, such the DOLLAR key.
+ */
+ explicit_modifiers = gtk_accelerator_get_default_mod_mask () | GDK_SHIFT_MASK;
+ used_modifiers = mask & explicit_modifiers;
+
+ /* Find the base keyval of the pressed key without the explicit
+ * modifiers. */
+ gdk_display_translate_key (gdk_display_get_default (),
+ keycode,
+ mask & ~explicit_modifiers,
+ group,
+ &unmodified_keyval,
+ NULL,
+ NULL,
+ NULL);
+
+ /* Normalize num-row keys to the number value. This allows these
+ * shortcuts to work when switching between AZERTY and layouts where
+ * the numbers are at shift level 0. */
+ gdk_display_translate_key (gdk_display_get_default (),
+ keycode,
+ GDK_SHIFT_MASK | (mask & ~explicit_modifiers),
+ group,
+ &shifted_keyval,
+ NULL,
+ NULL,
+ NULL);
+
+ if (shifted_keyval >= GDK_KEY_0 && shifted_keyval <= GDK_KEY_9)
+ unmodified_keyval = shifted_keyval;
+
+ /* Normalise <Tab> */
+ if (unmodified_keyval == GDK_KEY_ISO_Left_Tab)
+ unmodified_keyval = GDK_KEY_Tab;
+
+ if (unmodified_keyval == GDK_KEY_Sys_Req && (used_modifiers & GDK_ALT_MASK) != 0)
+ {
+ /* HACK: we don't want to use SysRq as a keybinding (but we do
+ * want Alt+Print), so we avoid translation from Alt+Print to SysRq */
+ unmodified_keyval = GDK_KEY_Print;
+ }
+
+ *out_keyval = unmodified_keyval;
+ *out_mask = used_modifiers;
+}