2010-12-07 16:29:22 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2010 Intel, Inc
|
2014-05-01 19:39:27 +02:00
|
|
|
* Copyright (C) 2014 Red Hat, Inc
|
2010-12-07 16:29:22 +01:00
|
|
|
*
|
|
|
|
* 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
|
2014-01-23 12:57:27 +01:00
|
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
2010-12-07 16:29:22 +01:00
|
|
|
*
|
|
|
|
* Authors: Thomas Wood <thomas.wood@intel.com>
|
|
|
|
* Rodrigo Moya <rodrigo@gnome.org>
|
2014-05-01 19:39:27 +02:00
|
|
|
* Christophe Fergeau <cfergeau@redhat.com>
|
2010-12-07 16:29:22 +01:00
|
|
|
*/
|
|
|
|
|
2011-02-24 19:52:00 +01:00
|
|
|
#include <config.h>
|
|
|
|
|
2010-12-09 10:42:33 +01:00
|
|
|
#include <glib/gi18n.h>
|
2012-07-31 18:27:17 +02:00
|
|
|
|
2010-12-07 16:29:22 +01:00
|
|
|
#include "keyboard-shortcuts.h"
|
|
|
|
|
2016-07-22 15:26:10 -03:00
|
|
|
#define CUSTOM_KEYS_BASENAME "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings"
|
2010-12-09 10:42:33 +01:00
|
|
|
|
2012-10-09 15:49:51 +02:00
|
|
|
static char *
|
|
|
|
replace_pictures_folder (const char *description)
|
|
|
|
{
|
2018-01-23 16:03:19 +13:00
|
|
|
g_autoptr(GRegex) pictures_regex = NULL;
|
2012-10-09 15:49:51 +02:00
|
|
|
const char *path;
|
2018-01-23 16:03:19 +13:00
|
|
|
g_autofree gchar *dirname = NULL;
|
|
|
|
g_autofree gchar *ret = NULL;
|
2012-10-09 15:49:51 +02:00
|
|
|
|
|
|
|
if (description == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (strstr (description, "$PICTURES") == NULL)
|
|
|
|
return g_strdup (description);
|
|
|
|
|
2016-07-22 15:26:10 -03:00
|
|
|
pictures_regex = g_regex_new ("\\$PICTURES", 0, 0, NULL);
|
2012-10-09 15:49:51 +02:00
|
|
|
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);
|
2016-07-22 15:26:10 -03:00
|
|
|
|
2012-10-09 15:49:51 +02:00
|
|
|
if (ret == NULL)
|
|
|
|
return g_strdup (description);
|
|
|
|
|
2018-01-23 16:03:19 +13:00
|
|
|
return g_steal_pointer (&ret);
|
2012-10-09 15:49:51 +02:00
|
|
|
}
|
|
|
|
|
2010-12-09 10:42:33 +01:00
|
|
|
static void
|
|
|
|
parse_start_tag (GMarkupParseContext *ctx,
|
2011-01-17 19:41:40 -05:00
|
|
|
const gchar *element_name,
|
|
|
|
const gchar **attr_names,
|
|
|
|
const gchar **attr_values,
|
|
|
|
gpointer user_data,
|
|
|
|
GError **error)
|
2010-12-09 10:42:33 +01:00
|
|
|
{
|
|
|
|
KeyList *keylist = (KeyList *) user_data;
|
2018-03-16 14:19:43 -03:00
|
|
|
KeyListEntry key;
|
2014-05-01 19:41:18 +02:00
|
|
|
const char *name, *schema, *description, *package, *context, *orig_description, *reverse_entry;
|
2014-06-28 00:21:01 +02:00
|
|
|
gboolean is_reversed, hidden;
|
2010-12-09 10:42:33 +01:00
|
|
|
|
|
|
|
name = NULL;
|
2011-02-16 17:43:27 +00:00
|
|
|
schema = NULL;
|
2011-02-17 12:25:49 +00:00
|
|
|
package = NULL;
|
2012-12-06 10:04:55 +01:00
|
|
|
context = NULL;
|
2010-12-09 10:42:33 +01:00
|
|
|
|
|
|
|
/* The top-level element, names the section in the tree */
|
|
|
|
if (g_str_equal (element_name, "KeyListEntries"))
|
|
|
|
{
|
|
|
|
const char *wm_name = NULL;
|
2011-01-18 20:59:04 -05:00
|
|
|
const char *group = NULL;
|
2010-12-09 10:42:33 +01:00
|
|
|
|
|
|
|
while (*attr_names && *attr_values)
|
|
|
|
{
|
2011-01-17 19:41:40 -05:00
|
|
|
if (g_str_equal (*attr_names, "name"))
|
|
|
|
{
|
|
|
|
if (**attr_values)
|
|
|
|
name = *attr_values;
|
2011-01-18 20:59:04 -05:00
|
|
|
} else if (g_str_equal (*attr_names, "group")) {
|
|
|
|
if (**attr_values)
|
|
|
|
group = *attr_values;
|
2011-01-17 19:41:40 -05:00
|
|
|
} else if (g_str_equal (*attr_names, "wm_name")) {
|
|
|
|
if (**attr_values)
|
|
|
|
wm_name = *attr_values;
|
2011-02-16 17:43:27 +00:00
|
|
|
} else if (g_str_equal (*attr_names, "schema")) {
|
|
|
|
if (**attr_values)
|
|
|
|
schema = *attr_values;
|
2011-01-17 19:41:40 -05:00
|
|
|
} else if (g_str_equal (*attr_names, "package")) {
|
|
|
|
if (**attr_values)
|
|
|
|
package = *attr_values;
|
|
|
|
}
|
|
|
|
++attr_names;
|
|
|
|
++attr_values;
|
|
|
|
}
|
2010-12-09 10:42:33 +01:00
|
|
|
|
|
|
|
if (name)
|
|
|
|
{
|
2011-01-17 19:41:40 -05:00
|
|
|
if (keylist->name)
|
|
|
|
g_warning ("Duplicate section name");
|
|
|
|
g_free (keylist->name);
|
|
|
|
keylist->name = g_strdup (name);
|
|
|
|
}
|
2010-12-09 10:42:33 +01:00
|
|
|
if (wm_name)
|
|
|
|
{
|
2011-01-17 19:41:40 -05:00
|
|
|
if (keylist->wm_name)
|
|
|
|
g_warning ("Duplicate window manager name");
|
|
|
|
g_free (keylist->wm_name);
|
|
|
|
keylist->wm_name = g_strdup (wm_name);
|
|
|
|
}
|
2010-12-09 10:42:33 +01:00
|
|
|
if (package)
|
|
|
|
{
|
2011-01-17 19:41:40 -05:00
|
|
|
if (keylist->package)
|
|
|
|
g_warning ("Duplicate gettext package name");
|
|
|
|
g_free (keylist->package);
|
|
|
|
keylist->package = g_strdup (package);
|
2011-02-17 12:25:49 +00:00
|
|
|
bind_textdomain_codeset (keylist->package, "UTF-8");
|
2011-01-17 19:41:40 -05:00
|
|
|
}
|
2011-01-18 20:59:04 -05:00
|
|
|
if (group)
|
|
|
|
{
|
|
|
|
if (keylist->group)
|
|
|
|
g_warning ("Duplicate group");
|
|
|
|
g_free (keylist->group);
|
|
|
|
keylist->group = g_strdup (group);
|
|
|
|
}
|
2011-02-16 17:43:27 +00:00
|
|
|
if (schema)
|
|
|
|
{
|
|
|
|
if (keylist->schema)
|
|
|
|
g_warning ("Duplicate schema");
|
|
|
|
g_free (keylist->schema);
|
|
|
|
keylist->schema = g_strdup (schema);
|
|
|
|
}
|
2010-12-09 10:42:33 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!g_str_equal (element_name, "KeyListEntry")
|
|
|
|
|| attr_names == NULL
|
|
|
|
|| attr_values == NULL)
|
|
|
|
return;
|
|
|
|
|
2011-02-16 17:43:27 +00:00
|
|
|
schema = NULL;
|
|
|
|
description = NULL;
|
2012-12-06 10:04:55 +01:00
|
|
|
context = NULL;
|
|
|
|
orig_description = NULL;
|
2014-05-01 19:41:18 +02:00
|
|
|
reverse_entry = NULL;
|
|
|
|
is_reversed = FALSE;
|
2014-06-28 00:21:01 +02:00
|
|
|
hidden = FALSE;
|
2010-12-09 10:42:33 +01:00
|
|
|
|
|
|
|
while (*attr_names && *attr_values)
|
|
|
|
{
|
|
|
|
if (g_str_equal (*attr_names, "name"))
|
|
|
|
{
|
2011-01-17 19:41:40 -05:00
|
|
|
/* skip if empty */
|
|
|
|
if (**attr_values)
|
|
|
|
name = *attr_values;
|
2011-02-16 17:43:27 +00:00
|
|
|
} else if (g_str_equal (*attr_names, "schema")) {
|
|
|
|
if (**attr_values) {
|
|
|
|
schema = *attr_values;
|
|
|
|
}
|
|
|
|
} else if (g_str_equal (*attr_names, "description")) {
|
2012-12-06 10:04:55 +01:00
|
|
|
if (**attr_values)
|
|
|
|
orig_description = *attr_values;
|
|
|
|
} else if (g_str_equal (*attr_names, "msgctxt")) {
|
|
|
|
if (**attr_values)
|
|
|
|
context = *attr_values;
|
2014-05-01 19:41:18 +02:00
|
|
|
} 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;
|
2014-06-28 00:21:01 +02:00
|
|
|
} else if (g_str_equal (*attr_names, "hidden")) {
|
|
|
|
if (g_str_equal (*attr_values, "true"))
|
|
|
|
hidden = TRUE;
|
|
|
|
}
|
2010-12-09 10:42:33 +01:00
|
|
|
|
|
|
|
++attr_names;
|
|
|
|
++attr_values;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name == NULL)
|
|
|
|
return;
|
|
|
|
|
2011-11-14 18:07:09 +00:00
|
|
|
if (schema == NULL &&
|
|
|
|
keylist->schema == NULL) {
|
|
|
|
g_debug ("Ignored GConf keyboard shortcut '%s'", name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-06 10:04:55 +01:00
|
|
|
if (context != NULL)
|
|
|
|
description = g_dpgettext2 (keylist->package, context, orig_description);
|
|
|
|
else
|
|
|
|
description = dgettext (keylist->package, orig_description);
|
|
|
|
|
2010-12-09 10:42:33 +01:00
|
|
|
key.name = g_strdup (name);
|
2011-11-14 18:07:09 +00:00
|
|
|
key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS;
|
2012-10-09 15:49:51 +02:00
|
|
|
key.description = replace_pictures_folder (description);
|
2011-06-29 23:55:53 +02:00
|
|
|
key.schema = schema ? g_strdup (schema) : g_strdup (keylist->schema);
|
2014-05-01 19:41:18 +02:00
|
|
|
key.reverse_entry = g_strdup (reverse_entry);
|
|
|
|
key.is_reversed = is_reversed;
|
2014-06-28 00:21:01 +02:00
|
|
|
key.hidden = hidden;
|
2010-12-09 10:42:33 +01:00
|
|
|
g_array_append_val (keylist->entries, key);
|
|
|
|
}
|
|
|
|
|
2016-07-22 15:26:10 -03:00
|
|
|
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
|
|
|
|
};
|
|
|
|
|
2010-12-09 10:42:33 +01:00
|
|
|
static gboolean
|
2016-07-22 15:26:10 -03:00
|
|
|
keyval_is_forbidden (guint keyval)
|
2010-12-09 10:42:33 +01:00
|
|
|
{
|
2016-07-22 15:26:10 -03:00
|
|
|
guint i;
|
|
|
|
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) {
|
|
|
|
if (keyval == forbidden_keyvals[i])
|
2010-12-09 10:42:33 +01:00
|
|
|
return TRUE;
|
2016-07-22 15:26:10 -03:00
|
|
|
}
|
2010-12-09 10:42:33 +01:00
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2016-07-22 15:26:10 -03:00
|
|
|
gboolean
|
2020-07-06 10:22:26 -07:00
|
|
|
is_valid_binding (const CcKeyCombo *combo)
|
2016-07-22 15:26:10 -03:00
|
|
|
{
|
2013-08-27 00:59:11 +02:00
|
|
|
if ((combo->mask == 0 || combo->mask == GDK_SHIFT_MASK) && combo->keycode != 0)
|
2016-07-22 15:26:10 -03:00
|
|
|
{
|
2013-08-27 00:59:11 +02:00
|
|
|
guint keyval = combo->keyval;
|
|
|
|
|
2016-07-22 15:26:10 -03:00
|
|
|
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)
|
2013-08-27 00:59:11 +02:00
|
|
|
|| (keyval == GDK_KEY_space && combo->mask == 0)
|
2016-07-22 15:26:10 -03:00
|
|
|
|| keyval_is_forbidden (keyval)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2016-09-08 14:00:07 +02:00
|
|
|
gboolean
|
2020-07-06 10:22:26 -07:00
|
|
|
is_empty_binding (const CcKeyCombo *combo)
|
2016-09-08 14:00:07 +02:00
|
|
|
{
|
2013-08-27 00:59:11 +02:00
|
|
|
if (combo->keyval == 0 &&
|
|
|
|
combo->mask == 0 &&
|
|
|
|
combo->keycode == 0)
|
2016-09-08 14:00:07 +02:00
|
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2016-09-08 14:31:34 +02:00
|
|
|
gboolean
|
2020-07-06 10:22:26 -07:00
|
|
|
is_valid_accel (const CcKeyCombo *combo)
|
2016-09-08 14:31:34 +02:00
|
|
|
{
|
|
|
|
/* Unlike gtk_accelerator_valid(), we want to allow Tab when combined
|
|
|
|
* with some modifiers (Alt+Tab and friends)
|
|
|
|
*/
|
2013-08-27 00:59:11 +02:00
|
|
|
return gtk_accelerator_valid (combo->keyval, combo->mask) ||
|
|
|
|
(combo->keyval == GDK_KEY_Tab && combo->mask != 0);
|
2016-09-08 14:31:34 +02:00
|
|
|
}
|
|
|
|
|
2016-07-22 15:26:10 -03:00
|
|
|
gchar*
|
|
|
|
find_free_settings_path (GSettings *settings)
|
|
|
|
{
|
2018-01-23 16:03:19 +13:00
|
|
|
g_auto(GStrv) used_names = NULL;
|
|
|
|
g_autofree gchar *dir = NULL;
|
2016-07-22 15:26:10 -03:00
|
|
|
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++)
|
|
|
|
{
|
2018-01-23 16:03:19 +13:00
|
|
|
g_autofree gchar *tmp = NULL;
|
2016-07-22 15:26:10 -03:00
|
|
|
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)
|
2018-01-23 16:03:19 +13:00
|
|
|
dir = g_steal_pointer (&tmp);
|
2016-07-22 15:26:10 -03:00
|
|
|
}
|
|
|
|
|
2018-01-23 16:03:19 +13:00
|
|
|
return g_steal_pointer (&dir);
|
2016-07-22 15:26:10 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
KeyList*
|
|
|
|
parse_keylist_from_file (const gchar *path)
|
2010-12-09 10:42:33 +01:00
|
|
|
{
|
2016-07-22 15:26:10 -03:00
|
|
|
KeyList *keylist;
|
2018-01-23 16:03:19 +13:00
|
|
|
g_autoptr(GError) err = NULL;
|
|
|
|
g_autofree gchar *buf = NULL;
|
2010-12-09 10:42:33 +01:00
|
|
|
gsize buf_len;
|
|
|
|
guint i;
|
2016-07-22 15:26:10 -03:00
|
|
|
|
2018-01-23 16:03:19 +13:00
|
|
|
g_autoptr(GMarkupParseContext) ctx = NULL;
|
2010-12-09 10:42:33 +01:00
|
|
|
GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL };
|
|
|
|
|
|
|
|
/* Parse file */
|
|
|
|
if (!g_file_get_contents (path, &buf, &buf_len, &err))
|
2016-07-22 15:26:10 -03:00
|
|
|
return NULL;
|
2010-12-09 10:42:33 +01:00
|
|
|
|
|
|
|
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);
|
2016-07-22 15:26:10 -03:00
|
|
|
|
2010-12-09 10:42:33 +01:00
|
|
|
for (i = 0; i < keylist->entries->len; i++)
|
2011-01-17 19:41:40 -05:00
|
|
|
g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name);
|
2016-07-22 15:26:10 -03:00
|
|
|
|
2010-12-09 10:42:33 +01:00
|
|
|
g_array_free (keylist->entries, TRUE);
|
|
|
|
g_free (keylist);
|
2018-01-23 16:03:19 +13:00
|
|
|
return NULL;
|
2010-12-09 10:42:33 +01:00
|
|
|
}
|
|
|
|
|
2016-07-22 15:26:10 -03:00
|
|
|
return keylist;
|
2010-12-07 16:29:22 +01:00
|
|
|
}
|
2016-07-22 16:06:18 -03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Stolen from GtkCellRendererAccel:
|
|
|
|
* https://git.gnome.org/browse/gtk+/tree/gtk/gtkcellrendereraccel.c#n261
|
|
|
|
*/
|
|
|
|
gchar*
|
2020-07-06 10:22:26 -07:00
|
|
|
convert_keysym_state_to_string (const CcKeyCombo *combo)
|
2016-07-22 16:06:18 -03:00
|
|
|
{
|
|
|
|
gchar *name;
|
|
|
|
|
2013-08-27 00:59:11 +02:00
|
|
|
if (combo->keyval == 0 && combo->keycode == 0)
|
2016-07-22 16:06:18 -03:00
|
|
|
{
|
|
|
|
/* This label is displayed in a treeview cell displaying
|
|
|
|
* a disabled accelerator key combination.
|
|
|
|
*/
|
|
|
|
name = g_strdup (_("Disabled"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-08-27 00:59:11 +02:00
|
|
|
name = gtk_accelerator_get_label_with_keycode (NULL, combo->keyval, combo->keycode, combo->mask);
|
2016-07-22 16:06:18 -03:00
|
|
|
|
|
|
|
if (name == NULL)
|
2013-08-27 00:59:11 +02:00
|
|
|
name = gtk_accelerator_name_with_keycode (NULL, combo->keyval, combo->keycode, combo->mask);
|
2016-07-22 16:06:18 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|