2
* Copyright (C) 2010 Intel, Inc
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software
16
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
* Authors: Thomas Wood <thomas.wood@intel.com>
19
* Rodrigo Moya <rodrigo@gnome.org>
24
#include <glib/gi18n.h>
26
#include "keyboard-shortcuts.h"
27
#include "wm-common.h"
29
#define BINDINGS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys"
30
#define CUSTOM_KEYS_BASENAME "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings"
31
#define CUSTOM_SHORTCUTS_ID "custom"
34
/* The untranslated name, combine with ->package to translate */
36
/* The group of keybindings (system or application) */
38
/* The gettext package to use to translate the section title */
40
/* Name of the window manager the keys would apply to */
42
/* The GSettings schema for the whole file, if any */
44
/* an array of KeyListEntry */
50
CcRegionKeyboardItemType type;
51
char *schema; /* GSettings schema name, if any */
52
char *description; /* description for GSettings types */
53
char *gettext_package;
54
char *name; /* GSettings schema path, or GSettings key name depending on type */
57
static GSettings *binding_settings = NULL;
58
static GHashTable *kb_system_sections = NULL;
59
static GHashTable *kb_apps_sections = NULL;
60
static GHashTable *kb_user_sections = NULL;
63
free_key_array (GPtrArray *keys)
69
for (i = 0; i < keys->len; i++)
71
CcRegionKeyboardItem *item;
73
item = g_ptr_array_index (keys, i);
75
g_object_unref (item);
78
g_ptr_array_free (keys, TRUE);
83
get_hash_for_group (BindingGroupType group)
89
case BINDING_GROUP_SYSTEM:
90
hash = kb_system_sections;
92
case BINDING_GROUP_APPS:
93
hash = kb_apps_sections;
95
case BINDING_GROUP_USER:
96
hash = kb_user_sections;
105
have_key_for_group (int group, const gchar *name)
111
g_hash_table_iter_init (&iter, get_hash_for_group (group));
112
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&keys))
114
for (i = 0; i < keys->len; i++)
116
CcRegionKeyboardItem *item = g_ptr_array_index (keys, i);
118
if (item->type == CC_REGION_KEYBOARD_ITEM_TYPE_GSETTINGS &&
119
g_strcmp0 (name, item->key) == 0)
132
append_section (const gchar *id,
133
BindingGroupType group,
134
const KeyListEntry *keys_list)
136
GPtrArray *keys_array;
141
hash = get_hash_for_group (group);
145
/* Add all CcRegionKeyboardItems for this section */
147
keys_array = g_hash_table_lookup (hash, id);
148
if (keys_array == NULL)
150
keys_array = g_ptr_array_new ();
154
for (i = 0; keys_list != NULL && keys_list[i].name != NULL; i++)
156
CcRegionKeyboardItem *item;
159
if (have_key_for_group (group, keys_list[i].name))
162
item = cc_region_keyboard_item_new (keys_list[i].type);
163
switch (keys_list[i].type)
165
case CC_REGION_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH:
166
ret = cc_region_keyboard_item_load_from_gsettings_path (item, keys_list[i].name, FALSE);
168
case CC_REGION_KEYBOARD_ITEM_TYPE_GSETTINGS:
169
ret = cc_region_keyboard_item_load_from_gsettings (item,
170
keys_list[i].description,
175
g_assert_not_reached ();
180
/* We don't actually want to popup a dialog - just skip this one */
181
g_object_unref (item);
187
g_ptr_array_add (keys_array, item);
190
/* Add the keys to the hash table */
192
g_hash_table_insert (hash, g_strdup (id), keys_array);
196
parse_start_tag (GMarkupParseContext *ctx,
197
const gchar *element_name,
198
const gchar **attr_names,
199
const gchar **attr_values,
203
KeyList *keylist = (KeyList *) user_data;
205
const char *name, *schema, *description, *package;
211
/* The top-level element, names the section in the tree */
212
if (g_str_equal (element_name, "KeyListEntries"))
214
const char *wm_name = NULL;
215
const char *group = NULL;
217
while (*attr_names && *attr_values)
219
if (g_str_equal (*attr_names, "name"))
223
} else if (g_str_equal (*attr_names, "group")) {
225
group = *attr_values;
226
} else if (g_str_equal (*attr_names, "wm_name")) {
228
wm_name = *attr_values;
229
} else if (g_str_equal (*attr_names, "schema")) {
231
schema = *attr_values;
232
} else if (g_str_equal (*attr_names, "package")) {
234
package = *attr_values;
243
g_warning ("Duplicate section name");
244
g_free (keylist->name);
245
keylist->name = g_strdup (name);
249
if (keylist->wm_name)
250
g_warning ("Duplicate window manager name");
251
g_free (keylist->wm_name);
252
keylist->wm_name = g_strdup (wm_name);
256
if (keylist->package)
257
g_warning ("Duplicate gettext package name");
258
g_free (keylist->package);
259
keylist->package = g_strdup (package);
260
bind_textdomain_codeset (keylist->package, "UTF-8");
265
g_warning ("Duplicate group");
266
g_free (keylist->group);
267
keylist->group = g_strdup (group);
272
g_warning ("Duplicate schema");
273
g_free (keylist->schema);
274
keylist->schema = g_strdup (schema);
279
if (!g_str_equal (element_name, "KeyListEntry")
280
|| attr_names == NULL
281
|| attr_values == NULL)
287
while (*attr_names && *attr_values)
289
if (g_str_equal (*attr_names, "name"))
294
} else if (g_str_equal (*attr_names, "schema")) {
296
schema = *attr_values;
298
} else if (g_str_equal (*attr_names, "description")) {
300
if (keylist->package)
302
description = dgettext (keylist->package, *attr_values);
306
description = _(*attr_values);
318
if (schema == NULL &&
319
keylist->schema == NULL) {
320
g_debug ("Ignored GConf keyboard shortcut '%s'", name);
324
key.name = g_strdup (name);
325
key.type = CC_REGION_KEYBOARD_ITEM_TYPE_GSETTINGS;
326
key.description = g_strdup (description);
327
key.gettext_package = g_strdup (keylist->package);
328
key.schema = schema ? g_strdup (schema) : g_strdup (keylist->schema);
329
g_array_append_val (keylist->entries, key);
333
strv_contains (char **strv,
337
for (p = strv; *p; p++)
338
if (strcmp (*p, str) == 0)
345
append_sections_from_file (const gchar *path, const char *datadir, gchar **wm_keybindings)
351
KeyListEntry key, *keys;
354
GMarkupParseContext *ctx;
355
GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL };
358
if (!g_file_get_contents (path, &buf, &buf_len, &err))
361
keylist = g_new0 (KeyList, 1);
362
keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
363
ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL);
365
if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err))
367
g_warning ("Failed to parse '%s': '%s'", path, err->message);
369
g_free (keylist->name);
370
g_free (keylist->package);
371
g_free (keylist->wm_name);
372
for (i = 0; i < keylist->entries->len; i++)
373
g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name);
374
g_array_free (keylist->entries, TRUE);
378
g_markup_parse_context_free (ctx);
384
/* If there's no keys to add, or the settings apply to a window manager
385
* that's not the one we're running */
386
if (keylist->entries->len == 0
387
|| (keylist->wm_name != NULL && !strv_contains (wm_keybindings, keylist->wm_name))
388
|| keylist->name == NULL)
390
g_free (keylist->name);
391
g_free (keylist->package);
392
g_free (keylist->wm_name);
393
g_array_free (keylist->entries, TRUE);
398
/* Empty KeyListEntry to end the array */
400
g_array_append_val (keylist->entries, key);
402
keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE);
403
if (keylist->package)
407
localedir = g_build_filename (datadir, "locale", NULL);
408
bindtextdomain (keylist->package, localedir);
411
if (keylist->group && strcmp (keylist->group, "system") == 0)
412
group = BINDING_GROUP_SYSTEM;
414
group = BINDING_GROUP_APPS;
416
append_section (keylist->name, group, keys);
418
g_free (keylist->name);
419
g_free (keylist->package);
420
g_free (keylist->wm_name);
421
g_free (keylist->schema);
422
g_free (keylist->group);
424
for (i = 0; keys[i].name != NULL; i++) {
425
KeyListEntry *entry = &keys[i];
426
g_free (entry->schema);
427
g_free (entry->description);
428
g_free (entry->gettext_package);
429
g_free (entry->name);
436
append_sections_from_gsettings (void)
443
/* load custom shortcuts from GSettings */
444
entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
446
custom_paths = g_settings_get_strv (binding_settings, "custom-keybindings");
447
for (i = 0; custom_paths[i]; i++)
449
key.name = g_strdup (custom_paths[i]);
450
if (!have_key_for_group (BINDING_GROUP_USER, key.name))
452
key.type = CC_REGION_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH;
453
g_array_append_val (entries, key);
458
g_strfreev (custom_paths);
460
if (entries->len > 0)
465
/* Empty KeyListEntry to end the array */
467
g_array_append_val (entries, key);
469
keys = (KeyListEntry *) entries->data;
470
append_section (CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, keys);
471
for (i = 0; i < entries->len; ++i)
473
g_free (keys[i].name);
478
append_section (CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, NULL);
481
g_array_free (entries, TRUE);
485
reload_sections (void)
487
gchar **wm_keybindings;
489
const gchar * const * data_dirs;
491
GHashTable *loaded_files;
492
const char *section_to_set;
494
/* Clear previous hash tables */
495
if (kb_system_sections != NULL)
496
g_hash_table_destroy (kb_system_sections);
497
kb_system_sections = g_hash_table_new_full (g_str_hash,
500
(GDestroyNotify) free_key_array);
502
if (kb_apps_sections != NULL)
503
g_hash_table_destroy (kb_apps_sections);
504
kb_apps_sections = g_hash_table_new_full (g_str_hash,
507
(GDestroyNotify) free_key_array);
509
if (kb_user_sections != NULL)
510
g_hash_table_destroy (kb_user_sections);
511
kb_user_sections = g_hash_table_new_full (g_str_hash,
514
(GDestroyNotify) free_key_array);
516
/* Load WM keybindings */
517
wm_keybindings = wm_common_get_current_keybindings ();
519
loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
521
data_dirs = g_get_system_data_dirs ();
522
for (i = 0; data_dirs[i] != NULL; i++)
527
dir_path = g_build_filename (data_dirs[i], "unity-control-center", "keybindings", NULL);
529
dir = g_dir_open (dir_path, 0, NULL);
536
for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir))
540
if (g_str_has_suffix (name, ".xml") == FALSE)
543
if (g_hash_table_lookup (loaded_files, name) != NULL)
545
g_debug ("Not loading %s, it was already loaded from another directory", name);
549
g_hash_table_insert (loaded_files, g_strdup (name), GINT_TO_POINTER (1));
550
path = g_build_filename (dir_path, name, NULL);
551
append_sections_from_file (path, data_dirs[i], wm_keybindings);
558
g_hash_table_destroy (loaded_files);
559
g_strfreev (wm_keybindings);
561
/* Load custom keybindings */
562
append_sections_from_gsettings ();
565
static const guint forbidden_keyvals[] = {
566
/* Navigation keys */
584
binding_name (guint keyval,
586
GdkModifierType mask,
589
if (keyval != 0 || keycode != 0)
591
gtk_accelerator_get_label_with_keycode (NULL, keyval, keycode, mask) :
592
gtk_accelerator_name_with_keycode (NULL, keyval, keycode, mask);
594
return g_strdup (translate ? _("Disabled") : "");
598
keyval_is_forbidden (guint keyval)
602
for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) {
603
if (keyval == forbidden_keyvals[i])
611
CcRegionKeyboardItem *orig_item;
612
CcRegionKeyboardItem *conflict_item;
614
GdkModifierType new_mask;
619
compare_keys_for_uniqueness (CcRegionKeyboardItem *element,
620
CcUniquenessData *data)
622
CcRegionKeyboardItem *orig_item;
624
orig_item = data->orig_item;
626
/* no conflict for : blanks, different modifiers, or ourselves */
627
if (element == NULL || data->new_mask != element->mask ||
628
cc_region_keyboard_item_equal (orig_item, element))
631
if (data->new_keyval != 0) {
632
if (data->new_keyval != element->keyval)
634
} else if (element->keyval != 0 || data->new_keycode != element->keycode)
637
data->conflict_item = element;
643
cb_check_for_uniqueness (gpointer key,
644
GPtrArray *keys_array,
645
CcUniquenessData *data)
649
for (i = 0; i < keys_array->len; i++)
651
CcRegionKeyboardItem *item;
653
item = keys_array->pdata[i];
654
if (compare_keys_for_uniqueness (item, data))
661
keyboard_shortcuts_accel_edited (CcRegionKeyboardItem *item,
664
GdkModifierType mask,
667
CcUniquenessData data;
673
/* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */
674
mask &= ~GDK_LOCK_MASK;
676
data.orig_item = item;
677
data.new_keyval = keyval;
678
data.new_mask = mask;
679
data.new_keycode = keycode;
680
data.conflict_item = NULL;
682
if (keyval != 0 || keycode != 0) /* any number of shortcuts can be disabled */
686
for (i = BINDING_GROUP_SYSTEM; i <= BINDING_GROUP_USER && data.conflict_item == NULL; i++)
690
table = get_hash_for_group (i);
693
g_hash_table_find (table, (GHRFunc) cb_check_for_uniqueness, &data);
697
/* Check for unmodified keys */
698
if ((mask == 0 || mask == GDK_SHIFT_MASK) && keycode != 0)
700
if ((keyval >= GDK_KEY_a && keyval <= GDK_KEY_z)
701
|| (keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z)
702
|| (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9)
703
|| (keyval >= GDK_KEY_kana_fullstop && keyval <= GDK_KEY_semivoicedsound)
704
|| (keyval >= GDK_KEY_Arabic_comma && keyval <= GDK_KEY_Arabic_sukun)
705
|| (keyval >= GDK_KEY_Serbian_dje && keyval <= GDK_KEY_Cyrillic_HARDSIGN)
706
|| (keyval >= GDK_KEY_Greek_ALPHAaccent && keyval <= GDK_KEY_Greek_omega)
707
|| (keyval >= GDK_KEY_hebrew_doublelowline && keyval <= GDK_KEY_hebrew_taf)
708
|| (keyval >= GDK_KEY_Thai_kokai && keyval <= GDK_KEY_Thai_lekkao)
709
|| (keyval >= GDK_KEY_Hangul && keyval <= GDK_KEY_Hangul_Special)
710
|| (keyval >= GDK_KEY_Hangul_Kiyeog && keyval <= GDK_KEY_Hangul_J_YeorinHieuh)
711
|| (keyval == GDK_KEY_Tab && mask == 0)
712
|| (keyval == GDK_KEY_space && mask == 0)
713
|| keyval_is_forbidden (keyval)) {
717
name = binding_name (keyval, keycode, mask, TRUE);
720
gtk_message_dialog_new (GTK_WINDOW (toplevel),
721
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
724
_("The shortcut \"%s\" cannot be used because it will become impossible to type using this key.\n"
725
"Please try with a key such as Control, Alt or Shift at the same time."),
729
gtk_dialog_run (GTK_DIALOG (dialog));
730
gtk_widget_destroy (dialog);
736
/* flag to see if the new accelerator was in use by something */
737
if (data.conflict_item != NULL)
743
name = binding_name (keyval, keycode, mask, TRUE);
746
gtk_message_dialog_new (GTK_WINDOW (toplevel),
747
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
750
_("The shortcut \"%s\" is already used for\n\"%s\""),
751
name, data.conflict_item->description);
754
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
755
_("If you reassign the shortcut to \"%s\", the \"%s\" shortcut "
756
"will be disabled."),
758
data.conflict_item->description);
760
gtk_dialog_add_button (GTK_DIALOG (dialog),
762
GTK_RESPONSE_ACCEPT);
764
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
765
GTK_RESPONSE_ACCEPT);
767
response = gtk_dialog_run (GTK_DIALOG (dialog));
768
gtk_widget_destroy (dialog);
770
if (response == GTK_RESPONSE_ACCEPT)
771
g_object_set (G_OBJECT (data.conflict_item), "binding", "", NULL);
780
on_window_manager_change (const char *wm_name,
787
keyboard_shortcuts_init (void)
789
wm_common_register_window_manager_change ((GFunc) on_window_manager_change, NULL);
790
binding_settings = g_settings_new (BINDINGS_SCHEMA);
795
keyboard_shortcuts_dispose (void)
797
if (kb_system_sections != NULL)
799
g_hash_table_destroy (kb_system_sections);
800
kb_system_sections = NULL;
802
if (kb_apps_sections != NULL)
804
g_hash_table_destroy (kb_apps_sections);
805
kb_apps_sections = NULL;
807
if (kb_user_sections != NULL)
809
g_hash_table_destroy (kb_user_sections);
810
kb_user_sections = NULL;
813
g_clear_object (&binding_settings);
816
static CcRegionKeyboardItem *
817
get_item_in_group (BindingGroupType group,
821
GHashTable *hash_table = get_hash_for_group (group);
825
g_hash_table_iter_init (&iter, hash_table);
826
while (g_hash_table_iter_next (&iter, NULL, &value))
828
GPtrArray *array = value;
831
for (i = 0; i < array->len; i++)
833
CcRegionKeyboardItem *item = array->pdata[i];
835
if (g_strcmp0 (item->schema, schema) == 0 &&
836
g_strcmp0 (item->key, key) == 0)
844
CcRegionKeyboardItem *
845
keyboard_shortcuts_get_item (const gchar *schema,
848
CcRegionKeyboardItem *item = get_item_in_group (BINDING_GROUP_SYSTEM, schema, key);
853
item = get_item_in_group (BINDING_GROUP_APPS, schema, key);
858
item = get_item_in_group (BINDING_GROUP_USER, schema, key);