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>
25
#include "keyboard-shortcuts.h"
26
#include "cc-keyboard-item.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 WID(builder, name) (GTK_WIDGET (gtk_builder_get_object (builder, name)))
35
/* The group of keybindings (system or application) */
37
/* The gettext package to use to translate the section title */
39
/* Name of the window manager the keys would apply to */
41
/* The GSettings schema for the whole file, if any */
43
/* an array of KeyListEntry */
49
CcKeyboardItemType type;
50
char *schema; /* GSettings schema name, if any */
51
char *description; /* description for GSettings types */
52
char *gettext_package;
53
char *name; /* GSettings schema path, or GSettings key name depending on type */
58
DETAIL_DESCRIPTION_COLUMN,
59
DETAIL_KEYENTRY_COLUMN,
65
SECTION_DESCRIPTION_COLUMN,
70
static guint maybe_block_accels_id = 0;
71
static gboolean block_accels = FALSE;
72
static GSettings *settings = NULL;
73
static GtkWidget *custom_shortcut_dialog = NULL;
74
static GtkWidget *custom_shortcut_name_entry = NULL;
75
static GtkWidget *custom_shortcut_command_entry = NULL;
76
static GHashTable *kb_system_sections = NULL;
77
static GHashTable *kb_apps_sections = NULL;
78
static GHashTable *kb_user_sections = NULL;
81
free_key_array (GPtrArray *keys)
87
for (i = 0; i < keys->len; i++)
91
item = g_ptr_array_index (keys, i);
93
g_object_unref (item);
96
g_ptr_array_free (keys, TRUE);
101
get_hash_for_group (BindingGroupType group)
107
case BINDING_GROUP_SYSTEM:
108
hash = kb_system_sections;
110
case BINDING_GROUP_APPS:
111
hash = kb_apps_sections;
113
case BINDING_GROUP_USER:
114
hash = kb_user_sections;
117
g_assert_not_reached ();
123
have_key_for_group (int group, const gchar *name)
129
g_hash_table_iter_init (&iter, get_hash_for_group (group));
130
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&keys))
132
for (i = 0; i < keys->len; i++)
134
CcKeyboardItem *item = g_ptr_array_index (keys, i);
136
if (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS &&
137
g_strcmp0 (name, item->key) == 0)
150
keybinding_key_changed_foreach (GtkTreeModel *model,
153
CcKeyboardItem *item)
155
CcKeyboardItem *tmp_item;
157
gtk_tree_model_get (item->model, iter,
158
DETAIL_KEYENTRY_COLUMN, &tmp_item,
161
if (item == tmp_item)
163
gtk_tree_model_row_changed (item->model, path, iter);
170
item_changed (CcKeyboardItem *item,
174
/* update the model */
175
gtk_tree_model_foreach (item->model, (GtkTreeModelForeachFunc) keybinding_key_changed_foreach, item);
180
append_section (GtkBuilder *builder,
182
BindingGroupType group,
183
const KeyListEntry *keys_list)
185
GPtrArray *keys_array;
186
GtkTreeModel *sort_model;
187
GtkTreeModel *model, *shortcut_model;
193
hash = get_hash_for_group (group);
195
sort_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview")));
196
model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model));
198
shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview")));
200
/* Add all CcKeyboardItems for this section */
202
keys_array = g_hash_table_lookup (hash, title);
203
if (keys_array == NULL)
205
keys_array = g_ptr_array_new ();
209
for (i = 0; keys_list != NULL && keys_list[i].name != NULL; i++)
211
CcKeyboardItem *item;
214
if (have_key_for_group (group, keys_list[i].name))
217
item = cc_keyboard_item_new (keys_list[i].type);
218
switch (keys_list[i].type)
220
case CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH:
221
ret = cc_keyboard_item_load_from_gsettings_path (item, keys_list[i].name, FALSE);
223
case CC_KEYBOARD_ITEM_TYPE_GSETTINGS:
224
ret = cc_keyboard_item_load_from_gsettings (item,
225
keys_list[i].description,
230
g_assert_not_reached ();
235
/* We don't actually want to popup a dialog - just skip this one */
236
g_object_unref (item);
240
item->model = shortcut_model;
243
g_signal_connect (G_OBJECT (item), "notify",
244
G_CALLBACK (item_changed), NULL);
246
g_ptr_array_add (keys_array, item);
249
/* Add the keys to the hash table */
252
static gboolean have_sep = FALSE;
254
g_hash_table_insert (hash, g_strdup (title), keys_array);
256
/* Append the section to the left tree view */
257
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
258
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
259
SECTION_DESCRIPTION_COLUMN, title,
260
SECTION_GROUP_COLUMN, group,
262
if (!have_sep && group != BINDING_GROUP_SYSTEM)
265
/* Append the section to the left tree view */
266
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
267
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
268
SECTION_DESCRIPTION_COLUMN, NULL,
269
SECTION_GROUP_COLUMN, BINDING_GROUP_SYSTEM,
276
parse_start_tag (GMarkupParseContext *ctx,
277
const gchar *element_name,
278
const gchar **attr_names,
279
const gchar **attr_values,
283
KeyList *keylist = (KeyList *) user_data;
285
const char *name, *schema, *description, *package;
291
/* The top-level element, names the section in the tree */
292
if (g_str_equal (element_name, "KeyListEntries"))
294
const char *wm_name = NULL;
295
const char *group = NULL;
297
while (*attr_names && *attr_values)
299
if (g_str_equal (*attr_names, "name"))
303
} else if (g_str_equal (*attr_names, "group")) {
305
group = *attr_values;
306
} else if (g_str_equal (*attr_names, "wm_name")) {
308
wm_name = *attr_values;
309
} else if (g_str_equal (*attr_names, "schema")) {
311
schema = *attr_values;
312
} else if (g_str_equal (*attr_names, "package")) {
314
package = *attr_values;
323
g_warning ("Duplicate section name");
324
g_free (keylist->name);
325
keylist->name = g_strdup (name);
329
if (keylist->wm_name)
330
g_warning ("Duplicate window manager name");
331
g_free (keylist->wm_name);
332
keylist->wm_name = g_strdup (wm_name);
336
if (keylist->package)
337
g_warning ("Duplicate gettext package name");
338
g_free (keylist->package);
339
keylist->package = g_strdup (package);
340
bind_textdomain_codeset (keylist->package, "UTF-8");
345
g_warning ("Duplicate group");
346
g_free (keylist->group);
347
keylist->group = g_strdup (group);
352
g_warning ("Duplicate schema");
353
g_free (keylist->schema);
354
keylist->schema = g_strdup (schema);
359
if (!g_str_equal (element_name, "KeyListEntry")
360
|| attr_names == NULL
361
|| attr_values == NULL)
367
while (*attr_names && *attr_values)
369
if (g_str_equal (*attr_names, "name"))
374
} else if (g_str_equal (*attr_names, "schema")) {
376
schema = *attr_values;
378
} else if (g_str_equal (*attr_names, "description")) {
380
if (keylist->package)
382
description = dgettext (keylist->package, *attr_values);
386
description = _(*attr_values);
398
if (schema == NULL &&
399
keylist->schema == NULL) {
400
g_debug ("Ignored GConf keyboard shortcut '%s'", name);
404
key.name = g_strdup (name);
405
key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS;
406
key.description = g_strdup (description);
407
key.gettext_package = g_strdup (keylist->package);
408
key.schema = schema ? g_strdup (schema) : g_strdup (keylist->schema);
409
g_array_append_val (keylist->entries, key);
413
strv_contains (char **strv,
417
for (p = strv; *p; p++)
418
if (strcmp (*p, str) == 0)
425
append_sections_from_file (GtkBuilder *builder, const gchar *path, const char *datadir, gchar **wm_keybindings)
431
KeyListEntry key, *keys;
435
GMarkupParseContext *ctx;
436
GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL };
439
if (!g_file_get_contents (path, &buf, &buf_len, &err))
442
keylist = g_new0 (KeyList, 1);
443
keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
444
ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL);
446
if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err))
448
g_warning ("Failed to parse '%s': '%s'", path, err->message);
450
g_free (keylist->name);
451
g_free (keylist->package);
452
g_free (keylist->wm_name);
453
for (i = 0; i < keylist->entries->len; i++)
454
g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name);
455
g_array_free (keylist->entries, TRUE);
459
g_markup_parse_context_free (ctx);
465
/* If there's no keys to add, or the settings apply to a window manager
466
* that's not the one we're running */
467
if (keylist->entries->len == 0
468
|| (keylist->wm_name != NULL && !strv_contains (wm_keybindings, keylist->wm_name))
469
|| keylist->name == NULL)
471
g_free (keylist->name);
472
g_free (keylist->package);
473
g_free (keylist->wm_name);
474
g_array_free (keylist->entries, TRUE);
479
/* Empty KeyListEntry to end the array */
481
g_array_append_val (keylist->entries, key);
483
keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE);
484
if (keylist->package)
488
localedir = g_build_filename (datadir, "locale", NULL);
489
bindtextdomain (keylist->package, localedir);
492
title = dgettext (keylist->package, keylist->name);
494
title = _(keylist->name);
496
if (keylist->group && strcmp (keylist->group, "system") == 0)
497
group = BINDING_GROUP_SYSTEM;
499
group = BINDING_GROUP_APPS;
501
append_section (builder, title, group, keys);
503
g_free (keylist->name);
504
g_free (keylist->package);
505
g_free (keylist->wm_name);
506
g_free (keylist->schema);
507
g_free (keylist->group);
509
for (i = 0; keys[i].name != NULL; i++) {
510
KeyListEntry *entry = &keys[i];
511
g_free (entry->schema);
512
g_free (entry->description);
513
g_free (entry->gettext_package);
514
g_free (entry->name);
521
append_sections_from_gsettings (GtkBuilder *builder)
528
/* load custom shortcuts from GSettings */
529
entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
531
custom_paths = g_settings_get_strv (settings, "custom-keybindings");
532
for (i = 0; custom_paths[i]; i++)
534
key.name = g_strdup (custom_paths[i]);
535
if (!have_key_for_group (BINDING_GROUP_USER, key.name))
537
key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH;
538
g_array_append_val (entries, key);
543
g_strfreev (custom_paths);
545
if (entries->len > 0)
550
/* Empty KeyListEntry to end the array */
552
g_array_append_val (entries, key);
554
keys = (KeyListEntry *) entries->data;
555
append_section (builder, _("Custom Shortcuts"), BINDING_GROUP_USER, keys);
556
for (i = 0; i < entries->len; ++i)
558
g_free (keys[i].name);
563
append_section (builder, _("Custom Shortcuts"), BINDING_GROUP_USER, NULL);
566
g_array_free (entries, TRUE);
570
reload_sections (GtkBuilder *builder)
572
gchar **wm_keybindings;
574
GtkTreeModel *sort_model;
575
GtkTreeModel *section_model;
576
GtkTreeModel *shortcut_model;
577
const gchar * const * data_dirs;
579
GtkTreeView *section_treeview;
580
GtkTreeSelection *selection;
582
GHashTable *loaded_files;
584
section_treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview"));
585
sort_model = gtk_tree_view_get_model (section_treeview);
586
section_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model));
588
shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview")));
589
/* FIXME: get current selection and keep it after refreshing */
591
/* Clear previous models and hash tables */
592
gtk_list_store_clear (GTK_LIST_STORE (section_model));
593
gtk_list_store_clear (GTK_LIST_STORE (shortcut_model));
594
if (kb_system_sections != NULL)
595
g_hash_table_destroy (kb_system_sections);
596
kb_system_sections = g_hash_table_new_full (g_str_hash,
599
(GDestroyNotify) free_key_array);
601
if (kb_apps_sections != NULL)
602
g_hash_table_destroy (kb_apps_sections);
603
kb_apps_sections = g_hash_table_new_full (g_str_hash,
606
(GDestroyNotify) free_key_array);
608
if (kb_user_sections != NULL)
609
g_hash_table_destroy (kb_user_sections);
610
kb_user_sections = g_hash_table_new_full (g_str_hash,
613
(GDestroyNotify) free_key_array);
615
/* Load WM keybindings */
616
wm_keybindings = wm_common_get_current_keybindings ();
618
loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
620
data_dirs = g_get_system_data_dirs ();
621
for (i = 0; data_dirs[i] != NULL; i++)
626
dir_path = g_build_filename (data_dirs[i], "gnome-control-center", "keybindings", NULL);
628
dir = g_dir_open (dir_path, 0, NULL);
635
for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir))
639
if (g_str_has_suffix (name, ".xml") == FALSE)
642
if (g_hash_table_lookup (loaded_files, name) != NULL)
644
g_debug ("Not loading %s, it was already loaded from another directory", name);
648
g_hash_table_insert (loaded_files, g_strdup (name), GINT_TO_POINTER (1));
649
path = g_build_filename (dir_path, name, NULL);
650
append_sections_from_file (builder, path, data_dirs[i], wm_keybindings);
657
g_hash_table_destroy (loaded_files);
658
g_strfreev (wm_keybindings);
660
/* Load custom keybindings */
661
append_sections_from_gsettings (builder);
663
/* Select the first item */
664
gtk_tree_model_get_iter_first (sort_model, &iter);
665
selection = gtk_tree_view_get_selection (section_treeview);
666
gtk_tree_selection_select_iter (selection, &iter);
670
accel_set_func (GtkTreeViewColumn *tree_column,
671
GtkCellRenderer *cell,
676
CcKeyboardItem *item;
678
gtk_tree_model_get (model, iter,
679
DETAIL_KEYENTRY_COLUMN, &item,
686
else if (! item->editable)
690
"accel-key", item->keyval,
691
"accel-mods", item->mask,
692
"keycode", item->keycode,
693
"style", PANGO_STYLE_ITALIC,
699
"accel-key", item->keyval,
700
"accel-mods", item->mask,
701
"keycode", item->keycode,
702
"style", PANGO_STYLE_NORMAL,
707
description_set_func (GtkTreeViewColumn *tree_column,
708
GtkCellRenderer *cell,
713
CcKeyboardItem *item;
715
gtk_tree_model_get (model, iter,
716
DETAIL_KEYENTRY_COLUMN, &item,
722
"text", item->description != NULL ?
723
item->description : _("<Unknown Action>"),
727
"editable", FALSE, NULL);
731
shortcut_selection_changed (GtkTreeSelection *selection, gpointer data)
733
GtkWidget *button = data;
736
CcKeyboardItem *item;
740
if (gtk_tree_selection_get_selected (selection, &model, &iter))
742
gtk_tree_model_get (model, &iter, DETAIL_KEYENTRY_COLUMN, &item, -1);
743
if (item && item->command != NULL && item->editable)
747
gtk_widget_set_sensitive (button, can_remove);
751
section_selection_changed (GtkTreeSelection *selection, gpointer data)
755
GtkBuilder *builder = GTK_BUILDER (data);
757
if (gtk_tree_selection_get_selected (selection, &model, &iter))
760
GtkWidget *shortcut_treeview;
761
GtkTreeModel *shortcut_model;
763
BindingGroupType group;
766
gtk_tree_model_get (model, &iter,
767
SECTION_DESCRIPTION_COLUMN, &description,
768
SECTION_GROUP_COLUMN, &group, -1);
770
keys = g_hash_table_lookup (get_hash_for_group (group), description);
773
g_warning ("Can't find section %s in sections hash table!!!", description);
777
gtk_widget_set_sensitive (WID (builder, "remove-toolbutton"), FALSE);
779
/* Fill the shortcut treeview with the keys for the selected section */
780
shortcut_treeview = GTK_WIDGET (gtk_builder_get_object (builder, "shortcut_treeview"));
781
shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (shortcut_treeview));
782
gtk_list_store_clear (GTK_LIST_STORE (shortcut_model));
784
for (i = 0; i < keys->len; i++)
787
CcKeyboardItem *item = g_ptr_array_index (keys, i);
789
gtk_list_store_append (GTK_LIST_STORE (shortcut_model), &new_row);
790
gtk_list_store_set (GTK_LIST_STORE (shortcut_model), &new_row,
791
DETAIL_DESCRIPTION_COLUMN, item->description,
792
DETAIL_KEYENTRY_COLUMN, item,
800
GtkTreeView *tree_view;
802
GtkTreeViewColumn *column;
806
edit_custom_shortcut (CcKeyboardItem *item)
812
settings = g_settings_new_with_path (item->schema, item->gsettings_path);
814
g_settings_bind (settings, "name",
815
G_OBJECT (custom_shortcut_name_entry), "text",
816
G_SETTINGS_BIND_DEFAULT);
817
gtk_widget_grab_focus (custom_shortcut_name_entry);
819
g_settings_bind (settings, "command",
820
G_OBJECT (custom_shortcut_command_entry), "text",
821
G_SETTINGS_BIND_DEFAULT);
823
g_settings_delay (settings);
825
gtk_window_present (GTK_WINDOW (custom_shortcut_dialog));
826
result = gtk_dialog_run (GTK_DIALOG (custom_shortcut_dialog));
829
case GTK_RESPONSE_OK:
830
g_settings_apply (settings);
834
g_settings_revert (settings);
839
g_settings_unbind (G_OBJECT (custom_shortcut_name_entry), "text");
840
g_settings_unbind (G_OBJECT (custom_shortcut_command_entry), "text");
842
g_object_unref (settings);
844
gtk_widget_hide (custom_shortcut_dialog);
850
remove_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter)
852
CcKeyboardItem *item;
853
GPtrArray *keys_array;
854
GVariantBuilder builder;
855
char **settings_paths;
858
gtk_tree_model_get (model, iter,
859
DETAIL_KEYENTRY_COLUMN, &item,
862
/* not a custom shortcut */
863
g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
865
g_settings_delay (item->settings);
866
g_settings_reset (item->settings, "name");
867
g_settings_reset (item->settings, "command");
868
g_settings_reset (item->settings, "binding");
869
g_settings_apply (item->settings);
872
settings_paths = g_settings_get_strv (settings, "custom-keybindings");
873
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
874
for (i = 0; settings_paths[i]; i++)
875
if (strcmp (settings_paths[i], item->gsettings_path) != 0)
876
g_variant_builder_add (&builder, "s", settings_paths[i]);
877
g_settings_set_value (settings,
878
"custom-keybindings", g_variant_builder_end (&builder));
879
g_strfreev (settings_paths);
880
g_object_unref (item);
882
keys_array = g_hash_table_lookup (get_hash_for_group (BINDING_GROUP_USER), _("Custom Shortcuts"));
883
g_ptr_array_remove (keys_array, item);
885
gtk_list_store_remove (GTK_LIST_STORE (model), iter);
891
update_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter)
893
CcKeyboardItem *item;
895
gtk_tree_model_get (model, iter,
896
DETAIL_KEYENTRY_COLUMN, &item,
899
g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
901
edit_custom_shortcut (item);
902
if (item->command == NULL || item->command[0] == '\0')
904
remove_custom_shortcut (model, iter);
908
gtk_list_store_set (GTK_LIST_STORE (model), iter,
909
DETAIL_KEYENTRY_COLUMN, item, -1);
914
real_start_editing_cb (IdleData *idle_data)
916
gtk_widget_grab_focus (GTK_WIDGET (idle_data->tree_view));
917
gtk_tree_view_set_cursor (idle_data->tree_view,
921
gtk_tree_path_free (idle_data->path);
927
start_editing_cb (GtkTreeView *tree_view,
928
GdkEventButton *event,
932
GtkTreeViewColumn *column;
934
if (event->window != gtk_tree_view_get_bin_window (tree_view))
937
if (gtk_tree_view_get_path_at_pos (tree_view,
946
CcKeyboardItem *item;
948
if (gtk_tree_path_get_depth (path) == 1)
950
gtk_tree_path_free (path);
954
model = gtk_tree_view_get_model (tree_view);
955
gtk_tree_model_get_iter (model, &iter, path);
956
gtk_tree_model_get (model, &iter,
957
DETAIL_KEYENTRY_COLUMN, &item,
960
/* if only the accel can be edited on the selected row
961
* always select the accel column */
962
if (item->desc_editable &&
963
column == gtk_tree_view_get_column (tree_view, 0))
965
gtk_widget_grab_focus (GTK_WIDGET (tree_view));
966
gtk_tree_view_set_cursor (tree_view, path,
967
gtk_tree_view_get_column (tree_view, 0),
969
update_custom_shortcut (model, &iter);
973
idle_data = g_new (IdleData, 1);
974
idle_data->tree_view = tree_view;
975
idle_data->path = path;
976
idle_data->column = item->desc_editable ? column :
977
gtk_tree_view_get_column (tree_view, 1);
978
g_idle_add ((GSourceFunc) real_start_editing_cb, idle_data);
981
g_signal_stop_emission_by_name (tree_view, "button_press_event");
987
start_editing_kb_cb (GtkTreeView *treeview,
989
GtkTreeViewColumn *column,
994
CcKeyboardItem *item;
996
model = gtk_tree_view_get_model (treeview);
997
gtk_tree_model_get_iter (model, &iter, path);
998
gtk_tree_model_get (model, &iter,
999
DETAIL_KEYENTRY_COLUMN, &item,
1004
/* This is a section heading - expand or collapse */
1005
if (gtk_tree_view_row_expanded (treeview, path))
1006
gtk_tree_view_collapse_row (treeview, path);
1008
gtk_tree_view_expand_row (treeview, path, FALSE);
1012
/* if only the accel can be edited on the selected row
1013
* always select the accel column */
1014
if (item->desc_editable &&
1015
column == gtk_tree_view_get_column (treeview, 0))
1017
gtk_widget_grab_focus (GTK_WIDGET (treeview));
1018
gtk_tree_view_set_cursor (treeview, path,
1019
gtk_tree_view_get_column (treeview, 0),
1021
update_custom_shortcut (model, &iter);
1025
gtk_widget_grab_focus (GTK_WIDGET (treeview));
1026
gtk_tree_view_set_cursor (treeview,
1028
gtk_tree_view_get_column (treeview, 1),
1033
static const guint forbidden_keyvals[] = {
1034
/* Navigation keys */
1054
binding_name (guint keyval,
1056
GdkModifierType mask,
1059
if (keyval != 0 || keycode != 0)
1061
gtk_accelerator_get_label_with_keycode (NULL, keyval, keycode, mask) :
1062
gtk_accelerator_name_with_keycode (NULL, keyval, keycode, mask);
1064
return g_strdup (translate ? _("Disabled") : "");
1068
keyval_is_forbidden (guint keyval)
1072
for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) {
1073
if (keyval == forbidden_keyvals[i])
1081
CcKeyboardItem *orig_item;
1082
CcKeyboardItem *conflict_item;
1084
GdkModifierType new_mask;
1089
compare_keys_for_uniqueness (CcKeyboardItem *element,
1090
CcUniquenessData *data)
1092
CcKeyboardItem *orig_item;
1094
orig_item = data->orig_item;
1096
/* no conflict for : blanks, different modifiers, or ourselves */
1097
if (element == NULL || data->new_mask != element->mask ||
1098
cc_keyboard_item_equal (orig_item, element))
1101
if (data->new_keyval != 0) {
1102
if (data->new_keyval != element->keyval)
1104
} else if (element->keyval != 0 || data->new_keycode != element->keycode)
1107
data->conflict_item = element;
1113
cb_check_for_uniqueness (gpointer key,
1114
GPtrArray *keys_array,
1115
CcUniquenessData *data)
1119
for (i = 0; i < keys_array->len; i++)
1121
CcKeyboardItem *item;
1123
item = keys_array->pdata[i];
1124
if (compare_keys_for_uniqueness (item, data))
1131
accel_edited_callback (GtkCellRendererText *cell,
1132
const char *path_string,
1134
GdkModifierType mask,
1138
GtkTreeModel *model;
1139
GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
1141
CcUniquenessData data;
1142
CcKeyboardItem *item;
1145
block_accels = FALSE;
1147
model = gtk_tree_view_get_model (view);
1148
gtk_tree_model_get_iter (model, &iter, path);
1149
gtk_tree_path_free (path);
1150
gtk_tree_model_get (model, &iter,
1151
DETAIL_KEYENTRY_COLUMN, &item,
1158
/* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */
1159
mask &= ~GDK_LOCK_MASK;
1161
data.orig_item = item;
1162
data.new_keyval = keyval;
1163
data.new_mask = mask;
1164
data.new_keycode = keycode;
1165
data.conflict_item = NULL;
1167
if (keyval != 0 || keycode != 0) /* any number of shortcuts can be disabled */
1171
for (i = BINDING_GROUP_SYSTEM; i <= BINDING_GROUP_USER && data.conflict_item == NULL; i++)
1175
table = get_hash_for_group (i);
1176
g_hash_table_find (table, (GHRFunc) cb_check_for_uniqueness, &data);
1180
/* Check for unmodified keys */
1181
if (mask == 0 && keycode != 0)
1183
if ((keyval >= GDK_KEY_a && keyval <= GDK_KEY_z)
1184
|| (keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z)
1185
|| (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9)
1186
|| (keyval >= GDK_KEY_kana_fullstop && keyval <= GDK_KEY_semivoicedsound)
1187
|| (keyval >= GDK_KEY_Arabic_comma && keyval <= GDK_KEY_Arabic_sukun)
1188
|| (keyval >= GDK_KEY_Serbian_dje && keyval <= GDK_KEY_Cyrillic_HARDSIGN)
1189
|| (keyval >= GDK_KEY_Greek_ALPHAaccent && keyval <= GDK_KEY_Greek_omega)
1190
|| (keyval >= GDK_KEY_hebrew_doublelowline && keyval <= GDK_KEY_hebrew_taf)
1191
|| (keyval >= GDK_KEY_Thai_kokai && keyval <= GDK_KEY_Thai_lekkao)
1192
|| (keyval >= GDK_KEY_Hangul && keyval <= GDK_KEY_Hangul_Special)
1193
|| (keyval >= GDK_KEY_Hangul_Kiyeog && keyval <= GDK_KEY_Hangul_J_YeorinHieuh)
1194
|| keyval_is_forbidden (keyval)) {
1198
name = binding_name (keyval, keycode, mask, TRUE);
1201
gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
1202
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1203
GTK_MESSAGE_WARNING,
1205
_("The shortcut \"%s\" cannot be used because it will become impossible to type using this key.\n"
1206
"Please try with a key such as Control, Alt or Shift at the same time."),
1210
gtk_dialog_run (GTK_DIALOG (dialog));
1211
gtk_widget_destroy (dialog);
1213
/* set it back to its previous value. */
1214
g_object_set (G_OBJECT (cell),
1215
"accel-key", item->keyval,
1216
"keycode", item->keycode,
1217
"accel-mods", item->mask,
1223
/* flag to see if the new accelerator was in use by something */
1224
if (data.conflict_item != NULL)
1230
name = binding_name (keyval, keycode, mask, TRUE);
1233
gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
1234
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1235
GTK_MESSAGE_WARNING,
1237
_("The shortcut \"%s\" is already used for\n\"%s\""),
1238
name, data.conflict_item->description);
1241
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1242
_("If you reassign the shortcut to \"%s\", the \"%s\" shortcut "
1243
"will be disabled."),
1245
data.conflict_item->description);
1247
gtk_dialog_add_button (GTK_DIALOG (dialog),
1249
GTK_RESPONSE_ACCEPT);
1251
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
1252
GTK_RESPONSE_ACCEPT);
1254
response = gtk_dialog_run (GTK_DIALOG (dialog));
1255
gtk_widget_destroy (dialog);
1257
if (response == GTK_RESPONSE_ACCEPT)
1259
g_object_set (G_OBJECT (data.conflict_item), "binding", "", NULL);
1261
str = binding_name (keyval, keycode, mask, FALSE);
1262
g_object_set (G_OBJECT (item), "binding", str, NULL);
1268
/* set it back to its previous value. */
1269
g_object_set (G_OBJECT (cell),
1270
"accel-key", item->keyval,
1271
"keycode", item->keycode,
1272
"accel-mods", item->mask,
1279
str = binding_name (keyval, keycode, mask, FALSE);
1280
g_object_set (G_OBJECT (item), "binding", str, NULL);
1286
accel_cleared_callback (GtkCellRendererText *cell,
1287
const char *path_string,
1290
GtkTreeView *view = (GtkTreeView *) data;
1291
GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
1292
CcKeyboardItem *item;
1294
GtkTreeModel *model;
1296
block_accels = FALSE;
1298
model = gtk_tree_view_get_model (view);
1299
gtk_tree_model_get_iter (model, &iter, path);
1300
gtk_tree_path_free (path);
1301
gtk_tree_model_get (model, &iter,
1302
DETAIL_KEYENTRY_COLUMN, &item,
1310
g_object_set (G_OBJECT (item), "binding", "", NULL);
1313
/* this handler is used to keep accels from activating while the user
1314
* is assigning a new shortcut so that he won't accidentally trigger one
1317
maybe_block_accels (GtkWidget *widget,
1323
return gtk_window_propagate_key_event (GTK_WINDOW (widget), event);
1329
find_free_settings_path ()
1333
int i, num, n_names;
1335
used_names = g_settings_get_strv (settings, "custom-keybindings");
1336
n_names = g_strv_length (used_names);
1338
for (num = 0; dir == NULL; num++)
1341
gboolean found = FALSE;
1343
tmp = g_strdup_printf ("%s/custom%d/", CUSTOM_KEYS_BASENAME, num);
1344
for (i = 0; i < n_names && !found; i++)
1345
found = strcmp (used_names[i], tmp) == 0;
1357
add_custom_shortcut (GtkTreeView *tree_view,
1358
GtkTreeModel *model)
1360
CcKeyboardItem *item;
1362
gchar *settings_path;
1364
item = cc_keyboard_item_new (CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
1366
settings_path = find_free_settings_path ();
1367
cc_keyboard_item_load_from_gsettings_path (item, settings_path, TRUE);
1368
g_free (settings_path);
1370
item->model = model;
1372
if (edit_custom_shortcut (item) &&
1373
item->command && item->command[0])
1375
GPtrArray *keys_array;
1378
GVariantBuilder builder;
1379
char **settings_paths;
1382
hash = get_hash_for_group (BINDING_GROUP_USER);
1383
keys_array = g_hash_table_lookup (hash, _("Custom Shortcuts"));
1384
if (keys_array == NULL)
1386
keys_array = g_ptr_array_new ();
1387
g_hash_table_insert (hash, g_strdup (_("Custom Shortcuts")), keys_array);
1390
g_ptr_array_add (keys_array, item);
1392
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1393
gtk_list_store_set (GTK_LIST_STORE (model), &iter, DETAIL_KEYENTRY_COLUMN, item, -1);
1395
settings_paths = g_settings_get_strv (settings, "custom-keybindings");
1396
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
1397
for (i = 0; settings_paths[i]; i++)
1398
g_variant_builder_add (&builder, "s", settings_paths[i]);
1399
g_variant_builder_add (&builder, "s", item->gsettings_path);
1400
g_settings_set_value (settings, "custom-keybindings",
1401
g_variant_builder_end (&builder));
1403
/* make the new shortcut visible */
1404
path = gtk_tree_model_get_path (model, &iter);
1405
gtk_tree_view_expand_to_path (tree_view, path);
1406
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0, 0);
1407
gtk_tree_path_free (path);
1411
g_object_unref (item);
1416
add_button_clicked (GtkWidget *button,
1417
GtkBuilder *builder)
1419
GtkTreeView *treeview;
1420
GtkTreeModel *model;
1421
GtkTreeModel *section_model;
1423
gboolean found, cont;
1425
treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder,
1426
"shortcut_treeview"));
1427
model = gtk_tree_view_get_model (treeview);
1429
/* Select the Custom Shortcuts section
1430
* before adding the shortcut itself */
1431
section_model = gtk_tree_view_get_model (GTK_TREE_VIEW (WID (builder, "section_treeview")));
1432
cont = gtk_tree_model_get_iter_first (section_model, &iter);
1436
BindingGroupType group;
1438
gtk_tree_model_get (section_model, &iter,
1439
SECTION_GROUP_COLUMN, &group,
1442
if (group == BINDING_GROUP_USER)
1447
cont = gtk_tree_model_iter_next (section_model, &iter);
1451
GtkTreeSelection *selection;
1453
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (WID (builder, "section_treeview")));
1454
gtk_tree_selection_select_iter (selection, &iter);
1457
/* And add the shortcut */
1458
add_custom_shortcut (treeview, model);
1462
remove_button_clicked (GtkWidget *button,
1463
GtkBuilder *builder)
1465
GtkTreeView *treeview;
1466
GtkTreeModel *model;
1467
GtkTreeSelection *selection;
1470
treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder,
1471
"shortcut_treeview"));
1472
model = gtk_tree_view_get_model (treeview);
1474
selection = gtk_tree_view_get_selection (treeview);
1475
if (gtk_tree_selection_get_selected (selection, NULL, &iter))
1477
remove_custom_shortcut (model, &iter);
1482
keyentry_sort_func (GtkTreeModel *model,
1487
CcKeyboardItem *item_a;
1488
CcKeyboardItem *item_b;
1492
gtk_tree_model_get (model, a,
1493
DETAIL_KEYENTRY_COLUMN, &item_a,
1497
gtk_tree_model_get (model, b,
1498
DETAIL_KEYENTRY_COLUMN, &item_b,
1501
if (item_a && item_b)
1503
if ((item_a->keyval || item_a->keycode) &&
1504
(item_b->keyval || item_b->keycode))
1506
gchar *name_a, *name_b;
1508
name_a = binding_name (item_a->keyval,
1513
name_b = binding_name (item_b->keyval,
1518
retval = g_utf8_collate (name_a, name_b);
1523
else if (item_a->keyval || item_a->keycode)
1525
else if (item_b->keyval || item_b->keycode)
1541
section_sort_item (GtkTreeModel *model,
1552
gtk_tree_model_get (model, a,
1553
SECTION_DESCRIPTION_COLUMN, &a_desc,
1554
SECTION_GROUP_COLUMN, &a_group,
1556
gtk_tree_model_get (model, b,
1557
SECTION_DESCRIPTION_COLUMN, &b_desc,
1558
SECTION_GROUP_COLUMN, &b_group,
1561
if (a_group == b_group)
1563
/* separators go after the section */
1566
else if (b_desc == NULL)
1569
ret = g_utf8_collate (a_desc, b_desc);
1573
if (a_group < b_group)
1586
sections_separator_func (GtkTreeModel *model,
1591
gboolean is_separator;
1594
is_separator = FALSE;
1596
gtk_tree_model_get (model, iter, SECTION_DESCRIPTION_COLUMN, &description, -1);
1597
if (description == NULL)
1598
is_separator = TRUE;
1599
g_free (description);
1601
return is_separator;
1605
setup_dialog (CcPanel *panel, GtkBuilder *builder)
1607
GtkCellRenderer *renderer;
1608
GtkTreeViewColumn *column;
1610
GtkTreeView *treeview;
1611
GtkTreeSelection *selection;
1613
GtkListStore *model;
1614
GtkTreeModelSort *sort_model;
1615
GtkStyleContext *context;
1617
gtk_widget_set_size_request (GTK_WIDGET (panel), -1, 400);
1619
/* Setup the section treeview */
1620
treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview"));
1621
gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (treeview),
1622
sections_separator_func,
1626
renderer = gtk_cell_renderer_text_new ();
1627
column = gtk_tree_view_column_new_with_attributes (_("Section"),
1629
"text", SECTION_DESCRIPTION_COLUMN,
1631
g_object_set (renderer,
1633
"ellipsize", PANGO_ELLIPSIZE_END,
1636
gtk_tree_view_append_column (treeview, column);
1638
model = gtk_list_store_new (SECTION_N_COLUMNS, G_TYPE_STRING, G_TYPE_INT);
1639
sort_model = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (model)));
1640
gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (sort_model));
1641
g_object_unref (model);
1643
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort_model),
1644
SECTION_DESCRIPTION_COLUMN,
1649
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model),
1650
SECTION_DESCRIPTION_COLUMN,
1651
GTK_SORT_ASCENDING);
1652
g_object_unref (sort_model);
1654
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
1656
gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
1658
g_signal_connect (selection, "changed",
1659
G_CALLBACK (section_selection_changed), builder);
1660
section_selection_changed (selection, builder);
1662
/* Setup the shortcut treeview */
1663
treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder,
1664
"shortcut_treeview"));
1666
settings = g_settings_new (BINDINGS_SCHEMA);
1668
g_signal_connect (treeview, "button_press_event",
1669
G_CALLBACK (start_editing_cb), builder);
1670
g_signal_connect (treeview, "row-activated",
1671
G_CALLBACK (start_editing_kb_cb), NULL);
1673
renderer = gtk_cell_renderer_text_new ();
1674
g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1676
column = gtk_tree_view_column_new_with_attributes (_("Action"),
1678
"text", DETAIL_DESCRIPTION_COLUMN,
1680
gtk_tree_view_column_set_cell_data_func (column, renderer, description_set_func, NULL, NULL);
1681
gtk_tree_view_column_set_resizable (column, FALSE);
1682
gtk_tree_view_column_set_expand (column, TRUE);
1684
gtk_tree_view_append_column (treeview, column);
1685
gtk_tree_view_column_set_sort_column_id (column, DETAIL_DESCRIPTION_COLUMN);
1687
renderer = (GtkCellRenderer *) g_object_new (GTK_TYPE_CELL_RENDERER_ACCEL,
1688
"accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER,
1691
g_signal_connect (renderer, "accel_edited",
1692
G_CALLBACK (accel_edited_callback),
1695
g_signal_connect (renderer, "accel_cleared",
1696
G_CALLBACK (accel_cleared_callback),
1699
column = gtk_tree_view_column_new_with_attributes (_("Shortcut"), renderer, NULL);
1700
gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL);
1701
gtk_tree_view_column_set_resizable (column, FALSE);
1702
gtk_tree_view_column_set_expand (column, FALSE);
1704
gtk_tree_view_append_column (treeview, column);
1705
gtk_tree_view_column_set_sort_column_id (column, DETAIL_KEYENTRY_COLUMN);
1707
model = gtk_list_store_new (DETAIL_N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER);
1708
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1709
DETAIL_KEYENTRY_COLUMN,
1712
gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (model));
1713
g_object_unref (model);
1715
widget = GTK_WIDGET (gtk_builder_get_object (builder, "actions_swindow"));
1716
context = gtk_widget_get_style_context (widget);
1717
gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
1718
widget = GTK_WIDGET (gtk_builder_get_object (builder, "shortcut-toolbar"));
1719
context = gtk_widget_get_style_context (widget);
1720
gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
1722
/* set up the dialog */
1723
shell = cc_panel_get_shell (CC_PANEL (panel));
1724
widget = cc_shell_get_toplevel (shell);
1726
maybe_block_accels_id = g_signal_connect (widget, "key_press_event",
1727
G_CALLBACK (maybe_block_accels), NULL);
1729
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
1730
g_signal_connect (selection, "changed",
1731
G_CALLBACK (shortcut_selection_changed),
1732
WID (builder, "remove-toolbutton"));
1734
/* setup the custom shortcut dialog */
1735
custom_shortcut_dialog = WID (builder,
1736
"custom-shortcut-dialog");
1737
custom_shortcut_name_entry = WID (builder,
1738
"custom-shortcut-name-entry");
1739
custom_shortcut_command_entry = WID (builder,
1740
"custom-shortcut-command-entry");
1741
g_signal_connect (WID (builder, "add-toolbutton"),
1742
"clicked", G_CALLBACK (add_button_clicked), builder);
1743
g_signal_connect (WID (builder, "remove-toolbutton"),
1744
"clicked", G_CALLBACK (remove_button_clicked), builder);
1746
gtk_dialog_set_default_response (GTK_DIALOG (custom_shortcut_dialog),
1749
gtk_window_set_transient_for (GTK_WINDOW (custom_shortcut_dialog),
1750
GTK_WINDOW (widget));
1752
gtk_window_set_resizable (GTK_WINDOW (custom_shortcut_dialog), FALSE);
1756
on_window_manager_change (const char *wm_name, GtkBuilder *builder)
1758
reload_sections (builder);
1762
keyboard_shortcuts_init (CcPanel *panel, GtkBuilder *builder)
1764
wm_common_register_window_manager_change ((GFunc) on_window_manager_change,
1766
setup_dialog (panel, builder);
1767
reload_sections (builder);
1771
keyboard_shortcuts_dispose (CcPanel *panel)
1773
if (maybe_block_accels_id != 0)
1776
GtkWidget *toplevel;
1778
shell = cc_panel_get_shell (CC_PANEL (panel));
1779
toplevel = cc_shell_get_toplevel (shell);
1781
g_signal_handler_disconnect (toplevel, maybe_block_accels_id);
1782
maybe_block_accels_id = 0;
1784
if (kb_system_sections != NULL)
1786
g_hash_table_destroy (kb_system_sections);
1787
kb_system_sections = NULL;
1789
if (kb_apps_sections != NULL)
1791
g_hash_table_destroy (kb_apps_sections);
1792
kb_apps_sections = NULL;
1794
if (kb_user_sections != NULL)
1796
g_hash_table_destroy (kb_user_sections);
1797
kb_user_sections = NULL;