1 /*
2  * Copyright © 2001, 2002 Havoc Pennington, Red Hat Inc.
3  * Copyright © 2008 Christian Persch
4  * Copyright (C) 2012-2021 MATE Developers
5  *
6  * Mate-terminal is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Mate-terminal is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 
22 #include <string.h>
23 
24 #include <gdk/gdkkeysyms.h>
25 
26 #include "terminal-accels.h"
27 #include "terminal-app.h"
28 #include "terminal-debug.h"
29 #include "terminal-intl.h"
30 #include "terminal-profile.h"
31 #include "terminal-util.h"
32 
33 /* NOTES
34  *
35  * There are two sources of keybindings changes, from GSettings and from
36  * the accel map (happens with in-place menu editing).
37  *
38  * When a keybinding GSettins key changes, we propagate that into the
39  * accel map.
40  * When the accel map changes, we queue a sync to GSettings.
41  *
42  * To avoid infinite loops, we short-circuit in both directions
43  * if the value is unchanged from last known.
44  *
45  * In the keybinding editor, when editing or clearing an accel, we write
46  * the change directly to GSettings and rely on the GSettings callback to
47  * actually apply the change to the accel map.
48  */
49 
50 #define ACCEL_PATH_ROOT "<Actions>/Main/"
51 #define ACCEL_PATH_NEW_TAB              ACCEL_PATH_ROOT "FileNewTab"
52 #define ACCEL_PATH_NEW_WINDOW           ACCEL_PATH_ROOT "FileNewWindow"
53 #define ACCEL_PATH_NEW_PROFILE          ACCEL_PATH_ROOT "FileNewProfile"
54 #define ACCEL_PATH_SAVE_CONTENTS        ACCEL_PATH_ROOT "FileSaveContents"
55 #define ACCEL_PATH_CLOSE_TAB            ACCEL_PATH_ROOT "FileCloseTab"
56 #define ACCEL_PATH_CLOSE_WINDOW         ACCEL_PATH_ROOT "FileCloseWindow"
57 #define ACCEL_PATH_COPY                 ACCEL_PATH_ROOT "EditCopy"
58 #define ACCEL_PATH_PASTE                ACCEL_PATH_ROOT "EditPaste"
59 #define ACCEL_PATH_SELECT_ALL           ACCEL_PATH_ROOT "EditSelectAll"
60 #define ACCEL_PATH_SEARCH_FIND          ACCEL_PATH_ROOT "SearchFind"
61 #define ACCEL_PATH_SEARCH_FIND_NEXT     ACCEL_PATH_ROOT "SearchFindNext"
62 #define ACCEL_PATH_SEARCH_FIND_PREVIOUS ACCEL_PATH_ROOT "SearchFindPrevious"
63 #define ACCEL_PATH_TOGGLE_MENUBAR       ACCEL_PATH_ROOT "ViewMenubar"
64 #define ACCEL_PATH_FULL_SCREEN          ACCEL_PATH_ROOT "ViewFullscreen"
65 #define ACCEL_PATH_RESET                ACCEL_PATH_ROOT "TerminalReset"
66 #define ACCEL_PATH_RESET_AND_CLEAR      ACCEL_PATH_ROOT "TerminalResetClear"
67 #define ACCEL_PATH_PREV_PROFILE         ACCEL_PATH_ROOT "ProfilePrevious"
68 #define ACCEL_PATH_NEXT_PROFILE         ACCEL_PATH_ROOT "ProfileNext"
69 #define ACCEL_PATH_PREV_TAB             ACCEL_PATH_ROOT "TabsPrevious"
70 #define ACCEL_PATH_NEXT_TAB             ACCEL_PATH_ROOT "TabsNext"
71 #define ACCEL_PATH_SET_TERMINAL_TITLE   ACCEL_PATH_ROOT "TerminalSetTitle"
72 #define ACCEL_PATH_HELP                 ACCEL_PATH_ROOT "HelpContents"
73 #define ACCEL_PATH_ZOOM_IN              ACCEL_PATH_ROOT "ViewZoomIn"
74 #define ACCEL_PATH_ZOOM_OUT             ACCEL_PATH_ROOT "ViewZoomOut"
75 #define ACCEL_PATH_ZOOM_NORMAL          ACCEL_PATH_ROOT "ViewZoom100"
76 #define ACCEL_PATH_MOVE_TAB_LEFT        ACCEL_PATH_ROOT "TabsMoveLeft"
77 #define ACCEL_PATH_MOVE_TAB_RIGHT       ACCEL_PATH_ROOT "TabsMoveRight"
78 #define ACCEL_PATH_DETACH_TAB           ACCEL_PATH_ROOT "TabsDetach"
79 #define ACCEL_PATH_SWITCH_TAB_PREFIX    ACCEL_PATH_ROOT "TabsSwitch"
80 
81 #define KEY_CLOSE_TAB            "close-tab"
82 #define KEY_CLOSE_WINDOW         "close-window"
83 #define KEY_COPY                 "copy"
84 #define KEY_DETACH_TAB           "detach-tab"
85 #define KEY_FULL_SCREEN          "full-screen"
86 #define KEY_HELP                 "help"
87 #define KEY_MOVE_TAB_LEFT        "move-tab-left"
88 #define KEY_MOVE_TAB_RIGHT       "move-tab-right"
89 #define KEY_NEW_PROFILE          "new-profile"
90 #define KEY_NEW_TAB              "new-tab"
91 #define KEY_NEW_WINDOW           "new-window"
92 #define KEY_NEXT_PROFILE         "next-profile"
93 #define KEY_NEXT_TAB             "next-tab"
94 #define KEY_PASTE                "paste"
95 #define KEY_PREV_PROFILE         "prev-profile"
96 #define KEY_PREV_TAB             "prev-tab"
97 #define KEY_RESET_AND_CLEAR      "reset-and-clear"
98 #define KEY_RESET                "reset"
99 #define KEY_SEARCH_FIND          "search-find"
100 #define KEY_SEARCH_FIND_NEXT     "search-find-next"
101 #define KEY_SEARCH_FIND_PREVIOUS "search-find-previous"
102 #define KEY_SELECT_ALL           "select-all"
103 #define KEY_SAVE_CONTENTS        "save-contents"
104 #define KEY_SET_TERMINAL_TITLE   "set-terminal-title"
105 #define KEY_TOGGLE_MENUBAR       "toggle-menubar"
106 #define KEY_ZOOM_IN              "zoom-in"
107 #define KEY_ZOOM_NORMAL          "zoom-normal"
108 #define KEY_ZOOM_OUT             "zoom-out"
109 #define KEY_SWITCH_TAB_PREFIX    "switch-to-tab-"
110 
111 #if 1
112 /*
113 * We don't want to enable content saving until vte supports it async.
114 * So we disable this code for stable versions.
115 */
116 #include "terminal-version.h"
117 
118 #if (TERMINAL_MINOR_VERSION & 1) != 0
119 #define ENABLE_SAVE
120 #else
121 #undef ENABLE_SAVE
122 #endif
123 #endif
124 
125 typedef struct
126 {
127 	const char *user_visible_name;
128 	const char *gsettings_key;
129 	const char *accel_path;
130 	/* last values received from GSettings */
131 	GdkModifierType gsettings_mask;
132 	guint gsettings_keyval;
133 	GClosure *closure;
134 	/* have gotten a notification from gtk */
135 	gboolean needs_gsettings_sync;
136 	gboolean accel_path_unlocked;
137 } KeyEntry;
138 
139 typedef struct
140 {
141 	KeyEntry *key_entry;
142 	guint n_elements;
143 	const char *user_visible_name;
144 } KeyEntryList;
145 
146 static KeyEntry file_entries[] =
147 {
148 	{
149 		N_("New Tab"),
150 		KEY_NEW_TAB, ACCEL_PATH_NEW_TAB, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_t, NULL, FALSE, TRUE
151 	},
152 	{
153 		N_("New Window"),
154 		KEY_NEW_WINDOW, ACCEL_PATH_NEW_WINDOW, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_n, NULL, FALSE, TRUE
155 	},
156 	{
157 		N_("New Profile"),
158 		KEY_NEW_PROFILE, ACCEL_PATH_NEW_PROFILE, 0, 0, NULL, FALSE, TRUE
159 	},
160 #ifdef ENABLE_SAVE
161 	{
162 		N_("Save Contents"),
163 		KEY_SAVE_CONTENTS, ACCEL_PATH_SAVE_CONTENTS, 0, 0, NULL, FALSE, TRUE
164 	},
165 #endif
166 	{
167 		N_("Close Tab"),
168 		KEY_CLOSE_TAB, ACCEL_PATH_CLOSE_TAB, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_w, NULL, FALSE, TRUE
169 	},
170 	{
171 		N_("Close Window"),
172 		KEY_CLOSE_WINDOW, ACCEL_PATH_CLOSE_WINDOW, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_q, NULL, FALSE, TRUE
173 	},
174 };
175 
176 static KeyEntry edit_entries[] =
177 {
178 	{
179 		N_("Copy"),
180 		KEY_COPY, ACCEL_PATH_COPY, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_c, NULL, FALSE, TRUE
181 	},
182 	{
183 		N_("Paste"),
184 		KEY_PASTE, ACCEL_PATH_PASTE, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_v, NULL, FALSE, TRUE
185 	},
186 	{
187 		N_("Select All"),
188 		KEY_SELECT_ALL, ACCEL_PATH_SELECT_ALL, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_a, NULL, FALSE, TRUE
189 	}
190 };
191 
192 static KeyEntry view_entries[] =
193 {
194 	{
195 		N_("Hide and Show menubar"),
196 		KEY_TOGGLE_MENUBAR, ACCEL_PATH_TOGGLE_MENUBAR, 0, 0, NULL, FALSE, TRUE
197 	},
198 	{
199 		N_("Full Screen"),
200 		KEY_FULL_SCREEN, ACCEL_PATH_FULL_SCREEN, 0, GDK_KEY_F11, NULL, FALSE, TRUE
201 	},
202 	{
203 		N_("Zoom In"),
204 		KEY_ZOOM_IN, ACCEL_PATH_ZOOM_IN, GDK_CONTROL_MASK, GDK_KEY_plus, NULL, FALSE, TRUE
205 	},
206 	{
207 		N_("Zoom Out"),
208 		KEY_ZOOM_OUT, ACCEL_PATH_ZOOM_OUT, GDK_CONTROL_MASK, GDK_KEY_minus, NULL, FALSE, TRUE
209 	},
210 	{
211 		N_("Normal Size"),
212 		KEY_ZOOM_NORMAL, ACCEL_PATH_ZOOM_NORMAL, GDK_CONTROL_MASK, GDK_KEY_0, NULL, FALSE, TRUE
213 	}
214 };
215 
216 static KeyEntry search_entries[] =
217 {
218 	{
219 		N_("Find"),
220 		KEY_SEARCH_FIND, ACCEL_PATH_SEARCH_FIND, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_f, NULL, FALSE, TRUE
221 	},
222 	{
223 		N_("Find Next"),
224 		KEY_SEARCH_FIND_NEXT, ACCEL_PATH_SEARCH_FIND_NEXT, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_h, NULL, FALSE, TRUE
225 	},
226 	{
227 		N_("Find Previous"),
228 		KEY_SEARCH_FIND_PREVIOUS, ACCEL_PATH_SEARCH_FIND_PREVIOUS, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_g, NULL, FALSE, TRUE
229 	}
230 };
231 
232 static KeyEntry terminal_entries[] =
233 {
234 	{
235 		N_("Set Title"),
236 		KEY_SET_TERMINAL_TITLE, ACCEL_PATH_SET_TERMINAL_TITLE, 0, 0, NULL, FALSE, TRUE
237 	},
238 	{
239 		N_("Reset"),
240 		KEY_RESET, ACCEL_PATH_RESET, 0, 0, NULL, FALSE, TRUE
241 	},
242 	{
243 		N_("Reset and Clear"),
244 		KEY_RESET_AND_CLEAR, ACCEL_PATH_RESET_AND_CLEAR, 0, 0, NULL, FALSE, TRUE
245 	},
246 	{
247 		N_("Switch to Previous Profile"),
248 		KEY_PREV_PROFILE, ACCEL_PATH_PREV_PROFILE, GDK_MOD1_MASK, GDK_KEY_Page_Up, NULL, FALSE, TRUE
249 	},
250 	{
251 		N_("Switch to Next Profile"),
252 		KEY_NEXT_PROFILE, ACCEL_PATH_NEXT_PROFILE, GDK_MOD1_MASK, GDK_KEY_Page_Down, NULL, FALSE, TRUE
253 	},
254 };
255 
256 static KeyEntry tabs_entries[] =
257 {
258 	{
259 		N_("Switch to Previous Tab"),
260 		KEY_PREV_TAB, ACCEL_PATH_PREV_TAB, GDK_CONTROL_MASK, GDK_KEY_Page_Up, NULL, FALSE, TRUE
261 	},
262 	{
263 		N_("Switch to Next Tab"),
264 		KEY_NEXT_TAB, ACCEL_PATH_NEXT_TAB, GDK_CONTROL_MASK, GDK_KEY_Page_Down, NULL, FALSE, TRUE
265 	},
266 	{
267 		N_("Move Tab to the Left"),
268 		KEY_MOVE_TAB_LEFT, ACCEL_PATH_MOVE_TAB_LEFT, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_Page_Up, NULL, FALSE, TRUE
269 	},
270 	{
271 		N_("Move Tab to the Right"),
272 		KEY_MOVE_TAB_RIGHT, ACCEL_PATH_MOVE_TAB_RIGHT, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GDK_KEY_Page_Down, NULL, FALSE, TRUE
273 	},
274 	{
275 		N_("Detach Tab"),
276 		KEY_DETACH_TAB, ACCEL_PATH_DETACH_TAB, 0, 0, NULL, FALSE, TRUE
277 	},
278 	{
279 		N_("Switch to Tab 1"),
280 		KEY_SWITCH_TAB_PREFIX "1",
281 		ACCEL_PATH_SWITCH_TAB_PREFIX "1", GDK_MOD1_MASK, GDK_KEY_1, NULL, FALSE, TRUE
282 	},
283 	{
284 		N_("Switch to Tab 2"),
285 		KEY_SWITCH_TAB_PREFIX "2",
286 		ACCEL_PATH_SWITCH_TAB_PREFIX "2", GDK_MOD1_MASK, GDK_KEY_2, NULL, FALSE, TRUE
287 	},
288 	{
289 		N_("Switch to Tab 3"),
290 		KEY_SWITCH_TAB_PREFIX "3",
291 		ACCEL_PATH_SWITCH_TAB_PREFIX "3", GDK_MOD1_MASK, GDK_KEY_3, NULL, FALSE, TRUE
292 	},
293 	{
294 		N_("Switch to Tab 4"),
295 		KEY_SWITCH_TAB_PREFIX "4",
296 		ACCEL_PATH_SWITCH_TAB_PREFIX "4", GDK_MOD1_MASK, GDK_KEY_4, NULL, FALSE, TRUE
297 	},
298 	{
299 		N_("Switch to Tab 5"),
300 		KEY_SWITCH_TAB_PREFIX "5",
301 		ACCEL_PATH_SWITCH_TAB_PREFIX "5", GDK_MOD1_MASK, GDK_KEY_5, NULL, FALSE, TRUE
302 	},
303 	{
304 		N_("Switch to Tab 6"),
305 		KEY_SWITCH_TAB_PREFIX "6",
306 		ACCEL_PATH_SWITCH_TAB_PREFIX "6", GDK_MOD1_MASK, GDK_KEY_6, NULL, FALSE, TRUE
307 	},
308 	{
309 		N_("Switch to Tab 7"),
310 		KEY_SWITCH_TAB_PREFIX "7",
311 		ACCEL_PATH_SWITCH_TAB_PREFIX "7", GDK_MOD1_MASK, GDK_KEY_7, NULL, FALSE, TRUE
312 	},
313 	{
314 		N_("Switch to Tab 8"),
315 		KEY_SWITCH_TAB_PREFIX "8",
316 		ACCEL_PATH_SWITCH_TAB_PREFIX "8", GDK_MOD1_MASK, GDK_KEY_8, NULL, FALSE, TRUE
317 	},
318 	{
319 		N_("Switch to Tab 9"),
320 		KEY_SWITCH_TAB_PREFIX "9",
321 		ACCEL_PATH_SWITCH_TAB_PREFIX "9", GDK_MOD1_MASK, GDK_KEY_9, NULL, FALSE, TRUE
322 	},
323 	{
324 		N_("Switch to Tab 10"),
325 		KEY_SWITCH_TAB_PREFIX "10",
326 		ACCEL_PATH_SWITCH_TAB_PREFIX "10", GDK_MOD1_MASK, GDK_KEY_0, NULL, FALSE, TRUE
327 	},
328 	{
329 		N_("Switch to Tab 11"),
330 		KEY_SWITCH_TAB_PREFIX "11",
331 		ACCEL_PATH_SWITCH_TAB_PREFIX "11", 0, 0, NULL, FALSE, TRUE
332 	},
333 	{
334 		N_("Switch to Tab 12"),
335 		KEY_SWITCH_TAB_PREFIX "12",
336 		ACCEL_PATH_SWITCH_TAB_PREFIX "12", 0, 0, NULL, FALSE, TRUE
337 	}
338 };
339 
340 static KeyEntry help_entries[] =
341 {
342 	{ N_("Contents"), KEY_HELP, ACCEL_PATH_HELP, 0, GDK_KEY_F1, NULL, FALSE, TRUE }
343 };
344 
345 static KeyEntryList all_entries[] =
346 {
347 	{ file_entries, G_N_ELEMENTS (file_entries), N_("File") },
348 	{ edit_entries, G_N_ELEMENTS (edit_entries), N_("Edit") },
349 	{ view_entries, G_N_ELEMENTS (view_entries), N_("View") },
350 	{ search_entries, G_N_ELEMENTS (search_entries), N_("Search") },
351 	{ terminal_entries, G_N_ELEMENTS (terminal_entries), N_("Terminal") },
352 	{ tabs_entries, G_N_ELEMENTS (tabs_entries), N_("Tabs") },
353 	{ help_entries, G_N_ELEMENTS (help_entries), N_("Help") }
354 };
355 
356 enum
357 {
358     ACTION_COLUMN,
359     KEYVAL_COLUMN,
360     N_COLUMNS
361 };
362 
363 static void keys_change_notify (GSettings *settings,
364                                 const gchar *key,
365                                 gpointer user_data);
366 
367 static void accel_changed_callback (GtkAccelGroup  *accel_group,
368                                     guint           keyval,
369                                     GdkModifierType modifier,
370                                     GClosure       *accel_closure,
371                                     gpointer        data);
372 
373 static gboolean binding_from_string (const char      *str,
374                                      guint           *accelerator_key,
375                                      GdkModifierType *accelerator_mods);
376 
377 static gboolean binding_from_value  (GVariant        *value,
378                                      guint           *accelerator_key,
379                                      GdkModifierType *accelerator_mods);
380 
381 static gboolean sync_idle_cb (gpointer data);
382 
383 static guint sync_idle_id = 0;
384 static GtkAccelGroup *notification_group = NULL;
385 /* never set GSettings keys in response to receiving a GSettings notify. */
386 static int inside_gsettings_notify = 0;
387 static GtkWidget *edit_keys_dialog = NULL;
388 static GtkTreeStore *edit_keys_store = NULL;
389 static GHashTable *gsettings_key_to_entry;
390 static GSettings *settings_keybindings;
391 
392 static char*
binding_name(guint keyval,GdkModifierType mask)393 binding_name (guint            keyval,
394               GdkModifierType  mask)
395 {
396 	if (keyval != 0)
397 		return gtk_accelerator_name (keyval, mask);
398 
399 	return g_strdup ("disabled");
400 }
401 
402 static char*
binding_display_name(guint keyval,GdkModifierType mask)403 binding_display_name (guint            keyval,
404                       GdkModifierType  mask)
405 {
406 	if (keyval != 0)
407 		return gtk_accelerator_get_label (keyval, mask);
408 
409 	return g_strdup (_("Disabled"));
410 }
411 
412 void
terminal_accels_init(void)413 terminal_accels_init (void)
414 {
415 	guint i, j;
416 
417 	settings_keybindings = g_settings_new (CONF_KEYS_SCHEMA);
418 
419 	g_signal_connect (settings_keybindings,
420 			  "changed",
421 			  G_CALLBACK(keys_change_notify),
422 			  NULL);
423 
424 	gsettings_key_to_entry = g_hash_table_new (g_str_hash, g_str_equal);
425 
426 	notification_group = gtk_accel_group_new ();
427 
428 	for (i = 0; i < G_N_ELEMENTS (all_entries); ++i)
429 	{
430 		for (j = 0; j < all_entries[i].n_elements; ++j)
431 		{
432 			KeyEntry *key_entry;
433 
434 			key_entry = &(all_entries[i].key_entry[j]);
435 
436 			g_hash_table_insert (gsettings_key_to_entry,
437 			                     (gpointer) key_entry->gsettings_key,
438 			                     key_entry);
439 
440 			key_entry->closure = g_closure_new_simple (sizeof (GClosure), key_entry);
441 
442 			g_closure_ref (key_entry->closure);
443 			g_closure_sink (key_entry->closure);
444 
445 			gtk_accel_group_connect_by_path (notification_group,
446 			                                 I_(key_entry->accel_path),
447 			                                 key_entry->closure);
448 			keys_change_notify (settings_keybindings, key_entry->gsettings_key, NULL);
449 		}
450 	}
451 
452 	g_signal_connect (notification_group, "accel-changed",
453 	                  G_CALLBACK (accel_changed_callback), NULL);
454 }
455 
456 void
terminal_accels_shutdown(void)457 terminal_accels_shutdown (void)
458 {
459 
460 	g_signal_handlers_disconnect_by_func (settings_keybindings,
461 					      G_CALLBACK(keys_change_notify),
462 					      NULL);
463 	g_object_unref (settings_keybindings);
464 
465 	if (sync_idle_id != 0)
466 	{
467 		g_source_remove (sync_idle_id);
468 		sync_idle_id = 0;
469 
470 		sync_idle_cb (NULL);
471 	}
472 
473 	g_hash_table_destroy (gsettings_key_to_entry);
474 	gsettings_key_to_entry = NULL;
475 
476 	g_object_unref (notification_group);
477 	notification_group = NULL;
478 }
479 
480 static gboolean
update_model_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)481 update_model_foreach (GtkTreeModel *model,
482                       GtkTreePath  *path,
483                       GtkTreeIter  *iter,
484                       gpointer      data)
485 {
486 	KeyEntry *key_entry = NULL;
487 
488 	gtk_tree_model_get (model, iter,
489 	                    KEYVAL_COLUMN, &key_entry,
490 	                    -1);
491 
492 	if (key_entry == (KeyEntry *) data)
493 	{
494 		gtk_tree_model_row_changed (model, path, iter);
495 		return TRUE;
496 	}
497 	return FALSE;
498 }
499 
500 static void
keys_change_notify(GSettings * settings,const gchar * key,gpointer user_data)501 keys_change_notify (GSettings *settings,
502                     const gchar *key,
503                     gpointer user_data)
504 {
505 	GVariant *val;
506 	KeyEntry *key_entry;
507 	GdkModifierType mask;
508 	guint keyval;
509 
510 	_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
511 	                       "key %s changed\n",
512 	                       key);
513 
514 	val = g_settings_get_value (settings, key);
515 
516 #ifdef MATE_ENABLE_DEBUG
517 	_TERMINAL_DEBUG_IF (TERMINAL_DEBUG_ACCELS)
518 	{
519 		if (val == NULL)
520 			_terminal_debug_print (TERMINAL_DEBUG_ACCELS, " changed to be unset\n");
521 		else if (!g_variant_is_of_type (val, G_VARIANT_TYPE_STRING))
522 			_terminal_debug_print (TERMINAL_DEBUG_ACCELS, " changed to non-string value\n");
523 		else
524 			_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
525 			                       " changed to \"%s\"\n",
526 			                       g_variant_get_string (val, NULL));
527 	}
528 #endif
529 
530 	key_entry = g_hash_table_lookup (gsettings_key_to_entry, key);
531 	if (!key_entry)
532 	{
533 		/* shouldn't really happen, but let's be safe */
534 		_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
535 		                       "  WARNING: KeyEntry for changed key not found, bailing out\n");
536 		return;
537 	}
538 
539 	if (!binding_from_value (val, &keyval, &mask))
540 	{
541 		const char *str = g_variant_is_of_type (val, G_VARIANT_TYPE_STRING) ? g_variant_get_string (val, NULL) : NULL;
542 		g_printerr ("The value \"%s\" of configuration key %s is not a valid accelerator\n",
543 		            str ? str : "(null)",
544 		            key_entry->gsettings_key);
545 		return;
546 	}
547 	key_entry->gsettings_keyval = keyval;
548 	key_entry->gsettings_mask = mask;
549 
550 	/* Unlock the path, so we can change its accel */
551 	if (!key_entry->accel_path_unlocked)
552 		gtk_accel_map_unlock_path (key_entry->accel_path);
553 
554 	/* sync over to GTK */
555 	_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
556 	                       "changing path %s to %s\n",
557 	                       key_entry->accel_path,
558 	                       binding_name (keyval, mask)); /* memleak */
559 	inside_gsettings_notify += 1;
560 	/* Note that this may return FALSE, e.g. when the entry was already set correctly. */
561 	gtk_accel_map_change_entry (key_entry->accel_path,
562 	                            keyval, mask,
563 	                            TRUE);
564 	inside_gsettings_notify -= 1;
565 
566 	/* Lock the path if the GSettings key isn't writable */
567 	key_entry->accel_path_unlocked = g_settings_is_writable (settings, key);
568 	if (!key_entry->accel_path_unlocked)
569 		gtk_accel_map_lock_path (key_entry->accel_path);
570 
571 	/* This seems necessary to update the tree model, since sometimes the
572 	 * notification on the notification_group seems not to be emitted correctly.
573 	 * Without this change, when trying to set an accel to e.g. Alt-T (while the main
574 	 * menu in the terminal windows is _Terminal with Alt-T mnemonic) only displays
575 	 * the accel change after a re-expose of the row.
576 	 * FIXME: Find out *why* the accel-changed signal is wrong here!
577 	 */
578 	if (edit_keys_store)
579 		gtk_tree_model_foreach (GTK_TREE_MODEL (edit_keys_store), update_model_foreach, key_entry);
580 
581 	g_variant_unref(val);
582 }
583 
584 static void
accel_changed_callback(GtkAccelGroup * accel_group,guint keyval,GdkModifierType modifier,GClosure * accel_closure,gpointer data)585 accel_changed_callback (GtkAccelGroup  *accel_group,
586                         guint           keyval,
587                         GdkModifierType modifier,
588                         GClosure       *accel_closure,
589                         gpointer        data)
590 {
591 	/* FIXME because GTK accel API is so nonsensical, we get
592 	 * a notify for each closure, on both the added and the removed
593 	 * accelerator. We just use the accel closure to find our
594 	 * accel entry, then update the value of that entry.
595 	 * We use an idle function to avoid setting the entry
596 	 * in GSettings when the accelerator gets removed and then
597 	 * setting it again when it gets added.
598 	 */
599 	KeyEntry *key_entry;
600 
601 	_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
602 	                       "Changed accel %s closure %p\n",
603 	                       binding_name (keyval, modifier), /* memleak */
604 	                       accel_closure);
605 
606 	if (inside_gsettings_notify)
607 	{
608 		_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
609 		                       "Ignoring change from gtk because we're inside a GSettings notify\n");
610 		return;
611 	}
612 
613 	key_entry = accel_closure->data;
614 	g_assert (key_entry);
615 
616 	key_entry->needs_gsettings_sync = TRUE;
617 
618 	if (sync_idle_id == 0)
619 		sync_idle_id = g_idle_add (sync_idle_cb, NULL);
620 }
621 
622 static gboolean
binding_from_string(const char * str,guint * accelerator_key,GdkModifierType * accelerator_mods)623 binding_from_string (const char      *str,
624                      guint           *accelerator_key,
625                      GdkModifierType *accelerator_mods)
626 {
627 	if (str == NULL ||
628 	        strcmp (str, "disabled") == 0)
629 	{
630 		*accelerator_key = 0;
631 		*accelerator_mods = 0;
632 		return TRUE;
633 	}
634 
635 	gtk_accelerator_parse (str, accelerator_key, accelerator_mods);
636 	if (*accelerator_key == 0 &&
637 	        *accelerator_mods == 0)
638 		return FALSE;
639 
640 	return TRUE;
641 }
642 
643 static gboolean
binding_from_value(GVariant * value,guint * accelerator_key,GdkModifierType * accelerator_mods)644 binding_from_value (GVariant         *value,
645                     guint            *accelerator_key,
646                     GdkModifierType  *accelerator_mods)
647 {
648 	if (value == NULL)
649 	{
650 		/* unset */
651 		*accelerator_key = 0;
652 		*accelerator_mods = 0;
653 		return TRUE;
654 	}
655 
656 	if (!g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
657 		return FALSE;
658 
659 	return binding_from_string (g_variant_get_string (value,NULL),
660 	                            accelerator_key,
661 	                            accelerator_mods);
662 }
663 
664 static void
add_key_entry_to_changeset(gpointer key,KeyEntry * key_entry,GSettings * changeset)665 add_key_entry_to_changeset (gpointer key,
666                             KeyEntry *key_entry,
667                             GSettings *changeset)
668 {
669 	GtkAccelKey gtk_key;
670 
671 	if (!key_entry->needs_gsettings_sync)
672 		return;
673 
674 	key_entry->needs_gsettings_sync = FALSE;
675 
676 	if (gtk_accel_map_lookup_entry (key_entry->accel_path, &gtk_key) &&
677 	        (gtk_key.accel_key != key_entry->gsettings_keyval ||
678 	         gtk_key.accel_mods != key_entry->gsettings_mask))
679 	{
680 		char *accel_name;
681 
682 		accel_name = binding_name (gtk_key.accel_key, gtk_key.accel_mods);
683 		g_settings_set_string (changeset, key_entry->gsettings_key, accel_name);
684 		g_free (accel_name);
685 	}
686 }
687 
688 static gboolean
sync_idle_cb(gpointer data)689 sync_idle_cb (gpointer data)
690 {
691 	GSettings *changeset;
692 
693 	_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
694 	                       "GSettings sync handler\n");
695 
696 	sync_idle_id = 0;
697 
698 	changeset = g_settings_new (CONF_KEYS_SCHEMA);
699 	g_settings_delay (changeset);
700 
701 	g_hash_table_foreach (gsettings_key_to_entry, (GHFunc) add_key_entry_to_changeset, changeset);
702 	g_settings_apply(changeset);
703 
704 	g_object_unref (changeset);
705 
706 	return FALSE;
707 }
708 
709 /* We have the same KeyEntry* in both columns;
710  * we only have two columns because we want to be able
711  * to sort by either one of them.
712  */
713 
714 static void
accel_set_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)715 accel_set_func (GtkTreeViewColumn *tree_column,
716                 GtkCellRenderer   *cell,
717                 GtkTreeModel      *model,
718                 GtkTreeIter       *iter,
719                 gpointer           data)
720 {
721 	KeyEntry *ke;
722 
723 	gtk_tree_model_get (model, iter,
724 	                    KEYVAL_COLUMN, &ke,
725 	                    -1);
726 
727 	if (ke == NULL)
728 		/* This is a title row */
729 		g_object_set (cell,
730 		              "visible", FALSE,
731 		              NULL);
732 	else
733 		g_object_set (cell,
734 		              "visible", TRUE,
735 		              "sensitive", ke->accel_path_unlocked,
736 		              "editable", ke->accel_path_unlocked,
737 		              "accel-key", ke->gsettings_keyval,
738 		              "accel-mods", ke->gsettings_mask,
739 		              NULL);
740 }
741 
742 static int
accel_compare_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)743 accel_compare_func (GtkTreeModel *model,
744                     GtkTreeIter  *a,
745                     GtkTreeIter  *b,
746                     gpointer      user_data)
747 {
748 	KeyEntry *ke_a;
749 	KeyEntry *ke_b;
750 	char *name_a;
751 	char *name_b;
752 	int result;
753 
754 	gtk_tree_model_get (model, a,
755 	                    KEYVAL_COLUMN, &ke_a,
756 	                    -1);
757 	if (ke_a == NULL)
758 	{
759 		gtk_tree_model_get (model, a,
760 		                    ACTION_COLUMN, &name_a,
761 		                    -1);
762 	}
763 	else
764 	{
765 		name_a = binding_display_name (ke_a->gsettings_keyval,
766 		                               ke_a->gsettings_mask);
767 	}
768 
769 	gtk_tree_model_get (model, b,
770 	                    KEYVAL_COLUMN, &ke_b,
771 	                    -1);
772 	if (ke_b == NULL)
773 	{
774 		gtk_tree_model_get (model, b,
775 		                    ACTION_COLUMN, &name_b,
776 		                    -1);
777 	}
778 	else
779 	{
780 		name_b = binding_display_name (ke_b->gsettings_keyval,
781 		                               ke_b->gsettings_mask);
782 	}
783 
784 	result = g_utf8_collate (name_a, name_b);
785 
786 	g_free (name_a);
787 	g_free (name_b);
788 
789 	return result;
790 }
791 
792 static void
treeview_accel_changed_cb(GtkAccelGroup * accel_group,guint keyval,GdkModifierType modifier,GClosure * accel_closure,GtkTreeModel * model)793 treeview_accel_changed_cb (GtkAccelGroup  *accel_group,
794                            guint keyval,
795                            GdkModifierType modifier,
796                            GClosure *accel_closure,
797                            GtkTreeModel *model)
798 {
799 	gtk_tree_model_foreach (model, update_model_foreach, accel_closure->data);
800 }
801 
802 static void
accel_edited_callback(GtkCellRendererAccel * cell,gchar * path_string,guint keyval,GdkModifierType mask,guint hardware_keycode,GtkTreeView * view)803 accel_edited_callback (GtkCellRendererAccel *cell,
804                        gchar                *path_string,
805                        guint                 keyval,
806                        GdkModifierType       mask,
807                        guint                 hardware_keycode,
808                        GtkTreeView          *view)
809 {
810 	GtkTreeModel *model;
811 	GtkTreePath *path;
812 	GtkTreeIter iter;
813 	KeyEntry *ke;
814 	GtkAccelGroupEntry *entries;
815 	guint n_entries;
816 	char *str;
817 
818 	model = gtk_tree_view_get_model (view);
819 
820 	path = gtk_tree_path_new_from_string (path_string);
821 	if (!path)
822 		return;
823 
824 	if (!gtk_tree_model_get_iter (model, &iter, path))
825 	{
826 		gtk_tree_path_free (path);
827 		return;
828 	}
829 	gtk_tree_path_free (path);
830 
831 	gtk_tree_model_get (model, &iter, KEYVAL_COLUMN, &ke, -1);
832 
833 	/* sanity check */
834 	if (ke == NULL)
835 		return;
836 
837 	/* Check if we already have an entry using this accel */
838 	entries = gtk_accel_group_query (notification_group, keyval, mask, &n_entries);
839 	if (n_entries > 0)
840 	{
841 		if (entries[0].accel_path_quark != g_quark_from_string (ke->accel_path))
842 		{
843 			GtkWidget *dialog;
844 			char *name;
845 			KeyEntry *other_key;
846 
847 			name = gtk_accelerator_get_label (keyval, mask);
848 			other_key = entries[0].closure->data;
849 			g_assert (other_key);
850 
851 			dialog =
852 			    gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
853 			                            GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
854 			                            GTK_MESSAGE_WARNING,
855 			                            GTK_BUTTONS_OK,
856 			                            _("The shortcut key “%s” is already bound to the “%s” action"),
857 			                            name,
858 
859 other_key->user_visible_name ? _(other_key->user_visible_name) : other_key->gsettings_key);
860 			g_free (name);
861 
862 			g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
863 			gtk_window_present (GTK_WINDOW (dialog));
864 		}
865 
866 		return;
867 	}
868 
869 	str = binding_name (keyval, mask);
870 
871 	_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
872 	                       "Edited path %s keyval %s, setting GSettings to %s\n",
873 	                       ke->accel_path,
874 	                       gdk_keyval_name (keyval) ? gdk_keyval_name (keyval) : "null",
875 	                       str);
876 #ifdef MATE_ENABLE_DEBUG
877 	_TERMINAL_DEBUG_IF (TERMINAL_DEBUG_ACCELS)
878 	{
879 		GtkAccelKey old_key;
880 
881 		if (gtk_accel_map_lookup_entry (ke->accel_path, &old_key))
882 		{
883 			_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
884 			                       "  Old entry of path %s is keyval %s mask %x\n",
885 			                       ke->accel_path, gdk_keyval_name (old_key.accel_key), old_key.accel_mods);
886 		}
887 		else
888 		{
889 			_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
890 			                       "  Failed to look up the old entry of path %s\n",
891 			                       ke->accel_path);
892 		}
893 	}
894 #endif
895 
896 	g_settings_set_string (settings_keybindings,
897 	                       ke->gsettings_key,
898 	                       str);
899 	g_free (str);
900 }
901 
902 static void
accel_cleared_callback(GtkCellRendererAccel * cell,gchar * path_string,GtkTreeView * view)903 accel_cleared_callback (GtkCellRendererAccel *cell,
904                         gchar                *path_string,
905                         GtkTreeView          *view)
906 {
907 	GtkTreeModel *model;
908 	GtkTreePath *path;
909 	GtkTreeIter iter;
910 	KeyEntry *ke;
911 	char *str;
912 
913 	model = gtk_tree_view_get_model (view);
914 
915 	path = gtk_tree_path_new_from_string (path_string);
916 	if (!path)
917 		return;
918 
919 	if (!gtk_tree_model_get_iter (model, &iter, path))
920 	{
921 		gtk_tree_path_free (path);
922 		return;
923 	}
924 	gtk_tree_path_free (path);
925 
926 	gtk_tree_model_get (model, &iter, KEYVAL_COLUMN, &ke, -1);
927 
928 	/* sanity check */
929 	if (ke == NULL)
930 		return;
931 
932 	ke->gsettings_keyval = 0;
933 	ke->gsettings_mask = 0;
934 	ke->needs_gsettings_sync = TRUE;
935 
936 	str = binding_name (0, 0);
937 
938 	_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
939 	                       "Cleared keybinding for GSettings %s",
940 	                       ke->gsettings_key);
941 
942 	g_settings_set_string (settings_keybindings,
943 	                       ke->gsettings_key,
944 	                       str);
945 	g_free (str);
946 }
947 
948 static void
edit_keys_dialog_destroy_cb(GtkWidget * widget,gpointer user_data)949 edit_keys_dialog_destroy_cb (GtkWidget *widget,
950                              gpointer user_data)
951 {
952 	g_signal_handlers_disconnect_by_func (notification_group, G_CALLBACK (treeview_accel_changed_cb), user_data);
953 	edit_keys_dialog = NULL;
954 	edit_keys_store = NULL;
955 }
956 
957 static void
edit_keys_dialog_response_cb(GtkWidget * editor,int response,gpointer use_data)958 edit_keys_dialog_response_cb (GtkWidget *editor,
959                               int response,
960                               gpointer use_data)
961 {
962 	if (response == GTK_RESPONSE_HELP)
963 	{
964 		terminal_util_show_help ("mate-terminal-shortcuts", GTK_WINDOW (editor));
965 		return;
966 	}
967 
968 	gtk_widget_destroy (editor);
969 }
970 
971 #ifdef MATE_ENABLE_DEBUG
972 static void
row_changed(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)973 row_changed (GtkTreeModel *tree_model,
974              GtkTreePath  *path,
975              GtkTreeIter  *iter,
976              gpointer      user_data)
977 {
978 	_terminal_debug_print (TERMINAL_DEBUG_ACCELS,
979 	                       "ROW-CHANGED [%s]\n", gtk_tree_path_to_string (path) /* leak */);
980 }
981 #endif
982 
983 void
terminal_edit_keys_dialog_show(GtkWindow * transient_parent)984 terminal_edit_keys_dialog_show (GtkWindow *transient_parent)
985 {
986 	TerminalApp *app;
987 	GtkWidget *dialog, *tree_view, *disable_mnemonics_button, *disable_menu_accel_button;
988 	GtkTreeViewColumn *column;
989 	GtkCellRenderer *cell_renderer;
990 	GtkTreeStore *tree;
991 	guint i;
992 
993 	if (edit_keys_dialog != NULL)
994 		goto done;
995 
996 	if (!terminal_util_load_builder_resource (TERMINAL_RESOURCES_PATH_PREFIX G_DIR_SEPARATOR_S "ui/keybinding-editor.ui",
997 	                                      "keybindings-dialog", &dialog,
998 	                                      "disable-mnemonics-checkbutton", &disable_mnemonics_button,
999 	                                      "disable-menu-accel-checkbutton", &disable_menu_accel_button,
1000 	                                      "accelerators-treeview", &tree_view,
1001 	                                      NULL))
1002 		return;
1003 
1004 	app = terminal_app_get ();
1005 	terminal_util_bind_object_property_to_widget (G_OBJECT (app), TERMINAL_APP_ENABLE_MNEMONICS,
1006 	        disable_mnemonics_button, 0);
1007 	terminal_util_bind_object_property_to_widget (G_OBJECT (app), TERMINAL_APP_ENABLE_MENU_BAR_ACCEL,
1008 	        disable_menu_accel_button, 0);
1009 
1010 	/* Column 1 */
1011 	cell_renderer = gtk_cell_renderer_text_new ();
1012 	column = gtk_tree_view_column_new_with_attributes (_("_Action"),
1013 	         cell_renderer,
1014 	         "text", ACTION_COLUMN,
1015 	         NULL);
1016 	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
1017 	gtk_tree_view_column_set_sort_column_id (column, ACTION_COLUMN);
1018 
1019 	/* Column 2 */
1020 	cell_renderer = gtk_cell_renderer_accel_new ();
1021 	g_object_set (cell_renderer,
1022 	              "editable", TRUE,
1023 	              "accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_GTK,
1024 	              NULL);
1025 	g_signal_connect (cell_renderer, "accel-edited",
1026 	                  G_CALLBACK (accel_edited_callback), tree_view);
1027 	g_signal_connect (cell_renderer, "accel-cleared",
1028 	                  G_CALLBACK (accel_cleared_callback), tree_view);
1029 
1030 	column = gtk_tree_view_column_new ();
1031 	gtk_tree_view_column_set_title (column, _("Shortcut _Key"));
1032 	gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
1033 	gtk_tree_view_column_set_cell_data_func (column, cell_renderer, accel_set_func, NULL, NULL);
1034 	gtk_tree_view_column_set_sort_column_id (column, KEYVAL_COLUMN);
1035 	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
1036 
1037 	/* Add the data */
1038 
1039 	tree = edit_keys_store = gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER);
1040 
1041 #ifdef MATE_ENABLE_DEBUG
1042 	_TERMINAL_DEBUG_IF (TERMINAL_DEBUG_ACCELS)
1043 	g_signal_connect (tree, "row-changed", G_CALLBACK (row_changed), NULL);
1044 #endif
1045 
1046 	for (i = 0; i < G_N_ELEMENTS (all_entries); ++i)
1047 	{
1048 		GtkTreeIter parent_iter;
1049 		guint j;
1050 
1051 		gtk_tree_store_append (tree, &parent_iter, NULL);
1052 		gtk_tree_store_set (tree, &parent_iter,
1053 		                    ACTION_COLUMN, _(all_entries[i].user_visible_name),
1054 		                    -1);
1055 
1056 		for (j = 0; j < all_entries[i].n_elements; ++j)
1057 		{
1058 			KeyEntry *key_entry = &(all_entries[i].key_entry[j]);
1059 			GtkTreeIter iter;
1060 
1061 			gtk_tree_store_insert_with_values (tree, &iter, &parent_iter, -1,
1062 			                                   ACTION_COLUMN, _(key_entry->user_visible_name),
1063 			                                   KEYVAL_COLUMN, key_entry,
1064 			                                   -1);
1065 		}
1066 	}
1067 
1068 	gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (tree),
1069 	                                 KEYVAL_COLUMN, accel_compare_func,
1070 	                                 NULL, NULL);
1071 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (tree), ACTION_COLUMN,
1072 	                                      GTK_SORT_ASCENDING);
1073 
1074 	gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (tree));
1075 	g_object_unref (tree);
1076 
1077 	gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view));
1078 
1079 	g_signal_connect (notification_group, "accel-changed",
1080 	                  G_CALLBACK (treeview_accel_changed_cb), tree);
1081 
1082 	edit_keys_dialog = dialog;
1083 	g_signal_connect (dialog, "destroy",
1084 	                  G_CALLBACK (edit_keys_dialog_destroy_cb), tree);
1085 	g_signal_connect (dialog, "response",
1086 	                  G_CALLBACK (edit_keys_dialog_response_cb),
1087 	                  NULL);
1088 	gtk_window_set_default_size (GTK_WINDOW (dialog), -1, 350);
1089 
1090 done:
1091 	gtk_window_set_transient_for (GTK_WINDOW (edit_keys_dialog), transient_parent);
1092 	gtk_window_present (GTK_WINDOW (edit_keys_dialog));
1093 }
1094