1 /* This program was written with lots of love under the GPL by Jonathan
2 * Blandford <jrb@gnome.org>
3 */
4
5 #include <config.h>
6
7 #include <stdlib.h>
8 #include <string.h>
9 #include <gtk/gtk.h>
10 #include <gio/gio.h>
11 #include <gdk/gdkx.h>
12 #include <X11/Xatom.h>
13 #include <glib/gi18n.h>
14 #include <gdk/gdkkeysyms.h>
15
16 #include "wm-common.h"
17 #include "capplet-util.h"
18 #include "eggcellrendererkeys.h"
19 #include "activate-settings-daemon.h"
20 #include "dconf-util.h"
21
22 #define GSETTINGS_KEYBINDINGS_DIR "/org/mate/desktop/keybindings/"
23 #define CUSTOM_KEYBINDING_SCHEMA "org.mate.control-center.keybinding"
24
25 #define MAX_ELEMENTS_BEFORE_SCROLLING 10
26 #define MAX_CUSTOM_SHORTCUTS 1000
27 #define RESPONSE_ADD 0
28 #define RESPONSE_REMOVE 1
29
30 typedef struct {
31 /* The untranslated name, combine with ->package to translate */
32 char *name;
33 /* The gettext package to use to translate the section title */
34 char *package;
35 /* Name of the window manager the keys would apply to */
36 char *wm_name;
37 /* The GSettings schema for the whole file */
38 char *schema;
39 /* an array of KeyListEntry */
40 GArray *entries;
41 } KeyList;
42
43 typedef enum {
44 COMPARISON_NONE = 0,
45 COMPARISON_GT,
46 COMPARISON_LT,
47 COMPARISON_EQ
48 } Comparison;
49
50 typedef struct
51 {
52 char *gsettings_path;
53 char *schema;
54 char *name;
55 int value;
56 char *value_schema; /* gsettings schema for key/value */
57 char *value_key;
58 char *description;
59 char *description_key;
60 char *cmd_key;
61 Comparison comparison;
62 } KeyListEntry;
63
64 enum
65 {
66 DESCRIPTION_COLUMN,
67 KEYENTRY_COLUMN,
68 N_COLUMNS
69 };
70
71 typedef struct
72 {
73 GSettings *settings;
74 char *gsettings_path;
75 char *gsettings_key;
76 guint keyval;
77 guint keycode;
78 EggVirtualModifierType mask;
79 gboolean editable;
80 GtkTreeModel *model;
81 char *description;
82 char *desc_gsettings_key;
83 gboolean desc_editable;
84 char *command;
85 char *cmd_gsettings_key;
86 gboolean cmd_editable;
87 gulong gsettings_cnxn;
88 gulong gsettings_cnxn_desc;
89 gulong gsettings_cnxn_cmd;
90 } KeyEntry;
91
92 static gboolean block_accels = FALSE;
93 static GtkWidget *custom_shortcut_dialog = NULL;
94 static GtkWidget *custom_shortcut_name_entry = NULL;
95 static GtkWidget *custom_shortcut_command_entry = NULL;
96
_gtk_builder_get_widget(GtkBuilder * builder,const gchar * name)97 static GtkWidget* _gtk_builder_get_widget(GtkBuilder* builder, const gchar* name)
98 {
99 return GTK_WIDGET (gtk_builder_get_object (builder, name));
100 }
101
binding_name(guint keyval,guint keycode,EggVirtualModifierType mask,gboolean translate)102 static char* binding_name(guint keyval, guint keycode, EggVirtualModifierType mask, gboolean translate)
103 {
104 if (keyval != 0 || keycode != 0)
105 {
106 if (translate)
107 {
108 return egg_virtual_accelerator_label (keyval, keycode, mask);
109 }
110 else
111 {
112 return egg_virtual_accelerator_name (keyval, keycode, mask);
113 }
114 }
115 else
116 {
117 return g_strdup (translate ? _("Disabled") : "");
118 }
119 }
120
121 static gboolean
binding_from_string(const char * str,guint * accelerator_key,guint * keycode,EggVirtualModifierType * accelerator_mods)122 binding_from_string (const char *str,
123 guint *accelerator_key,
124 guint *keycode,
125 EggVirtualModifierType *accelerator_mods)
126 {
127 g_return_val_if_fail (accelerator_key != NULL, FALSE);
128
129 if (str == NULL || strcmp (str, "disabled") == 0)
130 {
131 *accelerator_key = 0;
132 *keycode = 0;
133 *accelerator_mods = 0;
134 return TRUE;
135 }
136
137 egg_accelerator_parse_virtual (str, accelerator_key, keycode, accelerator_mods);
138
139 if (*accelerator_key == 0)
140 return FALSE;
141 else
142 return TRUE;
143 }
144
145 static void
accel_set_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)146 accel_set_func (GtkTreeViewColumn *tree_column,
147 GtkCellRenderer *cell,
148 GtkTreeModel *model,
149 GtkTreeIter *iter,
150 gpointer data)
151 {
152 KeyEntry *key_entry;
153
154 gtk_tree_model_get (model, iter,
155 KEYENTRY_COLUMN, &key_entry,
156 -1);
157
158 if (key_entry == NULL)
159 g_object_set (cell,
160 "visible", FALSE,
161 NULL);
162 else if (! key_entry->editable)
163 g_object_set (cell,
164 "visible", TRUE,
165 "editable", FALSE,
166 "accel_key", key_entry->keyval,
167 "accel_mask", key_entry->mask,
168 "keycode", key_entry->keycode,
169 "style", PANGO_STYLE_ITALIC,
170 NULL);
171 else
172 g_object_set (cell,
173 "visible", TRUE,
174 "editable", TRUE,
175 "accel_key", key_entry->keyval,
176 "accel_mask", key_entry->mask,
177 "keycode", key_entry->keycode,
178 "style", PANGO_STYLE_NORMAL,
179 NULL);
180 }
181
182 static void
description_set_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)183 description_set_func (GtkTreeViewColumn *tree_column,
184 GtkCellRenderer *cell,
185 GtkTreeModel *model,
186 GtkTreeIter *iter,
187 gpointer data)
188 {
189 KeyEntry *key_entry;
190
191 gtk_tree_model_get (model, iter,
192 KEYENTRY_COLUMN, &key_entry,
193 -1);
194
195 if (key_entry != NULL)
196 g_object_set (cell,
197 "editable", FALSE,
198 "text", key_entry->description != NULL ?
199 key_entry->description : _("<Unknown Action>"),
200 NULL);
201 else
202 g_object_set (cell,
203 "editable", FALSE, NULL);
204 }
205
206 static gboolean
keybinding_key_changed_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)207 keybinding_key_changed_foreach (GtkTreeModel *model,
208 GtkTreePath *path,
209 GtkTreeIter *iter,
210 gpointer user_data)
211 {
212 KeyEntry *key_entry;
213 KeyEntry *tmp_key_entry;
214
215 key_entry = (KeyEntry *)user_data;
216 gtk_tree_model_get (key_entry->model, iter,
217 KEYENTRY_COLUMN, &tmp_key_entry,
218 -1);
219
220 if (key_entry == tmp_key_entry)
221 {
222 gtk_tree_model_row_changed (key_entry->model, path, iter);
223 return TRUE;
224 }
225 return FALSE;
226 }
227
228 static void
keybinding_key_changed(GSettings * settings,gchar * key,KeyEntry * key_entry)229 keybinding_key_changed (GSettings *settings,
230 gchar *key,
231 KeyEntry *key_entry)
232 {
233 gchar *key_value;
234
235 key_value = g_settings_get_string (settings, key);
236
237 binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask);
238 key_entry->editable = g_settings_is_writable (settings, key);
239
240 /* update the model */
241 gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry);
242 }
243
244 static void
keybinding_description_changed(GSettings * settings,gchar * key,KeyEntry * key_entry)245 keybinding_description_changed (GSettings *settings,
246 gchar *key,
247 KeyEntry *key_entry)
248 {
249 gchar *key_value;
250
251 key_value = g_settings_get_string (settings, key);
252
253 g_free (key_entry->description);
254 key_entry->description = key_value ? g_strdup (key_value) : NULL;
255 g_free (key_value);
256
257 key_entry->desc_editable = g_settings_is_writable (settings, key);
258
259 /* update the model */
260 gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry);
261 }
262
263 static void
keybinding_command_changed(GSettings * settings,gchar * key,KeyEntry * key_entry)264 keybinding_command_changed (GSettings *settings,
265 gchar *key,
266 KeyEntry *key_entry)
267 {
268 gchar *key_value;
269
270 key_value = g_settings_get_string (settings, key);
271
272 g_free (key_entry->command);
273 key_entry->command = key_value ? g_strdup (key_value) : NULL;
274 key_entry->cmd_editable = g_settings_is_writable (settings, key);
275 g_free (key_value);
276
277 /* update the model */
278 gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry);
279 }
280
281 static int
keyentry_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)282 keyentry_sort_func (GtkTreeModel *model,
283 GtkTreeIter *a,
284 GtkTreeIter *b,
285 gpointer user_data)
286 {
287 KeyEntry *key_entry_a;
288 KeyEntry *key_entry_b;
289 int retval;
290
291 key_entry_a = NULL;
292 gtk_tree_model_get (model, a,
293 KEYENTRY_COLUMN, &key_entry_a,
294 -1);
295
296 key_entry_b = NULL;
297 gtk_tree_model_get (model, b,
298 KEYENTRY_COLUMN, &key_entry_b,
299 -1);
300
301 if (key_entry_a && key_entry_b)
302 {
303 if ((key_entry_a->keyval || key_entry_a->keycode) &&
304 (key_entry_b->keyval || key_entry_b->keycode))
305 {
306 gchar *name_a, *name_b;
307
308 name_a = binding_name (key_entry_a->keyval,
309 key_entry_a->keycode,
310 key_entry_a->mask,
311 TRUE);
312
313 name_b = binding_name (key_entry_b->keyval,
314 key_entry_b->keycode,
315 key_entry_b->mask,
316 TRUE);
317
318 retval = g_utf8_collate (name_a, name_b);
319
320 g_free (name_a);
321 g_free (name_b);
322 }
323 else if (key_entry_a->keyval || key_entry_a->keycode)
324 retval = -1;
325 else if (key_entry_b->keyval || key_entry_b->keycode)
326 retval = 1;
327 else
328 retval = 0;
329 }
330 else if (key_entry_a)
331 retval = -1;
332 else if (key_entry_b)
333 retval = 1;
334 else
335 retval = 0;
336
337 return retval;
338 }
339
340 static void
clear_old_model(GtkBuilder * builder)341 clear_old_model (GtkBuilder *builder)
342 {
343 GtkWidget *tree_view;
344 GtkWidget *actions_swindow;
345 GtkTreeModel *model;
346
347 tree_view = _gtk_builder_get_widget (builder, "shortcut_treeview");
348 model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
349
350 if (model == NULL)
351 {
352 /* create a new model */
353 model = (GtkTreeModel *) gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER);
354
355 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
356 KEYENTRY_COLUMN,
357 keyentry_sort_func,
358 NULL, NULL);
359
360 gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model);
361
362 g_object_unref (model);
363 }
364 else
365 {
366 /* clear the existing model */
367 gboolean valid;
368 GtkTreeIter iter;
369 KeyEntry *key_entry;
370
371 for (valid = gtk_tree_model_get_iter_first (model, &iter);
372 valid;
373 valid = gtk_tree_model_iter_next (model, &iter))
374 {
375 gtk_tree_model_get (model, &iter,
376 KEYENTRY_COLUMN, &key_entry,
377 -1);
378
379 if (key_entry != NULL)
380 {
381 g_signal_handler_disconnect (key_entry->settings, key_entry->gsettings_cnxn);
382 if (key_entry->gsettings_cnxn_desc != 0)
383 g_signal_handler_disconnect (key_entry->settings, key_entry->gsettings_cnxn_desc);
384 if (key_entry->gsettings_cnxn_cmd != 0)
385 g_signal_handler_disconnect (key_entry->settings, key_entry->gsettings_cnxn_cmd);
386
387 g_object_unref (key_entry->settings);
388 if (key_entry->gsettings_path)
389 g_free (key_entry->gsettings_path);
390 g_free (key_entry->gsettings_key);
391 g_free (key_entry->description);
392 g_free (key_entry->desc_gsettings_key);
393 g_free (key_entry->command);
394 g_free (key_entry->cmd_gsettings_key);
395 g_free (key_entry);
396 }
397 }
398
399 gtk_tree_store_clear (GTK_TREE_STORE (model));
400 }
401
402 actions_swindow = _gtk_builder_get_widget (builder, "actions_swindow");
403 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (actions_swindow),
404 GTK_POLICY_NEVER, GTK_POLICY_NEVER);
405 gtk_widget_set_size_request (actions_swindow, -1, -1);
406 }
407
408 typedef struct {
409 const char *key;
410 const char *path;
411 const char *schema;
412 gboolean found;
413 } KeyMatchData;
414
key_match(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)415 static gboolean key_match(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
416 {
417 KeyMatchData* match_data = data;
418 KeyEntry* element = NULL;
419 gchar *element_schema = NULL;
420 gchar *element_path = NULL;
421
422 gtk_tree_model_get(model, iter,
423 KEYENTRY_COLUMN, &element,
424 -1);
425
426 if (element && element->settings && G_IS_SETTINGS(element->settings))
427 {
428 g_object_get (element->settings, "schema-id", &element_schema, NULL);
429 g_object_get (element->settings, "path", &element_path, NULL);
430 }
431
432 if (element && g_strcmp0(element->gsettings_key, match_data->key) == 0
433 && g_strcmp0(element_schema, match_data->schema) == 0
434 && g_strcmp0(element_path, match_data->path) == 0)
435 {
436 match_data->found = TRUE;
437 return TRUE;
438 }
439
440 return FALSE;
441 }
442
key_is_already_shown(GtkTreeModel * model,const KeyListEntry * entry)443 static gboolean key_is_already_shown(GtkTreeModel* model, const KeyListEntry* entry)
444 {
445 KeyMatchData data;
446
447 data.key = entry->name;
448 data.schema = entry->schema;
449 data.path = entry->gsettings_path;
450 data.found = FALSE;
451 gtk_tree_model_foreach(model, key_match, &data);
452
453 return data.found;
454 }
455
should_show_key(const KeyListEntry * entry)456 static gboolean should_show_key(const KeyListEntry* entry)
457 {
458 GSettings *settings;
459 int value;
460
461 if (entry->comparison == COMPARISON_NONE)
462 {
463 return TRUE;
464 }
465
466 g_return_val_if_fail(entry->value_key != NULL, FALSE);
467 g_return_val_if_fail(entry->value_schema != NULL, FALSE);
468
469 settings = g_settings_new (entry->value_schema);
470 value = g_settings_get_int (settings, entry->value_key);
471 g_object_unref (settings);
472
473 switch (entry->comparison)
474 {
475 case COMPARISON_NONE:
476 /* For compiler warnings */
477 g_assert_not_reached ();
478 return FALSE;
479 case COMPARISON_GT:
480 if (value > entry->value)
481 {
482 return TRUE;
483 }
484 break;
485 case COMPARISON_LT:
486 if (value < entry->value)
487 {
488 return TRUE;
489 }
490 break;
491 case COMPARISON_EQ:
492 if (value == entry->value)
493 {
494 return TRUE;
495 }
496 break;
497 }
498
499 return FALSE;
500 }
501
502 static gboolean
count_rows_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)503 count_rows_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
504 {
505 gint *rows = data;
506
507 (*rows)++;
508
509 return FALSE;
510 }
511
512 static void
ensure_scrollbar(GtkBuilder * builder,int i)513 ensure_scrollbar (GtkBuilder *builder, int i)
514 {
515 if (i == MAX_ELEMENTS_BEFORE_SCROLLING)
516 {
517 GtkRequisition rectangle;
518 GObject *actions_swindow = gtk_builder_get_object (builder,
519 "actions_swindow");
520 GtkWidget *treeview = _gtk_builder_get_widget (builder,
521 "shortcut_treeview");
522 gtk_widget_get_preferred_size (treeview, &rectangle, NULL);
523 gtk_widget_set_size_request (treeview, -1, rectangle.height);
524 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (actions_swindow),
525 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
526 }
527 }
528
529 static void
find_section(GtkTreeModel * model,GtkTreeIter * iter,const char * title)530 find_section (GtkTreeModel *model,
531 GtkTreeIter *iter,
532 const char *title)
533 {
534 gboolean success;
535
536 success = gtk_tree_model_get_iter_first (model, iter);
537 while (success)
538 {
539 char *description = NULL;
540
541 gtk_tree_model_get (model, iter,
542 DESCRIPTION_COLUMN, &description,
543 -1);
544
545 if (g_strcmp0 (description, title) == 0)
546 return;
547 success = gtk_tree_model_iter_next (model, iter);
548 }
549
550 gtk_tree_store_append (GTK_TREE_STORE (model), iter, NULL);
551 gtk_tree_store_set (GTK_TREE_STORE (model), iter,
552 DESCRIPTION_COLUMN, title,
553 -1);
554 }
555
556 static void
append_keys_to_tree(GtkBuilder * builder,const gchar * title,const gchar * schema,const gchar * package,const KeyListEntry * keys_list)557 append_keys_to_tree (GtkBuilder *builder,
558 const gchar *title,
559 const gchar *schema,
560 const gchar *package,
561 const KeyListEntry *keys_list)
562 {
563 GtkTreeIter parent_iter, iter;
564 GtkTreeModel *model;
565 gint i, j;
566
567 model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview")));
568
569 /* Try to find a section parent iter, if it already exists */
570 find_section (model, &iter, title);
571 parent_iter = iter;
572
573 i = 0;
574 gtk_tree_model_foreach (model, count_rows_foreach, &i);
575
576 /* If the header we just added is the MAX_ELEMENTS_BEFORE_SCROLLING th,
577 * then we need to scroll now */
578 ensure_scrollbar (builder, i - 1);
579
580 for (j = 0; keys_list[j].name != NULL; j++)
581 {
582 GSettings *settings = NULL;
583 gchar *settings_path;
584 KeyEntry *key_entry;
585 const gchar *key_string;
586 gchar *key_value;
587 gchar *description;
588 gchar *command;
589
590 if (!should_show_key (&keys_list[j]))
591 continue;
592
593 if (key_is_already_shown (model, &keys_list[j]))
594 continue;
595
596 key_string = keys_list[j].name;
597
598 if (keys_list[j].gsettings_path != NULL)
599 {
600 settings = g_settings_new_with_path (schema, keys_list[j].gsettings_path);
601 settings_path = g_strdup(keys_list[j].gsettings_path);
602 }
603 else
604 {
605 settings = g_settings_new (schema);
606 settings_path = NULL;
607 }
608
609 if (keys_list[j].description_key != NULL)
610 {
611 /* it's a custom shortcut, so description is in gsettings */
612 description = g_settings_get_string (settings, keys_list[j].description_key);
613 }
614 else
615 {
616 /* it's from keyfile, so description need to be translated */
617 description = keys_list[j].description;
618 if (package)
619 {
620 bind_textdomain_codeset (package, "UTF-8");
621 description = dgettext (package, description);
622 }
623 else
624 {
625 description = _(description);
626 }
627 }
628
629 if (description == NULL)
630 {
631 /* Only print a warning for keys that should have a schema */
632 if (keys_list[j].description_key == NULL)
633 g_warning ("No description for key '%s'", key_string);
634 }
635
636
637
638 if (keys_list[j].cmd_key != NULL)
639 {
640 command = g_settings_get_string (settings, keys_list[j].cmd_key);
641 }
642 else
643 {
644 command = NULL;
645 }
646
647 key_entry = g_new0 (KeyEntry, 1);
648 key_entry->settings = settings;
649 key_entry->gsettings_path = settings_path;
650 key_entry->gsettings_key = g_strdup (key_string);
651 key_entry->editable = g_settings_is_writable (settings, key_string);
652 key_entry->model = model;
653 key_entry->description = description;
654 key_entry->command = command;
655 if (keys_list[j].description_key != NULL)
656 {
657 key_entry->desc_gsettings_key = g_strdup (keys_list[j].description_key);
658 key_entry->desc_editable = g_settings_is_writable (settings, key_entry->desc_gsettings_key);
659 gchar *gsettings_signal = g_strconcat ("changed::", key_entry->desc_gsettings_key, NULL);
660 key_entry->gsettings_cnxn_desc = g_signal_connect (settings,
661 gsettings_signal,
662 G_CALLBACK (keybinding_description_changed),
663 key_entry);
664 g_free (gsettings_signal);
665 }
666 if (keys_list[j].cmd_key != NULL)
667 {
668 key_entry->cmd_gsettings_key = g_strdup (keys_list[j].cmd_key);
669 key_entry->cmd_editable = g_settings_is_writable (settings, key_entry->cmd_gsettings_key);
670 gchar *gsettings_signal = g_strconcat ("changed::", key_entry->cmd_gsettings_key, NULL);
671 key_entry->gsettings_cnxn_cmd = g_signal_connect (settings,
672 gsettings_signal,
673 G_CALLBACK (keybinding_command_changed),
674 key_entry);
675 g_free (gsettings_signal);
676 }
677
678 gchar *gsettings_signal = g_strconcat ("changed::", key_string, NULL);
679 key_entry->gsettings_cnxn = g_signal_connect (settings,
680 gsettings_signal,
681 G_CALLBACK (keybinding_key_changed),
682 key_entry);
683 g_free (gsettings_signal);
684
685 key_value = g_settings_get_string (settings, key_string);
686 binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask);
687 g_free (key_value);
688
689 ensure_scrollbar (builder, i);
690
691 ++i;
692 gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter);
693 /* we use the DESCRIPTION_COLUMN only for the section headers */
694 gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
695 KEYENTRY_COLUMN, key_entry,
696 -1);
697 gtk_tree_view_expand_all (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview")));
698 }
699
700 /* Don't show an empty section */
701 if (gtk_tree_model_iter_n_children (model, &parent_iter) == 0)
702 gtk_tree_store_remove (GTK_TREE_STORE (model), &parent_iter);
703
704 if (i == 0)
705 gtk_widget_hide (_gtk_builder_get_widget (builder, "shortcuts_vbox"));
706 else
707 gtk_widget_show (_gtk_builder_get_widget (builder, "shortcuts_vbox"));
708 }
709
710 static void
parse_start_tag(GMarkupParseContext * ctx,const gchar * element_name,const gchar ** attr_names,const gchar ** attr_values,gpointer user_data,GError ** error)711 parse_start_tag (GMarkupParseContext *ctx,
712 const gchar *element_name,
713 const gchar **attr_names,
714 const gchar **attr_values,
715 gpointer user_data,
716 GError **error)
717 {
718 KeyList *keylist = (KeyList *) user_data;
719 KeyListEntry key;
720 const char *name, *value_key, *description, *value_schema;
721 int value;
722 Comparison comparison;
723 const char *schema = NULL;
724
725 name = NULL;
726
727 /* The top-level element, names the section in the tree */
728 if (g_str_equal (element_name, "KeyListEntries"))
729 {
730 const char *wm_name = NULL;
731 const char *package = NULL;
732
733 while (*attr_names && *attr_values)
734 {
735 if (g_str_equal (*attr_names, "name"))
736 {
737 if (**attr_values)
738 name = *attr_values;
739 }
740 else if (g_str_equal (*attr_names, "wm_name"))
741 {
742 if (**attr_values)
743 wm_name = *attr_values;
744 }
745 else if (g_str_equal (*attr_names, "package"))
746 {
747 if (**attr_values)
748 package = *attr_values;
749 }
750 else if (g_str_equal (*attr_names, "schema"))
751 {
752 if (**attr_values)
753 schema = *attr_values;
754 }
755 ++attr_names;
756 ++attr_values;
757 }
758
759 if (name)
760 {
761 if (keylist->name)
762 g_warning ("Duplicate section name");
763 g_free (keylist->name);
764 keylist->name = g_strdup (name);
765 }
766 if (wm_name)
767 {
768 if (keylist->wm_name)
769 g_warning ("Duplicate window manager name");
770 g_free (keylist->wm_name);
771 keylist->wm_name = g_strdup (wm_name);
772 }
773 if (package)
774 {
775 if (keylist->package)
776 g_warning ("Duplicate gettext package name");
777 g_free (keylist->package);
778 keylist->package = g_strdup (package);
779 }
780 if (schema)
781 {
782 if (keylist->schema)
783 g_warning ("Duplicate schema name");
784 g_free (keylist->schema);
785 keylist->schema = g_strdup (schema);
786 }
787 return;
788 }
789
790 if (!g_str_equal (element_name, "KeyListEntry")
791 || attr_names == NULL
792 || attr_values == NULL)
793 return;
794
795 value = 0;
796 comparison = COMPARISON_NONE;
797 value_key = NULL;
798 value_schema = NULL;
799 description = NULL;
800
801 while (*attr_names && *attr_values)
802 {
803 if (g_str_equal (*attr_names, "name"))
804 {
805 /* skip if empty */
806 if (**attr_values)
807 name = *attr_values;
808 } else if (g_str_equal (*attr_names, "value")) {
809 if (**attr_values) {
810 value = (int) g_ascii_strtoull (*attr_values, NULL, 0);
811 }
812 } else if (g_str_equal (*attr_names, "key")) {
813 if (**attr_values) {
814 value_key = *attr_values;
815 }
816 } else if (g_str_equal (*attr_names, "comparison")) {
817 if (**attr_values) {
818 if (g_str_equal (*attr_values, "gt")) {
819 comparison = COMPARISON_GT;
820 } else if (g_str_equal (*attr_values, "lt")) {
821 comparison = COMPARISON_LT;
822 } else if (g_str_equal (*attr_values, "eq")) {
823 comparison = COMPARISON_EQ;
824 }
825 }
826 } else if (g_str_equal (*attr_names, "description")) {
827 if (**attr_values) {
828 description = *attr_values;
829 }
830 } else if (g_str_equal (*attr_names, "schema")) {
831 if (**attr_values) {
832 value_schema = *attr_values;
833 }
834 }
835
836 ++attr_names;
837 ++attr_values;
838 }
839
840 if (name == NULL)
841 return;
842
843 key.name = g_strdup (name);
844 key.gsettings_path = NULL;
845 key.description_key = NULL;
846 key.description = g_strdup(description);
847 key.schema = g_strdup(schema);
848 key.value = value;
849 if (value_key) {
850 key.value_key = g_strdup (value_key);
851 key.value_schema = g_strdup (value_schema);
852 }
853 else {
854 key.value_key = NULL;
855 key.value_schema = NULL;
856 }
857 key.comparison = comparison;
858 key.cmd_key = NULL;
859 g_array_append_val (keylist->entries, key);
860 }
861
862 static gboolean
strv_contains(char ** strv,char * str)863 strv_contains (char **strv,
864 char *str)
865 {
866 char **p = strv;
867 for (p = strv; *p; p++)
868 if (strcmp (*p, str) == 0)
869 return TRUE;
870
871 return FALSE;
872 }
873
874 static void
append_keys_to_tree_from_file(GtkBuilder * builder,const char * filename,char ** wm_keybindings)875 append_keys_to_tree_from_file (GtkBuilder *builder,
876 const char *filename,
877 char **wm_keybindings)
878 {
879 GMarkupParseContext *ctx;
880 GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL };
881 KeyList *keylist;
882 KeyListEntry key, *keys;
883 GError *err = NULL;
884 char *buf;
885 const char *title;
886 gsize buf_len;
887 guint i;
888
889 if (!g_file_get_contents (filename, &buf, &buf_len, &err))
890 return;
891
892 keylist = g_new0 (KeyList, 1);
893 keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
894 ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL);
895
896 if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err))
897 {
898 g_warning ("Failed to parse '%s': '%s'", filename, err->message);
899 g_error_free (err);
900 g_free (keylist->name);
901 g_free (keylist->package);
902 g_free (keylist->wm_name);
903 g_free (keylist->schema);
904 for (i = 0; i < keylist->entries->len; i++)
905 g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name);
906 g_array_free (keylist->entries, TRUE);
907 g_free (keylist);
908 keylist = NULL;
909 }
910 g_markup_parse_context_free (ctx);
911 g_free (buf);
912
913 if (keylist == NULL)
914 return;
915
916 /* If there's no keys to add, or the settings apply to a window manager
917 * that's not the one we're running */
918 if (keylist->entries->len == 0
919 || (keylist->wm_name != NULL && !strv_contains (wm_keybindings, keylist->wm_name))
920 || keylist->name == NULL)
921 {
922 g_free (keylist->name);
923 g_free (keylist->package);
924 g_free (keylist->wm_name);
925 g_free (keylist->schema);
926 g_array_free (keylist->entries, TRUE);
927 g_free (keylist);
928 return;
929 }
930
931 /* Empty KeyListEntry to end the array */
932 key.name = NULL;
933 key.description_key = NULL;
934 key.value_key = NULL;
935 key.value = 0;
936 key.comparison = COMPARISON_NONE;
937 g_array_append_val (keylist->entries, key);
938
939 keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE);
940 if (keylist->package)
941 {
942 bind_textdomain_codeset (keylist->package, "UTF-8");
943 title = dgettext (keylist->package, keylist->name);
944 } else {
945 title = _(keylist->name);
946 }
947
948 append_keys_to_tree (builder, title, keylist->schema, keylist->package, keys);
949
950 g_free (keylist->name);
951 g_free (keylist->package);
952 for (i = 0; keys[i].name != NULL; i++)
953 g_free (keys[i].name);
954 g_free (keylist);
955 }
956
957 static void
append_keys_to_tree_from_gsettings(GtkBuilder * builder,const gchar * gsettings_path)958 append_keys_to_tree_from_gsettings (GtkBuilder *builder, const gchar *gsettings_path)
959 {
960 gchar **custom_list;
961 GArray *entries;
962 KeyListEntry key;
963 gint i;
964
965 /* load custom shortcuts from GSettings */
966 entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
967
968 key.value_key = NULL;
969 key.value = 0;
970 key.comparison = COMPARISON_NONE;
971
972 custom_list = dconf_util_list_subdirs (gsettings_path, FALSE);
973
974 if (custom_list != NULL)
975 {
976 for (i = 0; custom_list[i] != NULL; i++)
977 {
978 key.gsettings_path = g_strdup_printf("%s%s", gsettings_path, custom_list[i]);
979 key.name = g_strdup("binding");
980 key.cmd_key = g_strdup("action");
981 key.description_key = g_strdup("name");
982 key.schema = NULL;
983 g_array_append_val (entries, key);
984 }
985 }
986
987 g_strfreev (custom_list);
988
989 if (entries->len > 0)
990 {
991 KeyListEntry *keys;
992 int i;
993
994 /* Empty KeyListEntry to end the array */
995 key.gsettings_path = NULL;
996 key.name = NULL;
997 key.description_key = NULL;
998 key.cmd_key = NULL;
999 g_array_append_val (entries, key);
1000
1001 keys = (KeyListEntry *) entries->data;
1002 append_keys_to_tree (builder, _("Custom Shortcuts"), CUSTOM_KEYBINDING_SCHEMA, NULL, keys);
1003 for (i = 0; i < entries->len; ++i)
1004 {
1005 g_free (keys[i].name);
1006 g_free (keys[i].description_key);
1007 g_free (keys[i].cmd_key);
1008 g_free (keys[i].gsettings_path);
1009 }
1010 }
1011
1012 g_array_free (entries, TRUE);
1013 }
1014
1015 static void
reload_key_entries(GtkBuilder * builder)1016 reload_key_entries (GtkBuilder *builder)
1017 {
1018 gchar **wm_keybindings;
1019 GList *list, *l;
1020 const gchar * const * data_dirs;
1021 GHashTable *loaded_files;
1022 guint i;
1023
1024 wm_keybindings = wm_common_get_current_keybindings();
1025
1026 clear_old_model (builder);
1027
1028 loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1029 data_dirs = g_get_system_data_dirs ();
1030 for (i = 0; data_dirs[i] != NULL; i++)
1031 {
1032 g_autofree gchar *dir_path = NULL;
1033 GDir *dir;
1034 const gchar *name;
1035
1036 dir_path = g_build_filename (data_dirs[i], "mate-control-center", "keybindings", NULL);
1037 g_debug ("Keybinding dir: %s", dir_path);
1038
1039 dir = g_dir_open (dir_path, 0, NULL);
1040 if (!dir)
1041 continue;
1042
1043 for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir))
1044 {
1045 if (g_str_has_suffix (name, ".xml") == FALSE)
1046 continue;
1047
1048 if (g_hash_table_lookup (loaded_files, name) != NULL)
1049 {
1050 g_debug ("Not loading %s, it was already loaded from another directory", name);
1051 continue;
1052 }
1053
1054 g_hash_table_insert (loaded_files, g_strdup (name), g_strdup (dir_path));
1055 }
1056
1057 g_dir_close (dir);
1058 }
1059 list = g_hash_table_get_keys (loaded_files);
1060 list = g_list_sort(list, (GCompareFunc) g_str_equal);
1061 for (l = list; l != NULL; l = l->next)
1062 {
1063 g_autofree gchar *path = NULL;
1064 path = g_build_filename (g_hash_table_lookup (loaded_files, l->data), l->data, NULL);
1065 g_debug ("Keybinding file: %s", path);
1066 append_keys_to_tree_from_file (builder, path, wm_keybindings);
1067 }
1068 g_list_free (list);
1069 g_hash_table_destroy (loaded_files);
1070
1071 /* Load custom shortcuts _after_ system-provided ones,
1072 * since some of the custom shortcuts may also be listed
1073 * in a file. Loading the custom shortcuts last makes
1074 * such keys not show up in the custom section.
1075 */
1076 append_keys_to_tree_from_gsettings (builder, GSETTINGS_KEYBINDINGS_DIR);
1077
1078 g_strfreev (wm_keybindings);
1079 }
1080
1081 static void
key_entry_controlling_key_changed(GSettings * settings,gchar * key,gpointer user_data)1082 key_entry_controlling_key_changed (GSettings *settings, gchar *key, gpointer user_data)
1083 {
1084 reload_key_entries (user_data);
1085 }
1086
cb_check_for_uniqueness(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,KeyEntry * new_key)1087 static gboolean cb_check_for_uniqueness(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, KeyEntry* new_key)
1088 {
1089 KeyEntry* element;
1090
1091 gtk_tree_model_get (new_key->model, iter,
1092 KEYENTRY_COLUMN, &element,
1093 -1);
1094
1095 /* no conflict for : blanks, different modifiers, or ourselves */
1096 if (element == NULL || new_key->mask != element->mask)
1097 {
1098 return FALSE;
1099 }
1100
1101 gchar *new_key_schema = NULL;
1102 gchar *element_schema = NULL;
1103 gchar *new_key_path = NULL;
1104 gchar *element_path = NULL;
1105
1106 if (new_key && new_key->settings)
1107 {
1108 g_object_get (new_key->settings, "schema-id", &new_key_schema, NULL);
1109 g_object_get (new_key->settings, "path", &new_key_path, NULL);
1110 }
1111
1112 if (element->settings)
1113 {
1114 g_object_get (element->settings, "schema-id", &element_schema, NULL);
1115 g_object_get (element->settings, "path", &element_path, NULL);
1116 }
1117
1118 if (!g_strcmp0 (new_key->gsettings_key, element->gsettings_key) &&
1119 !g_strcmp0 (new_key_schema, element_schema) &&
1120 !g_strcmp0 (new_key_path, element_path))
1121 {
1122 return FALSE;
1123 }
1124
1125 if (new_key->keyval != 0)
1126 {
1127 if (new_key->keyval != element->keyval)
1128 {
1129 return FALSE;
1130 }
1131 }
1132 else if (element->keyval != 0 || new_key->keycode != element->keycode)
1133 {
1134 return FALSE;
1135 }
1136
1137 new_key->editable = FALSE;
1138 new_key->settings = element->settings;
1139 new_key->gsettings_key = element->gsettings_key;
1140 new_key->description = element->description;
1141 new_key->desc_gsettings_key = element->desc_gsettings_key;
1142 new_key->desc_editable = element->desc_editable;
1143
1144 return TRUE;
1145 }
1146
1147 static const guint forbidden_keyvals[] = {
1148 /* Navigation keys */
1149 GDK_KEY_Home,
1150 GDK_KEY_Left,
1151 GDK_KEY_Up,
1152 GDK_KEY_Right,
1153 GDK_KEY_Down,
1154 GDK_KEY_Page_Up,
1155 GDK_KEY_Page_Down,
1156 GDK_KEY_End,
1157 GDK_KEY_Tab,
1158
1159 /* Return */
1160 GDK_KEY_KP_Enter,
1161 GDK_KEY_Return,
1162
1163 GDK_KEY_space,
1164 GDK_KEY_Mode_switch
1165 };
1166
keyval_is_forbidden(guint keyval)1167 static gboolean keyval_is_forbidden(guint keyval)
1168 {
1169 guint i;
1170
1171 for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++)
1172 {
1173 if (keyval == forbidden_keyvals[i])
1174 {
1175 return TRUE;
1176 }
1177 }
1178
1179 return FALSE;
1180 }
1181
show_error(GtkWindow * parent,GError * err)1182 static void show_error(GtkWindow* parent, GError* err)
1183 {
1184 GtkWidget *dialog;
1185
1186 dialog = gtk_message_dialog_new (parent,
1187 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1188 GTK_MESSAGE_WARNING,
1189 GTK_BUTTONS_OK,
1190 _("Error saving the new shortcut"));
1191
1192 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1193 "%s", err->message);
1194 gtk_dialog_run (GTK_DIALOG (dialog));
1195 gtk_widget_destroy (dialog);
1196 }
1197
accel_edited_callback(GtkCellRendererText * cell,const char * path_string,guint keyval,EggVirtualModifierType mask,guint keycode,gpointer data)1198 static void accel_edited_callback(GtkCellRendererText* cell, const char* path_string, guint keyval, EggVirtualModifierType mask, guint keycode, gpointer data)
1199 {
1200 GtkTreeView* view = (GtkTreeView*) data;
1201 GtkTreeModel* model;
1202 GtkTreePath* path = gtk_tree_path_new_from_string (path_string);
1203 GtkTreeIter iter;
1204 KeyEntry* key_entry, tmp_key;
1205 char* str;
1206
1207 block_accels = FALSE;
1208
1209 model = gtk_tree_view_get_model (view);
1210 gtk_tree_model_get_iter (model, &iter, path);
1211 gtk_tree_path_free (path);
1212 gtk_tree_model_get (model, &iter,
1213 KEYENTRY_COLUMN, &key_entry,
1214 -1);
1215
1216 /* sanity check */
1217 if (key_entry == NULL)
1218 {
1219 return;
1220 }
1221
1222 /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */
1223 mask &= ~EGG_VIRTUAL_LOCK_MASK;
1224
1225 tmp_key.model = model;
1226 tmp_key.keyval = keyval;
1227 tmp_key.keycode = keycode;
1228 tmp_key.mask = mask;
1229 tmp_key.settings = key_entry->settings;
1230 tmp_key.gsettings_key = key_entry->gsettings_key;
1231 tmp_key.description = NULL;
1232 tmp_key.editable = TRUE; /* kludge to stuff in a return flag */
1233
1234 if (keyval != 0 || keycode != 0) /* any number of keys can be disabled */
1235 {
1236 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc) cb_check_for_uniqueness, &tmp_key);
1237 }
1238
1239 /* Check for unmodified keys */
1240 if (tmp_key.mask == 0 && tmp_key.keycode != 0)
1241 {
1242 if ((tmp_key.keyval >= GDK_KEY_a && tmp_key.keyval <= GDK_KEY_z)
1243 || (tmp_key.keyval >= GDK_KEY_A && tmp_key.keyval <= GDK_KEY_Z)
1244 || (tmp_key.keyval >= GDK_KEY_0 && tmp_key.keyval <= GDK_KEY_9)
1245 || (tmp_key.keyval >= GDK_KEY_kana_fullstop && tmp_key.keyval <= GDK_KEY_semivoicedsound)
1246 || (tmp_key.keyval >= GDK_KEY_Arabic_comma && tmp_key.keyval <= GDK_KEY_Arabic_sukun)
1247 || (tmp_key.keyval >= GDK_KEY_Serbian_dje && tmp_key.keyval <= GDK_KEY_Cyrillic_HARDSIGN)
1248 || (tmp_key.keyval >= GDK_KEY_Greek_ALPHAaccent && tmp_key.keyval <= GDK_KEY_Greek_omega)
1249 || (tmp_key.keyval >= GDK_KEY_hebrew_doublelowline && tmp_key.keyval <= GDK_KEY_hebrew_taf)
1250 || (tmp_key.keyval >= GDK_KEY_Thai_kokai && tmp_key.keyval <= GDK_KEY_Thai_lekkao)
1251 || (tmp_key.keyval >= GDK_KEY_Hangul && tmp_key.keyval <= GDK_KEY_Hangul_Special)
1252 || (tmp_key.keyval >= GDK_KEY_Hangul_Kiyeog && tmp_key.keyval <= GDK_KEY_Hangul_J_YeorinHieuh)
1253 || keyval_is_forbidden (tmp_key.keyval))
1254 {
1255
1256 GtkWidget *dialog;
1257 char *name;
1258
1259 name = binding_name (keyval, keycode, mask, TRUE);
1260
1261 dialog = gtk_message_dialog_new (
1262 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
1263 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1264 GTK_MESSAGE_WARNING,
1265 GTK_BUTTONS_CANCEL,
1266 _("The shortcut \"%s\" cannot be used because it will become impossible to type using this key.\n"
1267 "Please try with a key such as Control, Alt or Shift at the same time."),
1268 name);
1269
1270 g_free (name);
1271 gtk_dialog_run (GTK_DIALOG (dialog));
1272 gtk_widget_destroy (dialog);
1273
1274 /* set it back to its previous value. */
1275 egg_cell_renderer_keys_set_accelerator(
1276 EGG_CELL_RENDERER_KEYS(cell),
1277 key_entry->keyval,
1278 key_entry->keycode,
1279 key_entry->mask);
1280 return;
1281 }
1282 }
1283
1284 /* flag to see if the new accelerator was in use by something */
1285 if (!tmp_key.editable)
1286 {
1287 GtkWidget* dialog;
1288 char* name;
1289 int response;
1290
1291 name = binding_name(keyval, keycode, mask, TRUE);
1292
1293 dialog = gtk_message_dialog_new(
1294 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))),
1295 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
1296 GTK_MESSAGE_WARNING,
1297 GTK_BUTTONS_CANCEL,
1298 _("The shortcut \"%s\" is already used for\n\"%s\""),
1299 name,
1300 tmp_key.description ? tmp_key.description : tmp_key.gsettings_key);
1301 g_free (name);
1302
1303 gtk_message_dialog_format_secondary_text (
1304 GTK_MESSAGE_DIALOG (dialog),
1305 _("If you reassign the shortcut to \"%s\", the \"%s\" shortcut "
1306 "will be disabled."),
1307 key_entry->description ? key_entry->description : key_entry->gsettings_key,
1308 tmp_key.description ? tmp_key.description : tmp_key.gsettings_key);
1309
1310 gtk_dialog_add_button(GTK_DIALOG (dialog), _("_Reassign"), GTK_RESPONSE_ACCEPT);
1311
1312 gtk_dialog_set_default_response(GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1313
1314 response = gtk_dialog_run (GTK_DIALOG (dialog));
1315 gtk_widget_destroy (dialog);
1316
1317 if (response == GTK_RESPONSE_ACCEPT)
1318 {
1319 g_settings_set_string (tmp_key.settings, tmp_key.gsettings_key, "disabled");
1320
1321 str = binding_name (keyval, keycode, mask, FALSE);
1322 g_settings_set_string (key_entry->settings, key_entry->gsettings_key, str);
1323
1324 g_free (str);
1325 }
1326 else
1327 {
1328 /* set it back to its previous value. */
1329 egg_cell_renderer_keys_set_accelerator(
1330 EGG_CELL_RENDERER_KEYS(cell),
1331 key_entry->keyval,
1332 key_entry->keycode,
1333 key_entry->mask);
1334 }
1335
1336 return;
1337 }
1338
1339 str = binding_name (keyval, keycode, mask, FALSE);
1340
1341 g_settings_set_string(
1342 key_entry->settings,
1343 key_entry->gsettings_key,
1344 str);
1345
1346 g_free (str);
1347
1348 }
1349
1350 static void
accel_cleared_callback(GtkCellRendererText * cell,const char * path_string,gpointer data)1351 accel_cleared_callback (GtkCellRendererText *cell,
1352 const char *path_string,
1353 gpointer data)
1354 {
1355 GtkTreeView *view = (GtkTreeView *) data;
1356 GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
1357 KeyEntry *key_entry;
1358 GtkTreeIter iter;
1359 GtkTreeModel *model;
1360
1361 block_accels = FALSE;
1362
1363 model = gtk_tree_view_get_model (view);
1364 gtk_tree_model_get_iter (model, &iter, path);
1365 gtk_tree_path_free (path);
1366 gtk_tree_model_get (model, &iter,
1367 KEYENTRY_COLUMN, &key_entry,
1368 -1);
1369
1370 /* sanity check */
1371 if (key_entry == NULL)
1372 return;
1373
1374 /* Unset the key */
1375 g_settings_set_string (key_entry->settings,
1376 key_entry->gsettings_key,
1377 "disabled");
1378 }
1379
1380 static void
description_edited_callback(GtkCellRendererText * renderer,gchar * path_string,gchar * new_text,gpointer user_data)1381 description_edited_callback (GtkCellRendererText *renderer,
1382 gchar *path_string,
1383 gchar *new_text,
1384 gpointer user_data)
1385 {
1386 GtkTreeView *view = GTK_TREE_VIEW (user_data);
1387 GtkTreeModel *model;
1388 GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
1389 GtkTreeIter iter;
1390 KeyEntry *key_entry;
1391
1392 model = gtk_tree_view_get_model (view);
1393 gtk_tree_model_get_iter (model, &iter, path);
1394 gtk_tree_path_free (path);
1395
1396 gtk_tree_model_get (model, &iter,
1397 KEYENTRY_COLUMN, &key_entry,
1398 -1);
1399
1400 /* sanity check */
1401 if (key_entry == NULL || key_entry->desc_gsettings_key == NULL)
1402 return;
1403
1404 if (!g_settings_set_string (key_entry->settings, key_entry->desc_gsettings_key, new_text))
1405 key_entry->desc_editable = FALSE;
1406 }
1407
1408
1409 typedef struct
1410 {
1411 GtkTreeView *tree_view;
1412 GtkTreePath *path;
1413 GtkTreeViewColumn *column;
1414 } IdleData;
1415
1416 static gboolean
real_start_editing_cb(IdleData * idle_data)1417 real_start_editing_cb (IdleData *idle_data)
1418 {
1419 gtk_widget_grab_focus (GTK_WIDGET (idle_data->tree_view));
1420 gtk_tree_view_set_cursor (idle_data->tree_view,
1421 idle_data->path,
1422 idle_data->column,
1423 TRUE);
1424 gtk_tree_path_free (idle_data->path);
1425 g_free (idle_data);
1426 return FALSE;
1427 }
1428
1429 static gboolean
edit_custom_shortcut(KeyEntry * key)1430 edit_custom_shortcut (KeyEntry *key)
1431 {
1432 gint result;
1433 const gchar *text;
1434 gboolean ret;
1435
1436 gtk_entry_set_text (GTK_ENTRY (custom_shortcut_name_entry), key->description ? key->description : "");
1437 gtk_widget_set_sensitive (custom_shortcut_name_entry, key->desc_editable);
1438 gtk_widget_grab_focus (custom_shortcut_name_entry);
1439 gtk_entry_set_text (GTK_ENTRY (custom_shortcut_command_entry), key->command ? key->command : "");
1440 gtk_widget_set_sensitive (custom_shortcut_command_entry, key->cmd_editable);
1441
1442 gtk_window_present (GTK_WINDOW (custom_shortcut_dialog));
1443 result = gtk_dialog_run (GTK_DIALOG (custom_shortcut_dialog));
1444 switch (result)
1445 {
1446 case GTK_RESPONSE_OK:
1447 text = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_name_entry));
1448 g_free (key->description);
1449 key->description = g_strdup (text);
1450 text = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_command_entry));
1451 g_free (key->command);
1452 key->command = g_strdup (text);
1453 ret = TRUE;
1454 break;
1455 default:
1456 ret = FALSE;
1457 break;
1458 }
1459
1460 gtk_widget_hide (custom_shortcut_dialog);
1461
1462 return ret;
1463 }
1464
1465 static gboolean
remove_custom_shortcut(GtkTreeModel * model,GtkTreeIter * iter)1466 remove_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter)
1467 {
1468 GtkTreeIter parent;
1469 KeyEntry *key;
1470
1471 gtk_tree_model_get (model, iter,
1472 KEYENTRY_COLUMN, &key,
1473 -1);
1474
1475 /* not a custom shortcut */
1476 if (key->command == NULL)
1477 return FALSE;
1478
1479 g_signal_handler_disconnect (key->settings, key->gsettings_cnxn);
1480 if (key->gsettings_cnxn_desc != 0)
1481 g_signal_handler_disconnect (key->settings, key->gsettings_cnxn_desc);
1482 if (key->gsettings_cnxn_cmd != 0)
1483 g_signal_handler_disconnect (key->settings, key->gsettings_cnxn_cmd);
1484
1485 dconf_util_recursive_reset (key->gsettings_path, NULL);
1486 g_object_unref (key->settings);
1487
1488 g_free (key->gsettings_path);
1489 g_free (key->gsettings_key);
1490 g_free (key->description);
1491 g_free (key->desc_gsettings_key);
1492 g_free (key->command);
1493 g_free (key->cmd_gsettings_key);
1494 g_free (key);
1495
1496 gtk_tree_model_iter_parent (model, &parent, iter);
1497 gtk_tree_store_remove (GTK_TREE_STORE (model), iter);
1498 if (!gtk_tree_model_iter_has_child (model, &parent))
1499 gtk_tree_store_remove (GTK_TREE_STORE (model), &parent);
1500
1501 return TRUE;
1502 }
1503
1504 static void
update_custom_shortcut(GtkTreeModel * model,GtkTreeIter * iter)1505 update_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter)
1506 {
1507 KeyEntry *key;
1508
1509 gtk_tree_model_get (model, iter,
1510 KEYENTRY_COLUMN, &key,
1511 -1);
1512
1513 edit_custom_shortcut (key);
1514 if (key->command == NULL || key->command[0] == '\0')
1515 {
1516 remove_custom_shortcut (model, iter);
1517 }
1518 else
1519 {
1520 gtk_tree_store_set (GTK_TREE_STORE (model), iter,
1521 KEYENTRY_COLUMN, key, -1);
1522 if (key->description != NULL)
1523 g_settings_set_string (key->settings, key->desc_gsettings_key, key->description);
1524 else
1525 g_settings_reset (key->settings, key->desc_gsettings_key);
1526 g_settings_set_string (key->settings, key->cmd_gsettings_key, key->command);
1527 }
1528 }
1529
1530 static gchar *
find_free_gsettings_path(GError ** error)1531 find_free_gsettings_path (GError **error)
1532 {
1533 gchar **existing_dirs;
1534 gchar *dir = NULL;
1535 gchar *fulldir = NULL;
1536 int i;
1537 int j;
1538 gboolean found;
1539
1540 existing_dirs = dconf_util_list_subdirs (GSETTINGS_KEYBINDINGS_DIR, FALSE);
1541
1542 for (i = 0; i < MAX_CUSTOM_SHORTCUTS; i++)
1543 {
1544 found = TRUE;
1545 dir = g_strdup_printf ("custom%d/", i);
1546 for (j = 0; existing_dirs[j] != NULL; j++)
1547 if (!g_strcmp0(dir, existing_dirs[j]))
1548 {
1549 found = FALSE;
1550 g_free (dir);
1551 break;
1552 }
1553 if (found)
1554 break;
1555 }
1556 g_strfreev (existing_dirs);
1557
1558 if (i == MAX_CUSTOM_SHORTCUTS)
1559 {
1560 g_free (dir);
1561 dir = NULL;
1562 g_set_error_literal (error,
1563 g_quark_from_string ("Keyboard Shortcuts"),
1564 0,
1565 _("Too many custom shortcuts"));
1566 }
1567
1568 fulldir = g_strdup_printf ("%s%s", GSETTINGS_KEYBINDINGS_DIR, dir);
1569 g_free (dir);
1570 return fulldir;
1571 }
1572
1573 static void
add_custom_shortcut(GtkTreeView * tree_view,GtkTreeModel * model)1574 add_custom_shortcut (GtkTreeView *tree_view,
1575 GtkTreeModel *model)
1576 {
1577 KeyEntry *key_entry;
1578 GtkTreeIter iter;
1579 GtkTreeIter parent_iter;
1580 GtkTreePath *path;
1581 gchar *dir;
1582 GError *error;
1583
1584 error = NULL;
1585 dir = find_free_gsettings_path (&error);
1586 if (dir == NULL)
1587 {
1588 show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tree_view))), error);
1589
1590 g_error_free (error);
1591 return;
1592 }
1593
1594 key_entry = g_new0 (KeyEntry, 1);
1595 key_entry->gsettings_path = g_strdup(dir);
1596 key_entry->gsettings_key = g_strdup("binding");
1597 key_entry->editable = TRUE;
1598 key_entry->model = model;
1599 key_entry->desc_gsettings_key = g_strdup("name");
1600 key_entry->description = g_strdup ("");
1601 key_entry->desc_editable = TRUE;
1602 key_entry->cmd_gsettings_key = g_strdup("action");
1603 key_entry->command = g_strdup ("");
1604 key_entry->cmd_editable = TRUE;
1605 g_free (dir);
1606
1607 if (edit_custom_shortcut (key_entry) &&
1608 key_entry->command && key_entry->command[0])
1609 {
1610 find_section (model, &iter, _("Custom Shortcuts"));
1611 parent_iter = iter;
1612 gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter);
1613 gtk_tree_store_set (GTK_TREE_STORE (model), &iter, KEYENTRY_COLUMN, key_entry, -1);
1614
1615 /* store in gsettings */
1616 key_entry->settings = g_settings_new_with_path (CUSTOM_KEYBINDING_SCHEMA, key_entry->gsettings_path);
1617 g_settings_set_string (key_entry->settings, key_entry->gsettings_key, "disabled");
1618 g_settings_set_string (key_entry->settings, key_entry->desc_gsettings_key, key_entry->description);
1619 g_settings_set_string (key_entry->settings, key_entry->cmd_gsettings_key, key_entry->command);
1620
1621 /* add gsettings watches */
1622 key_entry->gsettings_cnxn_desc = g_signal_connect (key_entry->settings,
1623 "changed::name",
1624 G_CALLBACK (keybinding_description_changed),
1625 key_entry);
1626 key_entry->gsettings_cnxn_cmd = g_signal_connect (key_entry->settings,
1627 "changed::action",
1628 G_CALLBACK (keybinding_command_changed),
1629 key_entry);
1630 key_entry->gsettings_cnxn = g_signal_connect (key_entry->settings,
1631 "changed::binding",
1632 G_CALLBACK (keybinding_key_changed),
1633 key_entry);
1634
1635 /* make the new shortcut visible */
1636 path = gtk_tree_model_get_path (model, &iter);
1637 gtk_tree_view_expand_to_path (tree_view, path);
1638 gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0, 0);
1639 gtk_tree_path_free (path);
1640 }
1641 else
1642 {
1643 g_free (key_entry->gsettings_path);
1644 g_free (key_entry->gsettings_key);
1645 g_free (key_entry->description);
1646 g_free (key_entry->desc_gsettings_key);
1647 g_free (key_entry->command);
1648 g_free (key_entry->cmd_gsettings_key);
1649 g_free (key_entry);
1650 }
1651 }
1652
1653 static void
start_editing_kb_cb(GtkTreeView * treeview,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user_data)1654 start_editing_kb_cb (GtkTreeView *treeview,
1655 GtkTreePath *path,
1656 GtkTreeViewColumn *column,
1657 gpointer user_data)
1658 {
1659 GtkTreeModel *model;
1660 GtkTreeIter iter;
1661 KeyEntry *key;
1662
1663 model = gtk_tree_view_get_model (treeview);
1664 gtk_tree_model_get_iter (model, &iter, path);
1665 gtk_tree_model_get (model, &iter,
1666 KEYENTRY_COLUMN, &key,
1667 -1);
1668
1669 if (key == NULL)
1670 {
1671 /* This is a section heading - expand or collapse */
1672 if (gtk_tree_view_row_expanded (treeview, path))
1673 gtk_tree_view_collapse_row (treeview, path);
1674 else
1675 gtk_tree_view_expand_row (treeview, path, FALSE);
1676 return;
1677 }
1678
1679 /* if only the accel can be edited on the selected row
1680 * always select the accel column */
1681 if (key->desc_editable &&
1682 column == gtk_tree_view_get_column (treeview, 0))
1683 {
1684 gtk_widget_grab_focus (GTK_WIDGET (treeview));
1685 gtk_tree_view_set_cursor (treeview, path,
1686 gtk_tree_view_get_column (treeview, 0),
1687 FALSE);
1688 update_custom_shortcut (model, &iter);
1689 }
1690 else
1691 {
1692 gtk_widget_grab_focus (GTK_WIDGET (treeview));
1693 gtk_tree_view_set_cursor (treeview,
1694 path,
1695 gtk_tree_view_get_column (treeview, 1),
1696 TRUE);
1697 }
1698 }
1699
1700 static gboolean
start_editing_cb(GtkTreeView * tree_view,GdkEventButton * event,gpointer user_data)1701 start_editing_cb (GtkTreeView *tree_view,
1702 GdkEventButton *event,
1703 gpointer user_data)
1704 {
1705 GtkTreePath *path;
1706 GtkTreeViewColumn *column;
1707
1708 if ((event->window != gtk_tree_view_get_bin_window (tree_view)) ||
1709 (event->type != GDK_2BUTTON_PRESS))
1710 return FALSE;
1711
1712 if (gtk_tree_view_get_path_at_pos (tree_view,
1713 (gint) event->x,
1714 (gint) event->y,
1715 &path, &column,
1716 NULL, NULL))
1717 {
1718 IdleData *idle_data;
1719 GtkTreeModel *model;
1720 GtkTreeIter iter;
1721 KeyEntry *key;
1722
1723 if (gtk_tree_path_get_depth (path) == 1)
1724 {
1725 gtk_tree_path_free (path);
1726 return FALSE;
1727 }
1728
1729 model = gtk_tree_view_get_model (tree_view);
1730 gtk_tree_model_get_iter (model, &iter, path);
1731 gtk_tree_model_get (model, &iter,
1732 KEYENTRY_COLUMN, &key,
1733 -1);
1734
1735 /* if only the accel can be edited on the selected row
1736 * always select the accel column */
1737 if (key->desc_editable &&
1738 column == gtk_tree_view_get_column (tree_view, 0))
1739 {
1740 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
1741 gtk_tree_view_set_cursor (tree_view, path,
1742 gtk_tree_view_get_column (tree_view, 0),
1743 FALSE);
1744 update_custom_shortcut (model, &iter);
1745 }
1746 else
1747 {
1748 idle_data = g_new (IdleData, 1);
1749 idle_data->tree_view = tree_view;
1750 idle_data->path = path;
1751 idle_data->column = key->desc_editable ? column :
1752 gtk_tree_view_get_column (tree_view, 1);
1753 g_idle_add ((GSourceFunc) real_start_editing_cb, idle_data);
1754 block_accels = TRUE;
1755 }
1756 g_signal_stop_emission_by_name (tree_view, "button_press_event");
1757 }
1758 return TRUE;
1759 }
1760
1761 /* this handler is used to keep accels from activating while the user
1762 * is assigning a new shortcut so that he won't accidentally trigger one
1763 * of the widgets */
maybe_block_accels(GtkWidget * widget,GdkEventKey * event,gpointer user_data)1764 static gboolean maybe_block_accels(GtkWidget* widget, GdkEventKey* event, gpointer user_data)
1765 {
1766 if (block_accels)
1767 {
1768 return gtk_window_propagate_key_event(GTK_WINDOW(widget), event);
1769 }
1770
1771 return FALSE;
1772 }
1773
1774 static void
cb_dialog_response(GtkWidget * widget,gint response_id,gpointer data)1775 cb_dialog_response (GtkWidget *widget, gint response_id, gpointer data)
1776 {
1777 GtkBuilder *builder = data;
1778 GtkTreeView *treeview;
1779 GtkTreeModel *model;
1780 GtkTreeSelection *selection;
1781 GtkTreeIter iter;
1782
1783 treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder,
1784 "shortcut_treeview"));
1785 model = gtk_tree_view_get_model (treeview);
1786
1787 if (response_id == GTK_RESPONSE_HELP)
1788 {
1789 capplet_help (GTK_WINDOW (widget),
1790 "goscustdesk-39");
1791 }
1792 else if (response_id == RESPONSE_ADD)
1793 {
1794 add_custom_shortcut (treeview, model);
1795 }
1796 else if (response_id == RESPONSE_REMOVE)
1797 {
1798 selection = gtk_tree_view_get_selection (treeview);
1799 if (gtk_tree_selection_get_selected (selection, NULL, &iter))
1800 {
1801 remove_custom_shortcut (model, &iter);
1802 }
1803 }
1804 else
1805 {
1806 clear_old_model (builder);
1807 gtk_main_quit ();
1808 }
1809 }
1810
1811 static void
selection_changed(GtkTreeSelection * selection,gpointer data)1812 selection_changed (GtkTreeSelection *selection, gpointer data)
1813 {
1814 GtkWidget *button = data;
1815 GtkTreeModel *model;
1816 GtkTreeIter iter;
1817 KeyEntry *key;
1818 gboolean can_remove;
1819
1820 can_remove = FALSE;
1821 if (gtk_tree_selection_get_selected (selection, &model, &iter))
1822 {
1823 gtk_tree_model_get (model, &iter, KEYENTRY_COLUMN, &key, -1);
1824 if (key && key->command != NULL && key->editable)
1825 can_remove = TRUE;
1826 }
1827
1828 gtk_widget_set_sensitive (button, can_remove);
1829 }
1830
1831 static void
cb_app_dialog_response(GtkWidget * dialog,gint response_id,gpointer data)1832 cb_app_dialog_response (GtkWidget *dialog, gint response_id, gpointer data)
1833 {
1834 if (response_id == GTK_RESPONSE_OK)
1835 {
1836 GAppInfo *info;
1837 const gchar *custom_name;
1838
1839 info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
1840
1841 gtk_entry_set_text (GTK_ENTRY (custom_shortcut_command_entry),
1842 g_app_info_get_executable (info));
1843 /* if name isn't set yet, use the associated one */
1844 custom_name = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_name_entry));
1845 if (! custom_name || custom_name[0] == '\0')
1846 gtk_entry_set_text (GTK_ENTRY (custom_shortcut_name_entry),
1847 g_app_info_get_display_name (info));
1848
1849 g_object_unref (info);
1850 }
1851
1852 gtk_widget_hide (dialog);
1853 }
1854
1855 static void
setup_dialog(GtkBuilder * builder,GSettings * marco_settings)1856 setup_dialog (GtkBuilder *builder, GSettings *marco_settings)
1857 {
1858 GtkCellRenderer *renderer;
1859 GtkTreeViewColumn *column;
1860 GtkWidget *widget;
1861 GtkTreeView *treeview;
1862 GtkTreeSelection *selection;
1863 GtkWidget *button;
1864
1865 treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder,
1866 "shortcut_treeview"));
1867
1868 g_signal_connect (treeview, "button_press_event",
1869 G_CALLBACK (start_editing_cb), builder);
1870 g_signal_connect (treeview, "row-activated",
1871 G_CALLBACK (start_editing_kb_cb), NULL);
1872
1873 renderer = gtk_cell_renderer_text_new ();
1874
1875 g_signal_connect (renderer, "edited",
1876 G_CALLBACK (description_edited_callback),
1877 treeview);
1878
1879 column = gtk_tree_view_column_new_with_attributes (_("Action"),
1880 renderer,
1881 "text", DESCRIPTION_COLUMN,
1882 NULL);
1883 gtk_tree_view_column_set_cell_data_func (column, renderer, description_set_func, NULL, NULL);
1884 gtk_tree_view_column_set_resizable (column, FALSE);
1885
1886 gtk_tree_view_append_column (treeview, column);
1887 gtk_tree_view_column_set_sort_column_id (column, DESCRIPTION_COLUMN);
1888
1889 renderer = (GtkCellRenderer *) g_object_new (EGG_TYPE_CELL_RENDERER_KEYS,
1890 "accel_mode", EGG_CELL_RENDERER_KEYS_MODE_X,
1891 NULL);
1892
1893 g_signal_connect (renderer, "accel_edited",
1894 G_CALLBACK (accel_edited_callback),
1895 treeview);
1896
1897 g_signal_connect (renderer, "accel_cleared",
1898 G_CALLBACK (accel_cleared_callback),
1899 treeview);
1900
1901 column = gtk_tree_view_column_new_with_attributes (_("Shortcut"), renderer, NULL);
1902 gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL);
1903 gtk_tree_view_column_set_resizable (column, FALSE);
1904
1905 gtk_tree_view_append_column (treeview, column);
1906 gtk_tree_view_column_set_sort_column_id (column, KEYENTRY_COLUMN);
1907
1908 g_signal_connect (marco_settings,
1909 "changed::num-workspaces",
1910 G_CALLBACK (key_entry_controlling_key_changed),
1911 builder);
1912
1913 /* set up the dialog */
1914 reload_key_entries (builder);
1915
1916 widget = _gtk_builder_get_widget (builder, "mate-keybinding-dialog");
1917 gtk_window_set_default_size (GTK_WINDOW (widget), 400, 500);
1918 widget = _gtk_builder_get_widget (builder, "label-suggest");
1919 gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
1920 gtk_label_set_max_width_chars (GTK_LABEL (widget), 60);
1921
1922 widget = _gtk_builder_get_widget (builder, "mate-keybinding-dialog");
1923 capplet_set_icon (widget, "preferences-desktop-keyboard-shortcuts");
1924 gtk_widget_show (widget);
1925
1926 g_signal_connect (widget, "key_press_event", G_CALLBACK (maybe_block_accels), NULL);
1927 g_signal_connect (widget, "response", G_CALLBACK (cb_dialog_response), builder);
1928
1929 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
1930 g_signal_connect (selection, "changed",
1931 G_CALLBACK (selection_changed),
1932 _gtk_builder_get_widget (builder, "remove-button"));
1933
1934 /* setup the custom shortcut dialog */
1935 custom_shortcut_dialog = _gtk_builder_get_widget (builder,
1936 "custom-shortcut-dialog");
1937 custom_shortcut_name_entry = _gtk_builder_get_widget (builder,
1938 "custom-shortcut-name-entry");
1939 custom_shortcut_command_entry = _gtk_builder_get_widget (builder,
1940 "custom-shortcut-command-entry");
1941 gtk_dialog_set_default_response (GTK_DIALOG (custom_shortcut_dialog),
1942 GTK_RESPONSE_OK);
1943 gtk_window_set_transient_for (GTK_WINDOW (custom_shortcut_dialog),
1944 GTK_WINDOW (widget));
1945 button = _gtk_builder_get_widget (builder, "custom-shortcut-command-button");
1946 widget = _gtk_builder_get_widget (builder, "custom-shortcut-application-dialog");
1947 g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_dialog_run), widget);
1948 g_signal_connect (widget, "response", G_CALLBACK (cb_app_dialog_response), NULL);
1949 widget = gtk_app_chooser_dialog_get_widget (GTK_APP_CHOOSER_DIALOG (widget));
1950 gtk_app_chooser_widget_set_show_all (GTK_APP_CHOOSER_WIDGET (widget), TRUE);
1951 }
1952
1953 static void
on_window_manager_change(const char * wm_name,GtkBuilder * builder)1954 on_window_manager_change (const char *wm_name, GtkBuilder *builder)
1955 {
1956 reload_key_entries (builder);
1957 }
1958
1959 int
main(int argc,char * argv[])1960 main (int argc, char *argv[])
1961 {
1962 GtkBuilder *builder;
1963 GSettings *marco_settings;
1964
1965 capplet_init (NULL, &argc, &argv);
1966
1967 activate_settings_daemon ();
1968
1969 builder = gtk_builder_new_from_resource ("/org/mate/mcc/keybindings/mate-keybinding-properties.ui");
1970
1971 wm_common_register_window_manager_change ((GFunc) on_window_manager_change, builder);
1972
1973 marco_settings = g_settings_new ("org.mate.Marco.general");
1974
1975 setup_dialog (builder, marco_settings);
1976
1977 gtk_main ();
1978
1979 g_object_unref (marco_settings);
1980 g_object_unref (builder);
1981 return 0;
1982 }
1983
1984 /*
1985 * vim: sw=2 ts=8 cindent noai bs=2
1986 */
1987