GCC Code Coverage Report


Directory: ./
File: panels/keyboard/cc-keyboard-manager.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 375 0.0%
Functions: 0 29 0.0%
Branches: 0 230 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (C) 2010 Intel, Inc
3 * Copyright (C) 2016 Endless, 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 * Author: Thomas Wood <thomas.wood@intel.com>
19 * Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
20 *
21 */
22
23 #include <glib/gi18n.h>
24
25 #include "cc-keyboard-manager.h"
26 #include "keyboard-shortcuts.h"
27
28 #include <gdk/gdk.h>
29 #ifdef GDK_WINDOWING_X11
30 #include <gdk/x11/gdkx.h>
31 #include <X11/Xatom.h>
32 #endif
33
34 #define BINDINGS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys"
35 #define CUSTOM_SHORTCUTS_ID "custom"
36
37 struct _CcKeyboardManager
38 {
39 GObject parent;
40
41 GtkListStore *sections_store;
42
43 GHashTable *kb_system_sections;
44 GHashTable *kb_apps_sections;
45 GHashTable *kb_user_sections;
46
47 GSettings *binding_settings;
48 };
49
50 G_DEFINE_TYPE (CcKeyboardManager, cc_keyboard_manager, G_TYPE_OBJECT)
51
52 enum
53 {
54 SHORTCUT_ADDED,
55 SHORTCUT_CHANGED,
56 SHORTCUT_REMOVED,
57 SHORTCUTS_LOADED,
58 LAST_SIGNAL
59 };
60
61 static guint signals[LAST_SIGNAL] = { 0, };
62
63 /*
64 * Auxiliary methods
65 */
66 static void
67 free_key_array (GPtrArray *keys)
68 {
69 if (keys != NULL)
70 {
71 gint i;
72
73 for (i = 0; i < keys->len; i++)
74 {
75 CcKeyboardItem *item;
76
77 item = g_ptr_array_index (keys, i);
78
79 g_object_unref (item);
80 }
81
82 g_ptr_array_free (keys, TRUE);
83 }
84 }
85
86 static gboolean
87 find_conflict (CcUniquenessData *data,
88 CcKeyboardItem *item)
89 {
90 GList *l;
91 gboolean is_conflict = FALSE;
92
93 if (data->orig_item && cc_keyboard_item_equal (data->orig_item, item))
94 return FALSE;
95
96 for (l = cc_keyboard_item_get_key_combos (item); l; l = l->next)
97 {
98 CcKeyCombo *combo = l->data;
99
100 if (data->new_mask != combo->mask)
101 continue;
102
103 if (data->new_keyval != 0)
104 is_conflict = data->new_keyval == combo->keyval;
105 else
106 is_conflict = combo->keyval == 0 && data->new_keycode == combo->keycode;
107
108 if (is_conflict)
109 break;
110 }
111
112 if (is_conflict)
113 data->conflict_item = item;
114
115 return is_conflict;
116 }
117
118 static gboolean
119 compare_keys_for_uniqueness (CcKeyboardItem *current_item,
120 CcUniquenessData *data)
121 {
122 CcKeyboardItem *reverse_item;
123
124 /* No conflict for: blanks or ourselves */
125 if (!current_item || data->orig_item == current_item)
126 return FALSE;
127
128 reverse_item = cc_keyboard_item_get_reverse_item (current_item);
129
130 /* When the current item is the reversed shortcut of a main item, simply ignore it */
131 if (reverse_item && cc_keyboard_item_is_hidden (current_item))
132 return FALSE;
133
134 if (find_conflict (data, current_item))
135 return TRUE;
136
137 /* Also check for the reverse item if any */
138 if (reverse_item && find_conflict (data, reverse_item))
139 return TRUE;
140
141 return FALSE;
142 }
143
144 static gboolean
145 check_for_uniqueness (gpointer key,
146 GPtrArray *keys_array,
147 CcUniquenessData *data)
148 {
149 guint i;
150
151 for (i = 0; i < keys_array->len; i++)
152 {
153 CcKeyboardItem *item;
154
155 item = keys_array->pdata[i];
156
157 if (compare_keys_for_uniqueness (item, data))
158 return TRUE;
159 }
160
161 return FALSE;
162 }
163
164
165 static GHashTable*
166 get_hash_for_group (CcKeyboardManager *self,
167 BindingGroupType group)
168 {
169 GHashTable *hash;
170
171 switch (group)
172 {
173 case BINDING_GROUP_SYSTEM:
174 hash = self->kb_system_sections;
175 break;
176 case BINDING_GROUP_APPS:
177 hash = self->kb_apps_sections;
178 break;
179 case BINDING_GROUP_USER:
180 hash = self->kb_user_sections;
181 break;
182 default:
183 hash = NULL;
184 }
185
186 return hash;
187 }
188
189 static gboolean
190 have_key_for_group (CcKeyboardManager *self,
191 int group,
192 const gchar *name)
193 {
194 GHashTableIter iter;
195 GPtrArray *keys;
196 gint i;
197
198 g_hash_table_iter_init (&iter, get_hash_for_group (self, group));
199 while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &keys))
200 {
201 for (i = 0; i < keys->len; i++)
202 {
203 CcKeyboardItem *item = g_ptr_array_index (keys, i);
204
205 if (cc_keyboard_item_get_item_type (item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS &&
206 g_strcmp0 (name, cc_keyboard_item_get_key (item)) == 0)
207 {
208 return TRUE;
209 }
210 }
211 }
212
213 return FALSE;
214 }
215
216 static void
217 add_shortcuts (CcKeyboardManager *self)
218 {
219 GtkTreeModel *sections_model;
220 GtkTreeIter sections_iter;
221 gboolean can_continue;
222
223 sections_model = GTK_TREE_MODEL (self->sections_store);
224 can_continue = gtk_tree_model_get_iter_first (sections_model, &sections_iter);
225
226 while (can_continue)
227 {
228 BindingGroupType group;
229 GPtrArray *keys;
230 g_autofree gchar *id = NULL;
231 g_autofree gchar *title = NULL;
232 gint i;
233
234 gtk_tree_model_get (sections_model,
235 &sections_iter,
236 SECTION_DESCRIPTION_COLUMN, &title,
237 SECTION_GROUP_COLUMN, &group,
238 SECTION_ID_COLUMN, &id,
239 -1);
240
241 /* Ignore separators */
242 if (group == BINDING_GROUP_SEPARATOR)
243 {
244 can_continue = gtk_tree_model_iter_next (sections_model, &sections_iter);
245 continue;
246 }
247
248 keys = g_hash_table_lookup (get_hash_for_group (self, group), id);
249
250 for (i = 0; i < keys->len; i++)
251 {
252 CcKeyboardItem *item = g_ptr_array_index (keys, i);
253
254 if (!cc_keyboard_item_is_hidden (item))
255 {
256 g_signal_emit (self, signals[SHORTCUT_ADDED],
257 0,
258 item,
259 id,
260 title);
261 }
262 }
263
264 can_continue = gtk_tree_model_iter_next (sections_model, &sections_iter);
265 }
266
267 g_signal_emit (self, signals[SHORTCUTS_LOADED], 0);
268 }
269
270 static void
271 append_section (CcKeyboardManager *self,
272 const gchar *title,
273 const gchar *id,
274 BindingGroupType group,
275 const KeyListEntry *keys_list)
276 {
277 GtkTreeIter iter;
278 GHashTable *reverse_items;
279 GHashTable *hash;
280 GPtrArray *keys_array;
281 gboolean is_new;
282 gint i;
283
284 hash = get_hash_for_group (self, group);
285
286 if (!hash)
287 return;
288
289 /* Add all CcKeyboardItems for this section */
290 is_new = FALSE;
291 keys_array = g_hash_table_lookup (hash, id);
292 if (keys_array == NULL)
293 {
294 keys_array = g_ptr_array_new ();
295 is_new = TRUE;
296 }
297
298 reverse_items = g_hash_table_new (g_str_hash, g_str_equal);
299
300 for (i = 0; keys_list != NULL && keys_list[i].name != NULL; i++)
301 {
302 CcKeyboardItem *item;
303 gboolean ret;
304
305 if (have_key_for_group (self, group, keys_list[i].name))
306 continue;
307
308 item = cc_keyboard_item_new (keys_list[i].type);
309
310 switch (keys_list[i].type)
311 {
312 case CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH:
313 ret = cc_keyboard_item_load_from_gsettings_path (item, keys_list[i].name, FALSE);
314 break;
315
316 case CC_KEYBOARD_ITEM_TYPE_GSETTINGS:
317 ret = cc_keyboard_item_load_from_gsettings (item,
318 keys_list[i].description,
319 keys_list[i].schema,
320 keys_list[i].name);
321 if (ret && keys_list[i].reverse_entry != NULL)
322 {
323 CcKeyboardItem *reverse_item;
324 reverse_item = g_hash_table_lookup (reverse_items,
325 keys_list[i].reverse_entry);
326 if (reverse_item != NULL)
327 {
328 cc_keyboard_item_add_reverse_item (item,
329 reverse_item,
330 keys_list[i].is_reversed);
331 }
332 else
333 {
334 g_hash_table_insert (reverse_items,
335 keys_list[i].name,
336 item);
337 }
338 }
339 break;
340
341 default:
342 g_assert_not_reached ();
343 }
344
345 if (ret == FALSE)
346 {
347 /* We don't actually want to popup a dialog - just skip this one */
348 g_object_unref (item);
349 continue;
350 }
351
352 cc_keyboard_item_set_hidden (item, keys_list[i].hidden);
353
354 g_ptr_array_add (keys_array, item);
355 }
356
357 g_hash_table_destroy (reverse_items);
358
359 /* Add the keys to the hash table */
360 if (is_new)
361 {
362 g_hash_table_insert (hash, g_strdup (id), keys_array);
363
364 /* Append the section to the left tree view */
365 gtk_list_store_append (GTK_LIST_STORE (self->sections_store), &iter);
366 gtk_list_store_set (GTK_LIST_STORE (self->sections_store),
367 &iter,
368 SECTION_DESCRIPTION_COLUMN, title,
369 SECTION_ID_COLUMN, id,
370 SECTION_GROUP_COLUMN, group,
371 -1);
372 }
373 }
374
375 static void
376 append_sections_from_file (CcKeyboardManager *self,
377 const gchar *path,
378 const char *datadir,
379 gchar **wm_keybindings)
380 {
381 KeyList *keylist;
382 KeyListEntry *keys;
383 KeyListEntry key = { 0, 0, 0, 0, 0, 0, 0 };
384 const char *title;
385 int group;
386 guint i;
387
388 keylist = parse_keylist_from_file (path);
389
390 if (keylist == NULL)
391 return;
392
393 #define const_strv(s) ((const gchar* const*) s)
394
395 /* If there's no keys to add, or the settings apply to a window manager
396 * that's not the one we're running */
397 if (keylist->entries->len == 0 ||
398 (keylist->wm_name != NULL && !g_strv_contains (const_strv (wm_keybindings), keylist->wm_name)) ||
399 keylist->name == NULL)
400 {
401 g_free (keylist->name);
402 g_free (keylist->package);
403 g_free (keylist->wm_name);
404 g_array_free (keylist->entries, TRUE);
405 g_free (keylist);
406 return;
407 }
408
409 #undef const_strv
410
411 /* Empty KeyListEntry to end the array */
412 key.name = NULL;
413 g_array_append_val (keylist->entries, key);
414
415 keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE);
416 if (keylist->package)
417 {
418 g_autofree gchar *localedir = NULL;
419
420 localedir = g_build_filename (datadir, "locale", NULL);
421 bindtextdomain (keylist->package, localedir);
422
423 title = dgettext (keylist->package, keylist->name);
424 } else {
425 title = _(keylist->name);
426 }
427
428 if (keylist->group && strcmp (keylist->group, "system") == 0)
429 group = BINDING_GROUP_SYSTEM;
430 else
431 group = BINDING_GROUP_APPS;
432
433 append_section (self, title, keylist->name, group, keys);
434
435 g_free (keylist->name);
436 g_free (keylist->package);
437 g_free (keylist->wm_name);
438 g_free (keylist->schema);
439 g_free (keylist->group);
440
441 for (i = 0; keys[i].name != NULL; i++)
442 {
443 KeyListEntry *entry = &keys[i];
444 g_free (entry->schema);
445 g_free (entry->description);
446 g_free (entry->name);
447 g_free (entry->reverse_entry);
448 }
449
450 g_free (keylist);
451 g_free (keys);
452 }
453
454 static void
455 append_sections_from_gsettings (CcKeyboardManager *self)
456 {
457 g_auto(GStrv) custom_paths = NULL;
458 GArray *entries;
459 KeyListEntry key = { 0, 0, 0, 0, 0, 0, 0 };
460 int i;
461
462 /* load custom shortcuts from GSettings */
463 entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
464
465 custom_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings");
466 for (i = 0; custom_paths[i]; i++)
467 {
468 key.name = g_strdup (custom_paths[i]);
469 if (!have_key_for_group (self, BINDING_GROUP_USER, key.name))
470 {
471 key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH;
472 g_array_append_val (entries, key);
473 }
474 else
475 g_free (key.name);
476 }
477
478 if (entries->len > 0)
479 {
480 KeyListEntry *keys;
481 int i;
482
483 /* Empty KeyListEntry to end the array */
484 key.name = NULL;
485 g_array_append_val (entries, key);
486
487 keys = (KeyListEntry *) entries->data;
488 append_section (self, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, keys);
489 for (i = 0; i < entries->len; ++i)
490 {
491 g_free (keys[i].name);
492 }
493 }
494 else
495 {
496 append_section (self, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, NULL);
497 }
498
499 g_array_free (entries, TRUE);
500 }
501
502 #ifdef GDK_WINDOWING_X11
503 static char *
504 get_window_manager_property (GdkDisplay *display,
505 Atom atom,
506 Window window)
507 {
508 Display *xdisplay;
509 Atom utf8_string;
510 int result;
511 Atom actual_type;
512 int actual_format;
513 unsigned long n_items;
514 unsigned long bytes_after;
515 unsigned char *prop;
516 char *value;
517
518 if (window == None)
519 return NULL;
520
521 xdisplay = gdk_x11_display_get_xdisplay (display);
522 utf8_string = XInternAtom (xdisplay, "UTF8_STRING", False);
523
524 gdk_x11_display_error_trap_push (display);
525
526 result = XGetWindowProperty (xdisplay,
527 window,
528 atom,
529 0,
530 G_MAXLONG,
531 False,
532 utf8_string,
533 &actual_type,
534 &actual_format,
535 &n_items,
536 &bytes_after,
537 &prop);
538
539 gdk_x11_display_error_trap_pop_ignored (display);
540
541 if (result != Success ||
542 actual_type != utf8_string ||
543 actual_format != 8 ||
544 n_items == 0)
545 {
546 XFree (prop);
547 return NULL;
548 }
549
550 value = g_strndup ((const char *) prop, n_items);
551 XFree (prop);
552
553 if (!g_utf8_validate (value, -1, NULL))
554 {
555 g_free (value);
556 return NULL;
557 }
558
559 return value;
560 }
561
562 static Window
563 get_wm_window (GdkDisplay *display)
564 {
565 Display *xdisplay;
566 Atom wm_check;
567 int result;
568 Atom actual_type;
569 int actual_format;
570 unsigned long n_items;
571 unsigned long bytes_after;
572 unsigned char *prop;
573 Window wm_window;
574
575 xdisplay = gdk_x11_display_get_xdisplay (display);
576 wm_check = XInternAtom (xdisplay, "_NET_SUPPORTING_WM_CHECK", False);
577
578 gdk_x11_display_error_trap_push (display);
579
580 result = XGetWindowProperty (xdisplay,
581 XDefaultRootWindow (xdisplay),
582 wm_check,
583 0,
584 G_MAXLONG,
585 False,
586 XA_WINDOW,
587 &actual_type,
588 &actual_format,
589 &n_items,
590 &bytes_after,
591 &prop);
592
593 gdk_x11_display_error_trap_pop_ignored (display);
594
595 if (result != Success ||
596 actual_type != XA_WINDOW ||
597 n_items == 0)
598 {
599 XFree (prop);
600 return None;
601 }
602
603 wm_window = *(Window *) prop;
604 XFree (prop);
605
606 return wm_window;
607 }
608 #endif
609
610 static GStrv
611 get_current_keybindings (void)
612 {
613 #ifdef GDK_WINDOWING_X11
614 GdkDisplay *display;
615 Display *xdisplay;
616 Atom keybindings_atom;
617 Window wm_window;
618 char *keybindings;
619 GStrv results;
620
621 display = gdk_display_get_default ();
622 if (!GDK_IS_X11_DISPLAY (display))
623 return NULL;
624
625 xdisplay = gdk_x11_display_get_xdisplay (display);
626 keybindings_atom = XInternAtom (xdisplay, "_GNOME_WM_KEYBINDINGS", False);
627
628 wm_window = get_wm_window (display);
629 keybindings = get_window_manager_property (display,
630 keybindings_atom,
631 wm_window);
632
633 if (keybindings != NULL)
634 {
635 GStrv p;
636
637 results = g_strsplit (keybindings, ",", -1);
638
639 for (p = results; p && *p; p++)
640 g_strstrip (*p);
641
642 g_free (keybindings);
643 }
644 else
645 {
646 Atom wm_atom;
647 char *wm_name;
648
649 wm_atom = XInternAtom (xdisplay, "_NET_WM_NAME", False);
650 wm_name = get_window_manager_property (display, wm_atom, wm_window);
651
652 results = g_new0 (char *, 2);
653 results[0] = wm_name ? wm_name : g_strdup ("Unknown");
654 }
655
656 return results;
657 #else
658 return NULL;
659 #endif
660 }
661
662 static void
663 reload_sections (CcKeyboardManager *self)
664 {
665 GHashTable *loaded_files;
666 GDir *dir;
667 gchar *default_wm_keybindings[] = { "Mutter", "GNOME Shell", NULL };
668 g_auto(GStrv) wm_keybindings = NULL;
669 const gchar * const * data_dirs;
670 guint i;
671
672 /* Clear previous models and hash tables */
673 gtk_list_store_clear (GTK_LIST_STORE (self->sections_store));
674
675 g_clear_pointer (&self->kb_system_sections, g_hash_table_destroy);
676 self->kb_system_sections = g_hash_table_new_full (g_str_hash,
677 g_str_equal,
678 g_free,
679 (GDestroyNotify) free_key_array);
680
681 g_clear_pointer (&self->kb_apps_sections, g_hash_table_destroy);
682 self->kb_apps_sections = g_hash_table_new_full (g_str_hash,
683 g_str_equal,
684 g_free,
685 (GDestroyNotify) free_key_array);
686
687 g_clear_pointer (&self->kb_user_sections, g_hash_table_destroy);
688 self->kb_user_sections = g_hash_table_new_full (g_str_hash,
689 g_str_equal,
690 g_free,
691 (GDestroyNotify) free_key_array);
692
693 /* Load WM keybindings */
694 wm_keybindings = get_current_keybindings ();
695
696 if (wm_keybindings == NULL)
697 wm_keybindings = g_strdupv (default_wm_keybindings);
698
699 loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
700
701 data_dirs = g_get_system_data_dirs ();
702 for (i = 0; data_dirs[i] != NULL; i++)
703 {
704 g_autofree gchar *dir_path = NULL;
705 const gchar *name;
706
707 dir_path = g_build_filename (data_dirs[i], "gnome-control-center", "keybindings", NULL);
708
709 dir = g_dir_open (dir_path, 0, NULL);
710 if (!dir)
711 continue;
712
713 for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir))
714 {
715 g_autofree gchar *path = NULL;
716
717 if (g_str_has_suffix (name, ".xml") == FALSE)
718 continue;
719
720 if (g_hash_table_lookup (loaded_files, name) != NULL)
721 {
722 g_debug ("Not loading %s, it was already loaded from another directory", name);
723 continue;
724 }
725
726 g_hash_table_insert (loaded_files, g_strdup (name), GINT_TO_POINTER (1));
727 path = g_build_filename (dir_path, name, NULL);
728 append_sections_from_file (self, path, data_dirs[i], wm_keybindings);
729 }
730
731 g_dir_close (dir);
732 }
733
734 g_hash_table_destroy (loaded_files);
735
736 /* Load custom keybindings */
737 append_sections_from_gsettings (self);
738 }
739
740 /*
741 * Callbacks
742 */
743 static void
744 cc_keyboard_manager_finalize (GObject *object)
745 {
746 CcKeyboardManager *self = (CcKeyboardManager *)object;
747
748 g_clear_pointer (&self->kb_system_sections, g_hash_table_destroy);
749 g_clear_pointer (&self->kb_apps_sections, g_hash_table_destroy);
750 g_clear_pointer (&self->kb_user_sections, g_hash_table_destroy);
751 g_clear_object (&self->binding_settings);
752 g_clear_object (&self->sections_store);
753
754 G_OBJECT_CLASS (cc_keyboard_manager_parent_class)->finalize (object);
755 }
756
757 static void
758 cc_keyboard_manager_get_property (GObject *object,
759 guint prop_id,
760 GValue *value,
761 GParamSpec *pspec)
762 {
763 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
764 }
765
766 static void
767 cc_keyboard_manager_set_property (GObject *object,
768 guint prop_id,
769 const GValue *value,
770 GParamSpec *pspec)
771 {
772 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
773 }
774
775 static void
776 cc_keyboard_manager_class_init (CcKeyboardManagerClass *klass)
777 {
778 GObjectClass *object_class = G_OBJECT_CLASS (klass);
779
780 object_class->finalize = cc_keyboard_manager_finalize;
781 object_class->get_property = cc_keyboard_manager_get_property;
782 object_class->set_property = cc_keyboard_manager_set_property;
783
784 /**
785 * CcKeyboardManager:shortcut-added:
786 *
787 * Emitted when a shortcut is added.
788 */
789 signals[SHORTCUT_ADDED] = g_signal_new ("shortcut-added",
790 CC_TYPE_KEYBOARD_MANAGER,
791 G_SIGNAL_RUN_FIRST,
792 0, NULL, NULL, NULL,
793 G_TYPE_NONE,
794 3,
795 CC_TYPE_KEYBOARD_ITEM,
796 G_TYPE_STRING,
797 G_TYPE_STRING);
798
799 /**
800 * CcKeyboardManager:shortcut-changed:
801 *
802 * Emitted when a shortcut is added.
803 */
804 signals[SHORTCUT_CHANGED] = g_signal_new ("shortcut-changed",
805 CC_TYPE_KEYBOARD_MANAGER,
806 G_SIGNAL_RUN_FIRST,
807 0, NULL, NULL, NULL,
808 G_TYPE_NONE,
809 1,
810 CC_TYPE_KEYBOARD_ITEM);
811
812
813 /**
814 * CcKeyboardManager:shortcut-removed:
815 *
816 * Emitted when a shortcut is removed.
817 */
818 signals[SHORTCUT_REMOVED] = g_signal_new ("shortcut-removed",
819 CC_TYPE_KEYBOARD_MANAGER,
820 G_SIGNAL_RUN_FIRST,
821 0, NULL, NULL, NULL,
822 G_TYPE_NONE,
823 1,
824 CC_TYPE_KEYBOARD_ITEM);
825
826 /**
827 * CcKeyboardManager:shortcuts-loaded:
828 *
829 * Emitted after all shortcuts are loaded.
830 */
831 signals[SHORTCUTS_LOADED] = g_signal_new ("shortcuts-loaded",
832 CC_TYPE_KEYBOARD_MANAGER,
833 G_SIGNAL_RUN_FIRST,
834 0, NULL, NULL, NULL,
835 G_TYPE_NONE, 0);
836 }
837
838 static void
839 cc_keyboard_manager_init (CcKeyboardManager *self)
840 {
841 /* Bindings */
842 self->binding_settings = g_settings_new (BINDINGS_SCHEMA);
843
844 /* Setup the section models */
845 self->sections_store = gtk_list_store_new (SECTION_N_COLUMNS,
846 G_TYPE_STRING,
847 G_TYPE_STRING,
848 G_TYPE_INT);
849 }
850
851
852 CcKeyboardManager *
853 cc_keyboard_manager_new (void)
854 {
855 return g_object_new (CC_TYPE_KEYBOARD_MANAGER, NULL);
856 }
857
858 void
859 cc_keyboard_manager_load_shortcuts (CcKeyboardManager *self)
860 {
861 g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
862
863 reload_sections (self);
864 add_shortcuts (self);
865 }
866
867 /**
868 * cc_keyboard_manager_create_custom_shortcut:
869 * @self: a #CcKeyboardPanel
870 *
871 * Creates a new temporary keyboard shortcut.
872 *
873 * Returns: (transfer full): a #CcKeyboardItem
874 */
875 CcKeyboardItem*
876 cc_keyboard_manager_create_custom_shortcut (CcKeyboardManager *self)
877 {
878 CcKeyboardItem *item;
879 g_autofree gchar *settings_path = NULL;
880
881 g_return_val_if_fail (CC_IS_KEYBOARD_MANAGER (self), NULL);
882
883 item = cc_keyboard_item_new (CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
884
885 settings_path = find_free_settings_path (self->binding_settings);
886 cc_keyboard_item_load_from_gsettings_path (item, settings_path, TRUE);
887
888 return item;
889 }
890
891 /**
892 * cc_keyboard_manager_add_custom_shortcut:
893 * @self: a #CcKeyboardPanel
894 * @item: the #CcKeyboardItem to be added
895 *
896 * Effectively adds the custom shortcut.
897 */
898 void
899 cc_keyboard_manager_add_custom_shortcut (CcKeyboardManager *self,
900 CcKeyboardItem *item)
901 {
902 GPtrArray *keys_array;
903 GHashTable *hash;
904 GVariantBuilder builder;
905 char **settings_paths;
906 int i;
907
908 g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
909
910 hash = get_hash_for_group (self, BINDING_GROUP_USER);
911 keys_array = g_hash_table_lookup (hash, CUSTOM_SHORTCUTS_ID);
912
913 if (keys_array == NULL)
914 {
915 keys_array = g_ptr_array_new ();
916 g_hash_table_insert (hash, g_strdup (CUSTOM_SHORTCUTS_ID), keys_array);
917 }
918
919 g_ptr_array_add (keys_array, item);
920
921 settings_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings");
922
923 g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
924
925 for (i = 0; settings_paths[i]; i++)
926 g_variant_builder_add (&builder, "s", settings_paths[i]);
927
928 g_variant_builder_add (&builder, "s", cc_keyboard_item_get_gsettings_path (item));
929
930 g_settings_set_value (self->binding_settings, "custom-keybindings", g_variant_builder_end (&builder));
931
932 g_signal_emit (self, signals[SHORTCUT_ADDED],
933 0,
934 item,
935 CUSTOM_SHORTCUTS_ID,
936 _("Custom Shortcuts"));
937 }
938
939 /**
940 * cc_keyboard_manager_remove_custom_shortcut:
941 * @self: a #CcKeyboardPanel
942 * @item: the #CcKeyboardItem to be added
943 *
944 * Removed the custom shortcut.
945 */
946 void
947 cc_keyboard_manager_remove_custom_shortcut (CcKeyboardManager *self,
948 CcKeyboardItem *item)
949 {
950 GPtrArray *keys_array;
951 GVariantBuilder builder;
952 GSettings *settings;
953 char **settings_paths;
954 int i;
955
956 g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
957
958 /* Shortcut not a custom shortcut */
959 g_assert (cc_keyboard_item_get_item_type (item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
960
961 settings = cc_keyboard_item_get_settings (item);
962 g_settings_delay (settings);
963 g_settings_reset (settings, "name");
964 g_settings_reset (settings, "command");
965 g_settings_reset (settings, "binding");
966 g_settings_apply (settings);
967 g_settings_sync ();
968
969 settings_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings");
970 g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
971
972 for (i = 0; settings_paths[i]; i++)
973 if (strcmp (settings_paths[i], cc_keyboard_item_get_gsettings_path (item)) != 0)
974 g_variant_builder_add (&builder, "s", settings_paths[i]);
975
976 g_settings_set_value (self->binding_settings,
977 "custom-keybindings",
978 g_variant_builder_end (&builder));
979
980 g_strfreev (settings_paths);
981
982 keys_array = g_hash_table_lookup (get_hash_for_group (self, BINDING_GROUP_USER), CUSTOM_SHORTCUTS_ID);
983 g_ptr_array_remove (keys_array, item);
984
985 g_signal_emit (self, signals[SHORTCUT_REMOVED], 0, item);
986 }
987
988 /**
989 * cc_keyboard_manager_get_collision:
990 * @self: a #CcKeyboardManager
991 * @item: (nullable): a keyboard shortcut
992 * @combo: a #CcKeyCombo
993 *
994 * Retrieves the collision item for the given shortcut.
995 *
996 * Returns: (transfer none)(nullable): the collisioned shortcut
997 */
998 CcKeyboardItem*
999 cc_keyboard_manager_get_collision (CcKeyboardManager *self,
1000 CcKeyboardItem *item,
1001 CcKeyCombo *combo)
1002 {
1003 CcUniquenessData data;
1004 BindingGroupType i;
1005
1006 g_return_val_if_fail (CC_IS_KEYBOARD_MANAGER (self), NULL);
1007
1008 data.orig_item = item;
1009 data.new_keyval = combo->keyval;
1010 data.new_mask = combo->mask;
1011 data.new_keycode = combo->keycode;
1012 data.conflict_item = NULL;
1013
1014 if (combo->keyval == 0 && combo->keycode == 0)
1015 return NULL;
1016
1017 /* Any number of shortcuts can be disabled */
1018 for (i = BINDING_GROUP_SYSTEM; i <= BINDING_GROUP_USER && !data.conflict_item; i++)
1019 {
1020 GHashTable *table;
1021
1022 table = get_hash_for_group (self, i);
1023
1024 if (!table)
1025 continue;
1026
1027 g_hash_table_find (table, (GHRFunc) check_for_uniqueness, &data);
1028 }
1029
1030 return data.conflict_item;
1031 }
1032
1033 /**
1034 * cc_keyboard_manager_reset_shortcut:
1035 * @self: a #CcKeyboardManager
1036 * @item: a #CcKeyboardItem
1037 *
1038 * Resets the keyboard shortcut managed by @item, and eventually
1039 * disables any shortcut that conflicts with the new shortcut's
1040 * value.
1041 */
1042 void
1043 cc_keyboard_manager_reset_shortcut (CcKeyboardManager *self,
1044 CcKeyboardItem *item)
1045 {
1046 GList *l;
1047
1048 g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
1049 g_return_if_fail (CC_IS_KEYBOARD_ITEM (item));
1050
1051 /* Disables any shortcut that conflicts with the new shortcut's value */
1052 for (l = cc_keyboard_item_get_default_combos (item); l; l = l->next)
1053 {
1054 CcKeyCombo *combo = l->data;
1055 CcKeyboardItem *collision;
1056
1057 collision = cc_keyboard_manager_get_collision (self, NULL, combo);
1058 if (collision)
1059 cc_keyboard_item_remove_key_combo (collision, combo);
1060 }
1061
1062 /* Resets the current item */
1063 cc_keyboard_item_reset (item);
1064 }
1065