GCC Code Coverage Report


Directory: ./
File: panels/keyboard/keyboard-shortcuts.c
Date: 2024-05-03 09:46:52
Exec Total Coverage
Lines: 14 210 6.7%
Functions: 1 10 10.0%
Branches: 6 198 3.0%

Line Branch Exec Source
1 /*
2 * Copyright (C) 2010 Intel, Inc
3 * Copyright (C) 2014 Red Hat, Inc
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authors: Thomas Wood <thomas.wood@intel.com>
19 * Rodrigo Moya <rodrigo@gnome.org>
20 * Christophe Fergeau <cfergeau@redhat.com>
21 */
22
23 #include <config.h>
24
25 #include <glib/gi18n.h>
26
27 #include "keyboard-shortcuts.h"
28
29 #define CUSTOM_KEYS_BASENAME "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings"
30
31 static char *
32 replace_pictures_folder (const char *description)
33 {
34 g_autoptr(GRegex) pictures_regex = NULL;
35 const char *path;
36 g_autofree gchar *dirname = NULL;
37 g_autofree gchar *ret = NULL;
38
39 if (description == NULL)
40 return NULL;
41
42 if (strstr (description, "$PICTURES") == NULL)
43 return g_strdup (description);
44
45 pictures_regex = g_regex_new ("\\$PICTURES", 0, 0, NULL);
46 path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
47 dirname = g_filename_display_basename (path);
48 ret = g_regex_replace (pictures_regex, description, -1,
49 0, dirname, 0, NULL);
50
51 if (ret == NULL)
52 return g_strdup (description);
53
54 return g_steal_pointer (&ret);
55 }
56
57 static void
58 parse_start_tag (GMarkupParseContext *ctx,
59 const gchar *element_name,
60 const gchar **attr_names,
61 const gchar **attr_values,
62 gpointer user_data,
63 GError **error)
64 {
65 KeyList *keylist = (KeyList *) user_data;
66 KeyListEntry key;
67 const char *name, *schema, *description, *package, *context, *orig_description, *reverse_entry;
68 gboolean is_reversed, hidden;
69
70 name = NULL;
71 schema = NULL;
72 package = NULL;
73 context = NULL;
74
75 /* The top-level element, names the section in the tree */
76 if (g_str_equal (element_name, "KeyListEntries"))
77 {
78 const char *wm_name = NULL;
79 const char *group = NULL;
80
81 while (*attr_names && *attr_values)
82 {
83 if (g_str_equal (*attr_names, "name"))
84 {
85 if (**attr_values)
86 name = *attr_values;
87 } else if (g_str_equal (*attr_names, "group")) {
88 if (**attr_values)
89 group = *attr_values;
90 } else if (g_str_equal (*attr_names, "wm_name")) {
91 if (**attr_values)
92 wm_name = *attr_values;
93 } else if (g_str_equal (*attr_names, "schema")) {
94 if (**attr_values)
95 schema = *attr_values;
96 } else if (g_str_equal (*attr_names, "package")) {
97 if (**attr_values)
98 package = *attr_values;
99 }
100 ++attr_names;
101 ++attr_values;
102 }
103
104 if (name)
105 {
106 if (keylist->name)
107 g_warning ("Duplicate section name");
108 g_free (keylist->name);
109 keylist->name = g_strdup (name);
110 }
111 if (wm_name)
112 {
113 if (keylist->wm_name)
114 g_warning ("Duplicate window manager name");
115 g_free (keylist->wm_name);
116 keylist->wm_name = g_strdup (wm_name);
117 }
118 if (package)
119 {
120 if (keylist->package)
121 g_warning ("Duplicate gettext package name");
122 g_free (keylist->package);
123 keylist->package = g_strdup (package);
124 bind_textdomain_codeset (keylist->package, "UTF-8");
125 }
126 if (group)
127 {
128 if (keylist->group)
129 g_warning ("Duplicate group");
130 g_free (keylist->group);
131 keylist->group = g_strdup (group);
132 }
133 if (schema)
134 {
135 if (keylist->schema)
136 g_warning ("Duplicate schema");
137 g_free (keylist->schema);
138 keylist->schema = g_strdup (schema);
139 }
140 return;
141 }
142
143 if (!g_str_equal (element_name, "KeyListEntry")
144 || attr_names == NULL
145 || attr_values == NULL)
146 return;
147
148 schema = NULL;
149 description = NULL;
150 context = NULL;
151 orig_description = NULL;
152 reverse_entry = NULL;
153 is_reversed = FALSE;
154 hidden = FALSE;
155
156 while (*attr_names && *attr_values)
157 {
158 if (g_str_equal (*attr_names, "name"))
159 {
160 /* skip if empty */
161 if (**attr_values)
162 name = *attr_values;
163 } else if (g_str_equal (*attr_names, "schema")) {
164 if (**attr_values) {
165 schema = *attr_values;
166 }
167 } else if (g_str_equal (*attr_names, "description")) {
168 if (**attr_values)
169 orig_description = *attr_values;
170 } else if (g_str_equal (*attr_names, "msgctxt")) {
171 if (**attr_values)
172 context = *attr_values;
173 } else if (g_str_equal (*attr_names, "reverse-entry")) {
174 if (**attr_values)
175 reverse_entry = *attr_values;
176 } else if (g_str_equal (*attr_names, "is-reversed")) {
177 if (g_str_equal (*attr_values, "true"))
178 is_reversed = TRUE;
179 } else if (g_str_equal (*attr_names, "hidden")) {
180 if (g_str_equal (*attr_values, "true"))
181 hidden = TRUE;
182 }
183
184 ++attr_names;
185 ++attr_values;
186 }
187
188 if (name == NULL)
189 return;
190
191 if (schema == NULL &&
192 keylist->schema == NULL) {
193 g_debug ("Ignored GConf keyboard shortcut '%s'", name);
194 return;
195 }
196
197 if (context != NULL)
198 description = g_dpgettext2 (keylist->package, context, orig_description);
199 else
200 description = dgettext (keylist->package, orig_description);
201
202 key.name = g_strdup (name);
203 key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS;
204 key.description = replace_pictures_folder (description);
205 key.schema = schema ? g_strdup (schema) : g_strdup (keylist->schema);
206 key.reverse_entry = g_strdup (reverse_entry);
207 key.is_reversed = is_reversed;
208 key.hidden = hidden;
209 g_array_append_val (keylist->entries, key);
210 }
211
212 static const guint forbidden_keyvals[] = {
213 /* Navigation keys */
214 GDK_KEY_Home,
215 GDK_KEY_Left,
216 GDK_KEY_Up,
217 GDK_KEY_Right,
218 GDK_KEY_Down,
219 GDK_KEY_Page_Up,
220 GDK_KEY_Page_Down,
221 GDK_KEY_End,
222 GDK_KEY_Tab,
223
224 /* Return */
225 GDK_KEY_KP_Enter,
226 GDK_KEY_Return,
227
228 GDK_KEY_Mode_switch
229 };
230
231 static gboolean
232 keyval_is_forbidden (guint keyval)
233 {
234 guint i;
235
236 for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) {
237 if (keyval == forbidden_keyvals[i])
238 return TRUE;
239 }
240
241 return FALSE;
242 }
243
244 gboolean
245 is_valid_binding (const CcKeyCombo *combo)
246 {
247 if ((combo->mask == 0 || combo->mask == GDK_SHIFT_MASK) && combo->keycode != 0)
248 {
249 guint keyval = combo->keyval;
250
251 if ((keyval >= GDK_KEY_a && keyval <= GDK_KEY_z)
252 || (keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z)
253 || (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9)
254 || (keyval >= GDK_KEY_kana_fullstop && keyval <= GDK_KEY_semivoicedsound)
255 || (keyval >= GDK_KEY_Arabic_comma && keyval <= GDK_KEY_Arabic_sukun)
256 || (keyval >= GDK_KEY_Serbian_dje && keyval <= GDK_KEY_Cyrillic_HARDSIGN)
257 || (keyval >= GDK_KEY_Greek_ALPHAaccent && keyval <= GDK_KEY_Greek_omega)
258 || (keyval >= GDK_KEY_hebrew_doublelowline && keyval <= GDK_KEY_hebrew_taf)
259 || (keyval >= GDK_KEY_Thai_kokai && keyval <= GDK_KEY_Thai_lekkao)
260 || (keyval >= GDK_KEY_Hangul_Kiyeog && keyval <= GDK_KEY_Hangul_J_YeorinHieuh)
261 || (keyval == GDK_KEY_space && combo->mask == 0)
262 || keyval_is_forbidden (keyval)) {
263 return FALSE;
264 }
265 }
266 return TRUE;
267 }
268
269 gboolean
270 is_empty_binding (const CcKeyCombo *combo)
271 {
272 if (combo->keyval == 0 &&
273 combo->mask == 0 &&
274 combo->keycode == 0)
275 return TRUE;
276 return FALSE;
277 }
278
279 gboolean
280 is_valid_accel (const CcKeyCombo *combo)
281 {
282 /* Unlike gtk_accelerator_valid(), we want to allow Tab when combined
283 * with some modifiers (Alt+Tab and friends)
284 */
285 return gtk_accelerator_valid (combo->keyval, combo->mask) ||
286 (combo->keyval == GDK_KEY_Tab && combo->mask != 0);
287 }
288
289 gchar*
290 find_free_settings_path (GSettings *settings)
291 {
292 g_auto(GStrv) used_names = NULL;
293 g_autofree gchar *dir = NULL;
294 int i, num, n_names;
295
296 used_names = g_settings_get_strv (settings, "custom-keybindings");
297 n_names = g_strv_length (used_names);
298
299 for (num = 0; dir == NULL; num++)
300 {
301 g_autofree gchar *tmp = NULL;
302 gboolean found = FALSE;
303
304 tmp = g_strdup_printf ("%s/custom%d/", CUSTOM_KEYS_BASENAME, num);
305 for (i = 0; i < n_names && !found; i++)
306 found = strcmp (used_names[i], tmp) == 0;
307
308 if (!found)
309 dir = g_steal_pointer (&tmp);
310 }
311
312 return g_steal_pointer (&dir);
313 }
314
315 KeyList*
316 parse_keylist_from_file (const gchar *path)
317 {
318 KeyList *keylist;
319 g_autoptr(GError) err = NULL;
320 g_autofree gchar *buf = NULL;
321 gsize buf_len;
322 guint i;
323
324 g_autoptr(GMarkupParseContext) ctx = NULL;
325 GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL };
326
327 /* Parse file */
328 if (!g_file_get_contents (path, &buf, &buf_len, &err))
329 return NULL;
330
331 keylist = g_new0 (KeyList, 1);
332 keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
333 ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL);
334
335 if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err))
336 {
337 g_warning ("Failed to parse '%s': '%s'", path, err->message);
338 g_free (keylist->name);
339 g_free (keylist->package);
340 g_free (keylist->wm_name);
341
342 for (i = 0; i < keylist->entries->len; i++)
343 g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name);
344
345 g_array_free (keylist->entries, TRUE);
346 g_free (keylist);
347 return NULL;
348 }
349
350 return keylist;
351 }
352
353 /*
354 * Stolen from GtkCellRendererAccel:
355 * https://git.gnome.org/browse/gtk+/tree/gtk/gtkcellrendereraccel.c#n261
356 */
357 gchar*
358 convert_keysym_state_to_string (const CcKeyCombo *combo)
359 {
360 gchar *name;
361
362 if (combo->keyval == 0 && combo->keycode == 0)
363 {
364 /* This label is displayed in a treeview cell displaying
365 * a disabled accelerator key combination.
366 */
367 name = g_strdup (_("Disabled"));
368 }
369 else
370 {
371 name = gtk_accelerator_get_label_with_keycode (NULL, combo->keyval, combo->keycode, combo->mask);
372
373 if (name == NULL)
374 name = gtk_accelerator_name_with_keycode (NULL, combo->keyval, combo->keycode, combo->mask);
375 }
376
377 return name;
378 }
379
380 /* This adjusts the keyval and modifiers such that it matches how
381 * gnome-shell detects shortcuts, which works as follows:
382 * First for the non-modifier key, the keycode that generates this
383 * keyval at the lowest shift level is determined, which might be a
384 * level > 0, such as for numbers in the num-row in AZERTY.
385 * Next it checks if all the specified modifiers were pressed.
386 */
387 void
388 24 normalize_keyval_and_mask (guint keycode,
389 GdkModifierType mask,
390 guint group,
391 guint *out_keyval,
392 GdkModifierType *out_mask)
393 {
394 guint unmodified_keyval;
395 guint shifted_keyval;
396 GdkModifierType explicit_modifiers;
397 GdkModifierType used_modifiers;
398
399 /* We want shift to always be included as explicit modifier for
400 * gnome-shell shortcuts. That's because users usually think of
401 * shortcuts as including the shift key rather than being defined
402 * for the shifted keyval.
403 * This helps with num-row keys which have different keyvals on
404 * different layouts for example, but also with keys that have
405 * explicit key codes at shift level 0, that gnome-shell would prefer
406 * over shifted ones, such the DOLLAR key.
407 */
408 24 explicit_modifiers = gtk_accelerator_get_default_mod_mask () | GDK_SHIFT_MASK;
409 24 used_modifiers = mask & explicit_modifiers;
410
411 /* Find the base keyval of the pressed key without the explicit
412 * modifiers. */
413 24 gdk_display_translate_key (gdk_display_get_default (),
414 keycode,
415 24 mask & ~explicit_modifiers,
416 group,
417 &unmodified_keyval,
418 NULL,
419 NULL,
420 NULL);
421
422 /* Normalize num-row keys to the number value. This allows these
423 * shortcuts to work when switching between AZERTY and layouts where
424 * the numbers are at shift level 0. */
425 24 gdk_display_translate_key (gdk_display_get_default (),
426 keycode,
427 24 GDK_SHIFT_MASK | (mask & ~explicit_modifiers),
428 group,
429 &shifted_keyval,
430 NULL,
431 NULL,
432 NULL);
433
434
4/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 13 times.
24 if (shifted_keyval >= GDK_KEY_0 && shifted_keyval <= GDK_KEY_9)
435 2 unmodified_keyval = shifted_keyval;
436
437 /* Normalise <Tab> */
438
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (unmodified_keyval == GDK_KEY_ISO_Left_Tab)
439 unmodified_keyval = GDK_KEY_Tab;
440
441
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
24 if (unmodified_keyval == GDK_KEY_Sys_Req && (used_modifiers & GDK_ALT_MASK) != 0)
442 {
443 /* HACK: we don't want to use SysRq as a keybinding (but we do
444 * want Alt+Print), so we avoid translation from Alt+Print to SysRq */
445 unmodified_keyval = GDK_KEY_Print;
446 }
447
448 24 *out_keyval = unmodified_keyval;
449 24 *out_mask = used_modifiers;
450 24 }
451