summaryrefslogtreecommitdiffstats
path: root/panels/keyboard/cc-keyboard-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'panels/keyboard/cc-keyboard-manager.c')
-rw-r--r--panels/keyboard/cc-keyboard-manager.c923
1 files changed, 923 insertions, 0 deletions
diff --git a/panels/keyboard/cc-keyboard-manager.c b/panels/keyboard/cc-keyboard-manager.c
new file mode 100644
index 0000000..55ae3af
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-manager.c
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2010 Intel, Inc
+ * Copyright (C) 2016 Endless, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Thomas Wood <thomas.wood@intel.com>
+ * Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ */
+
+#include <glib/gi18n.h>
+
+#include "cc-keyboard-manager.h"
+#include "keyboard-shortcuts.h"
+#include "wm-common.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#define BINDINGS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys"
+#define CUSTOM_SHORTCUTS_ID "custom"
+
+struct _CcKeyboardManager
+{
+ GObject parent;
+
+ GtkListStore *sections_store;
+
+ GHashTable *kb_system_sections;
+ GHashTable *kb_apps_sections;
+ GHashTable *kb_user_sections;
+
+ GSettings *binding_settings;
+
+ gpointer wm_changed_id;
+};
+
+G_DEFINE_TYPE (CcKeyboardManager, cc_keyboard_manager, G_TYPE_OBJECT)
+
+enum
+{
+ SHORTCUT_ADDED,
+ SHORTCUT_CHANGED,
+ SHORTCUT_REMOVED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+/*
+ * Auxiliary methos
+ */
+static void
+free_key_array (GPtrArray *keys)
+{
+ if (keys != NULL)
+ {
+ gint i;
+
+ for (i = 0; i < keys->len; i++)
+ {
+ CcKeyboardItem *item;
+
+ item = g_ptr_array_index (keys, i);
+
+ g_object_unref (item);
+ }
+
+ g_ptr_array_free (keys, TRUE);
+ }
+}
+
+static gboolean
+find_conflict (CcUniquenessData *data,
+ CcKeyboardItem *item)
+{
+ GList *l;
+ gboolean is_conflict = FALSE;
+
+ if (data->orig_item && cc_keyboard_item_equal (data->orig_item, item))
+ return FALSE;
+
+ for (l = cc_keyboard_item_get_key_combos (item); l; l = l->next)
+ {
+ CcKeyCombo *combo = l->data;
+
+ if (data->new_mask != combo->mask)
+ continue;
+
+ if (data->new_keyval != 0)
+ is_conflict = data->new_keyval == combo->keyval;
+ else
+ is_conflict = combo->keyval == 0 && data->new_keycode == combo->keycode;
+
+ if (is_conflict)
+ break;
+ }
+
+ if (is_conflict)
+ data->conflict_item = item;
+
+ return is_conflict;
+}
+
+static gboolean
+compare_keys_for_uniqueness (CcKeyboardItem *current_item,
+ CcUniquenessData *data)
+{
+ CcKeyboardItem *reverse_item;
+
+ /* No conflict for: blanks or ourselves */
+ if (!current_item || data->orig_item == current_item)
+ return FALSE;
+
+ reverse_item = cc_keyboard_item_get_reverse_item (current_item);
+
+ /* When the current item is the reversed shortcut of a main item, simply ignore it */
+ if (reverse_item && cc_keyboard_item_is_hidden (current_item))
+ return FALSE;
+
+ if (find_conflict (data, current_item))
+ return TRUE;
+
+ /* Also check for the reverse item if any */
+ if (reverse_item && find_conflict (data, reverse_item))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+check_for_uniqueness (gpointer key,
+ GPtrArray *keys_array,
+ CcUniquenessData *data)
+{
+ guint i;
+
+ for (i = 0; i < keys_array->len; i++)
+ {
+ CcKeyboardItem *item;
+
+ item = keys_array->pdata[i];
+
+ if (compare_keys_for_uniqueness (item, data))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static GHashTable*
+get_hash_for_group (CcKeyboardManager *self,
+ BindingGroupType group)
+{
+ GHashTable *hash;
+
+ switch (group)
+ {
+ case BINDING_GROUP_SYSTEM:
+ hash = self->kb_system_sections;
+ break;
+ case BINDING_GROUP_APPS:
+ hash = self->kb_apps_sections;
+ break;
+ case BINDING_GROUP_USER:
+ hash = self->kb_user_sections;
+ break;
+ default:
+ hash = NULL;
+ }
+
+ return hash;
+}
+
+static gboolean
+have_key_for_group (CcKeyboardManager *self,
+ int group,
+ const gchar *name)
+{
+ GHashTableIter iter;
+ GPtrArray *keys;
+ gint i;
+
+ g_hash_table_iter_init (&iter, get_hash_for_group (self, group));
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &keys))
+ {
+ for (i = 0; i < keys->len; i++)
+ {
+ CcKeyboardItem *item = g_ptr_array_index (keys, i);
+
+ if (cc_keyboard_item_get_item_type (item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS &&
+ g_strcmp0 (name, cc_keyboard_item_get_key (item)) == 0)
+ {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+add_shortcuts (CcKeyboardManager *self)
+{
+ GtkTreeModel *sections_model;
+ GtkTreeIter sections_iter;
+ gboolean can_continue;
+
+ sections_model = GTK_TREE_MODEL (self->sections_store);
+ can_continue = gtk_tree_model_get_iter_first (sections_model, &sections_iter);
+
+ while (can_continue)
+ {
+ BindingGroupType group;
+ GPtrArray *keys;
+ g_autofree gchar *id = NULL;
+ g_autofree gchar *title = NULL;
+ gint i;
+
+ gtk_tree_model_get (sections_model,
+ &sections_iter,
+ SECTION_DESCRIPTION_COLUMN, &title,
+ SECTION_GROUP_COLUMN, &group,
+ SECTION_ID_COLUMN, &id,
+ -1);
+
+ /* Ignore separators */
+ if (group == BINDING_GROUP_SEPARATOR)
+ {
+ can_continue = gtk_tree_model_iter_next (sections_model, &sections_iter);
+ continue;
+ }
+
+ keys = g_hash_table_lookup (get_hash_for_group (self, group), id);
+
+ for (i = 0; i < keys->len; i++)
+ {
+ CcKeyboardItem *item = g_ptr_array_index (keys, i);
+
+ if (!cc_keyboard_item_is_hidden (item))
+ {
+ g_signal_emit (self, signals[SHORTCUT_ADDED],
+ 0,
+ item,
+ id,
+ title);
+ }
+ }
+
+ can_continue = gtk_tree_model_iter_next (sections_model, &sections_iter);
+ }
+}
+
+static void
+append_section (CcKeyboardManager *self,
+ const gchar *title,
+ const gchar *id,
+ BindingGroupType group,
+ const KeyListEntry *keys_list)
+{
+ GtkTreeIter iter;
+ GHashTable *reverse_items;
+ GHashTable *hash;
+ GPtrArray *keys_array;
+ gboolean is_new;
+ gint i;
+
+ hash = get_hash_for_group (self, group);
+
+ if (!hash)
+ return;
+
+ /* Add all CcKeyboardItems for this section */
+ is_new = FALSE;
+ keys_array = g_hash_table_lookup (hash, id);
+ if (keys_array == NULL)
+ {
+ keys_array = g_ptr_array_new ();
+ is_new = TRUE;
+ }
+
+ reverse_items = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; keys_list != NULL && keys_list[i].name != NULL; i++)
+ {
+ CcKeyboardItem *item;
+ gboolean ret;
+
+ if (have_key_for_group (self, group, keys_list[i].name))
+ continue;
+
+ item = cc_keyboard_item_new (keys_list[i].type);
+
+ switch (keys_list[i].type)
+ {
+ case CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH:
+ ret = cc_keyboard_item_load_from_gsettings_path (item, keys_list[i].name, FALSE);
+ break;
+
+ case CC_KEYBOARD_ITEM_TYPE_GSETTINGS:
+ ret = cc_keyboard_item_load_from_gsettings (item,
+ keys_list[i].description,
+ keys_list[i].schema,
+ keys_list[i].name);
+ if (ret && keys_list[i].reverse_entry != NULL)
+ {
+ CcKeyboardItem *reverse_item;
+ reverse_item = g_hash_table_lookup (reverse_items,
+ keys_list[i].reverse_entry);
+ if (reverse_item != NULL)
+ {
+ cc_keyboard_item_add_reverse_item (item,
+ reverse_item,
+ keys_list[i].is_reversed);
+ }
+ else
+ {
+ g_hash_table_insert (reverse_items,
+ keys_list[i].name,
+ item);
+ }
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (ret == FALSE)
+ {
+ /* We don't actually want to popup a dialog - just skip this one */
+ g_object_unref (item);
+ continue;
+ }
+
+ cc_keyboard_item_set_hidden (item, keys_list[i].hidden);
+
+ g_ptr_array_add (keys_array, item);
+ }
+
+ g_hash_table_destroy (reverse_items);
+
+ /* Add the keys to the hash table */
+ if (is_new)
+ {
+ g_hash_table_insert (hash, g_strdup (id), keys_array);
+
+ /* Append the section to the left tree view */
+ gtk_list_store_append (GTK_LIST_STORE (self->sections_store), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (self->sections_store),
+ &iter,
+ SECTION_DESCRIPTION_COLUMN, title,
+ SECTION_ID_COLUMN, id,
+ SECTION_GROUP_COLUMN, group,
+ -1);
+ }
+}
+
+static void
+append_sections_from_file (CcKeyboardManager *self,
+ const gchar *path,
+ const char *datadir,
+ gchar **wm_keybindings)
+{
+ KeyList *keylist;
+ KeyListEntry *keys;
+ KeyListEntry key = { 0, 0, 0, 0, 0, 0, 0 };
+ const char *title;
+ int group;
+ guint i;
+
+ keylist = parse_keylist_from_file (path);
+
+ if (keylist == NULL)
+ return;
+
+#define const_strv(s) ((const gchar* const*) s)
+
+ /* If there's no keys to add, or the settings apply to a window manager
+ * that's not the one we're running */
+ if (keylist->entries->len == 0 ||
+ (keylist->wm_name != NULL && !g_strv_contains (const_strv (wm_keybindings), keylist->wm_name)) ||
+ keylist->name == NULL)
+ {
+ g_free (keylist->name);
+ g_free (keylist->package);
+ g_free (keylist->wm_name);
+ g_array_free (keylist->entries, TRUE);
+ g_free (keylist);
+ return;
+ }
+
+#undef const_strv
+
+ /* Empty KeyListEntry to end the array */
+ key.name = NULL;
+ g_array_append_val (keylist->entries, key);
+
+ keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE);
+ if (keylist->package)
+ {
+ g_autofree gchar *localedir = NULL;
+
+ localedir = g_build_filename (datadir, "locale", NULL);
+ bindtextdomain (keylist->package, localedir);
+
+ title = dgettext (keylist->package, keylist->name);
+ } else {
+ title = _(keylist->name);
+ }
+
+ if (keylist->group && strcmp (keylist->group, "system") == 0)
+ group = BINDING_GROUP_SYSTEM;
+ else
+ group = BINDING_GROUP_APPS;
+
+ append_section (self, title, keylist->name, group, keys);
+
+ g_free (keylist->name);
+ g_free (keylist->package);
+ g_free (keylist->wm_name);
+ g_free (keylist->schema);
+ g_free (keylist->group);
+
+ for (i = 0; keys[i].name != NULL; i++)
+ {
+ KeyListEntry *entry = &keys[i];
+ g_free (entry->schema);
+ g_free (entry->description);
+ g_free (entry->name);
+ g_free (entry->reverse_entry);
+ }
+
+ g_free (keylist);
+ g_free (keys);
+}
+
+static void
+append_sections_from_gsettings (CcKeyboardManager *self)
+{
+ g_auto(GStrv) custom_paths = NULL;
+ GArray *entries;
+ KeyListEntry key = { 0, 0, 0, 0, 0, 0, 0 };
+ int i;
+
+ /* load custom shortcuts from GSettings */
+ entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
+
+ custom_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings");
+ for (i = 0; custom_paths[i]; i++)
+ {
+ key.name = g_strdup (custom_paths[i]);
+ if (!have_key_for_group (self, BINDING_GROUP_USER, key.name))
+ {
+ key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH;
+ g_array_append_val (entries, key);
+ }
+ else
+ g_free (key.name);
+ }
+
+ if (entries->len > 0)
+ {
+ KeyListEntry *keys;
+ int i;
+
+ /* Empty KeyListEntry to end the array */
+ key.name = NULL;
+ g_array_append_val (entries, key);
+
+ keys = (KeyListEntry *) entries->data;
+ append_section (self, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, keys);
+ for (i = 0; i < entries->len; ++i)
+ {
+ g_free (keys[i].name);
+ }
+ }
+ else
+ {
+ append_section (self, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, NULL);
+ }
+
+ g_array_free (entries, TRUE);
+}
+
+static void
+reload_sections (CcKeyboardManager *self)
+{
+ GHashTable *loaded_files;
+ GDir *dir;
+ gchar *default_wm_keybindings[] = { "Mutter", "GNOME Shell", NULL };
+ g_auto(GStrv) wm_keybindings = NULL;
+ const gchar * const * data_dirs;
+ guint i;
+
+ /* Clear previous models and hash tables */
+ gtk_list_store_clear (GTK_LIST_STORE (self->sections_store));
+
+ g_clear_pointer (&self->kb_system_sections, g_hash_table_destroy);
+ self->kb_system_sections = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) free_key_array);
+
+ g_clear_pointer (&self->kb_apps_sections, g_hash_table_destroy);
+ self->kb_apps_sections = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) free_key_array);
+
+ g_clear_pointer (&self->kb_user_sections, g_hash_table_destroy);
+ self->kb_user_sections = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) free_key_array);
+
+ /* Load WM keybindings */
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ wm_keybindings = wm_common_get_current_keybindings ();
+ else
+#endif
+ wm_keybindings = g_strdupv (default_wm_keybindings);
+
+ loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ data_dirs = g_get_system_data_dirs ();
+ for (i = 0; data_dirs[i] != NULL; i++)
+ {
+ g_autofree gchar *dir_path = NULL;
+ const gchar *name;
+
+ dir_path = g_build_filename (data_dirs[i], "gnome-control-center", "keybindings", NULL);
+
+ dir = g_dir_open (dir_path, 0, NULL);
+ if (!dir)
+ continue;
+
+ for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir))
+ {
+ g_autofree gchar *path = NULL;
+
+ if (g_str_has_suffix (name, ".xml") == FALSE)
+ continue;
+
+ if (g_hash_table_lookup (loaded_files, name) != NULL)
+ {
+ g_debug ("Not loading %s, it was already loaded from another directory", name);
+ continue;
+ }
+
+ g_hash_table_insert (loaded_files, g_strdup (name), GINT_TO_POINTER (1));
+ path = g_build_filename (dir_path, name, NULL);
+ append_sections_from_file (self, path, data_dirs[i], wm_keybindings);
+ }
+
+ g_dir_close (dir);
+ }
+
+ g_hash_table_destroy (loaded_files);
+
+ /* Load custom keybindings */
+ append_sections_from_gsettings (self);
+}
+
+/*
+ * Callbacks
+ */
+static void
+on_window_manager_change (const char *wm_name,
+ CcKeyboardManager *self)
+{
+ reload_sections (self);
+}
+
+static void
+cc_keyboard_manager_finalize (GObject *object)
+{
+ CcKeyboardManager *self = (CcKeyboardManager *)object;
+
+ g_clear_pointer (&self->kb_system_sections, g_hash_table_destroy);
+ g_clear_pointer (&self->kb_apps_sections, g_hash_table_destroy);
+ g_clear_pointer (&self->kb_user_sections, g_hash_table_destroy);
+ g_clear_object (&self->binding_settings);
+
+ g_clear_pointer (&self->wm_changed_id, wm_common_unregister_window_manager_change);
+
+ G_OBJECT_CLASS (cc_keyboard_manager_parent_class)->finalize (object);
+}
+
+static void
+cc_keyboard_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+cc_keyboard_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+cc_keyboard_manager_class_init (CcKeyboardManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_keyboard_manager_finalize;
+ object_class->get_property = cc_keyboard_manager_get_property;
+ object_class->set_property = cc_keyboard_manager_set_property;
+
+ /**
+ * CcKeyboardManager:shortcut-added:
+ *
+ * Emitted when a shortcut is added.
+ */
+ signals[SHORTCUT_ADDED] = g_signal_new ("shortcut-added",
+ CC_TYPE_KEYBOARD_MANAGER,
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 3,
+ CC_TYPE_KEYBOARD_ITEM,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ /**
+ * CcKeyboardManager:shortcut-changed:
+ *
+ * Emitted when a shortcut is added.
+ */
+ signals[SHORTCUT_CHANGED] = g_signal_new ("shortcut-changed",
+ CC_TYPE_KEYBOARD_MANAGER,
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ CC_TYPE_KEYBOARD_ITEM);
+
+
+ /**
+ * CcKeyboardManager:shortcut-removed:
+ *
+ * Emitted when a shortcut is removed.
+ */
+ signals[SHORTCUT_REMOVED] = g_signal_new ("shortcut-removed",
+ CC_TYPE_KEYBOARD_MANAGER,
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ CC_TYPE_KEYBOARD_ITEM);
+}
+
+static void
+cc_keyboard_manager_init (CcKeyboardManager *self)
+{
+ /* Bindings */
+ self->binding_settings = g_settings_new (BINDINGS_SCHEMA);
+
+ /* Setup the section models */
+ self->sections_store = gtk_list_store_new (SECTION_N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INT);
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ self->wm_changed_id = wm_common_register_window_manager_change ((GFunc) on_window_manager_change,
+ self);
+#endif
+}
+
+
+CcKeyboardManager *
+cc_keyboard_manager_new (void)
+{
+ return g_object_new (CC_TYPE_KEYBOARD_MANAGER, NULL);
+}
+
+void
+cc_keyboard_manager_load_shortcuts (CcKeyboardManager *self)
+{
+ g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
+
+ reload_sections (self);
+ add_shortcuts (self);
+}
+
+/**
+ * cc_keyboard_manager_create_custom_shortcut:
+ * @self: a #CcKeyboardPanel
+ *
+ * Creates a new temporary keyboard shortcut.
+ *
+ * Returns: (transfer full): a #CcKeyboardItem
+ */
+CcKeyboardItem*
+cc_keyboard_manager_create_custom_shortcut (CcKeyboardManager *self)
+{
+ CcKeyboardItem *item;
+ g_autofree gchar *settings_path = NULL;
+
+ g_return_val_if_fail (CC_IS_KEYBOARD_MANAGER (self), NULL);
+
+ item = cc_keyboard_item_new (CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
+
+ settings_path = find_free_settings_path (self->binding_settings);
+ cc_keyboard_item_load_from_gsettings_path (item, settings_path, TRUE);
+
+ return item;
+}
+
+/**
+ * cc_keyboard_manager_add_custom_shortcut:
+ * @self: a #CcKeyboardPanel
+ * @item: the #CcKeyboardItem to be added
+ *
+ * Effectively adds the custom shortcut.
+ */
+void
+cc_keyboard_manager_add_custom_shortcut (CcKeyboardManager *self,
+ CcKeyboardItem *item)
+{
+ GPtrArray *keys_array;
+ GHashTable *hash;
+ GVariantBuilder builder;
+ char **settings_paths;
+ int i;
+
+ g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
+
+ hash = get_hash_for_group (self, BINDING_GROUP_USER);
+ keys_array = g_hash_table_lookup (hash, CUSTOM_SHORTCUTS_ID);
+
+ if (keys_array == NULL)
+ {
+ keys_array = g_ptr_array_new ();
+ g_hash_table_insert (hash, g_strdup (CUSTOM_SHORTCUTS_ID), keys_array);
+ }
+
+ g_ptr_array_add (keys_array, item);
+
+ settings_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings");
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ for (i = 0; settings_paths[i]; i++)
+ g_variant_builder_add (&builder, "s", settings_paths[i]);
+
+ g_variant_builder_add (&builder, "s", cc_keyboard_item_get_gsettings_path (item));
+
+ g_settings_set_value (self->binding_settings, "custom-keybindings", g_variant_builder_end (&builder));
+
+ g_signal_emit (self, signals[SHORTCUT_ADDED],
+ 0,
+ item,
+ CUSTOM_SHORTCUTS_ID,
+ _("Custom Shortcuts"));
+}
+
+/**
+ * cc_keyboard_manager_remove_custom_shortcut:
+ * @self: a #CcKeyboardPanel
+ * @item: the #CcKeyboardItem to be added
+ *
+ * Removed the custom shortcut.
+ */
+void
+cc_keyboard_manager_remove_custom_shortcut (CcKeyboardManager *self,
+ CcKeyboardItem *item)
+{
+ GPtrArray *keys_array;
+ GVariantBuilder builder;
+ GSettings *settings;
+ char **settings_paths;
+ int i;
+
+ g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
+
+ /* Shortcut not a custom shortcut */
+ g_assert (cc_keyboard_item_get_item_type (item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
+
+ settings = cc_keyboard_item_get_settings (item);
+ g_settings_delay (settings);
+ g_settings_reset (settings, "name");
+ g_settings_reset (settings, "command");
+ g_settings_reset (settings, "binding");
+ g_settings_apply (settings);
+ g_settings_sync ();
+
+ settings_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings");
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ for (i = 0; settings_paths[i]; i++)
+ if (strcmp (settings_paths[i], cc_keyboard_item_get_gsettings_path (item)) != 0)
+ g_variant_builder_add (&builder, "s", settings_paths[i]);
+
+ g_settings_set_value (self->binding_settings,
+ "custom-keybindings",
+ g_variant_builder_end (&builder));
+
+ g_strfreev (settings_paths);
+
+ keys_array = g_hash_table_lookup (get_hash_for_group (self, BINDING_GROUP_USER), CUSTOM_SHORTCUTS_ID);
+ g_ptr_array_remove (keys_array, item);
+
+ g_signal_emit (self, signals[SHORTCUT_REMOVED], 0, item);
+}
+
+/**
+ * cc_keyboard_manager_get_collision:
+ * @self: a #CcKeyboardManager
+ * @item: (nullable): a keyboard shortcut
+ * @combo: a #CcKeyCombo
+ *
+ * Retrieves the collision item for the given shortcut.
+ *
+ * Returns: (transfer none)(nullable): the collisioned shortcut
+ */
+CcKeyboardItem*
+cc_keyboard_manager_get_collision (CcKeyboardManager *self,
+ CcKeyboardItem *item,
+ CcKeyCombo *combo)
+{
+ CcUniquenessData data;
+ BindingGroupType i;
+
+ g_return_val_if_fail (CC_IS_KEYBOARD_MANAGER (self), NULL);
+
+ data.orig_item = item;
+ data.new_keyval = combo->keyval;
+ data.new_mask = combo->mask;
+ data.new_keycode = combo->keycode;
+ data.conflict_item = NULL;
+
+ if (combo->keyval == 0 && combo->keycode == 0)
+ return NULL;
+
+ /* Any number of shortcuts can be disabled */
+ for (i = BINDING_GROUP_SYSTEM; i <= BINDING_GROUP_USER && !data.conflict_item; i++)
+ {
+ GHashTable *table;
+
+ table = get_hash_for_group (self, i);
+
+ if (!table)
+ continue;
+
+ g_hash_table_find (table, (GHRFunc) check_for_uniqueness, &data);
+ }
+
+ return data.conflict_item;
+}
+
+/**
+ * cc_keyboard_manager_disable_shortcut:
+ * @self: a #CcKeyboardManager
+ * @item: a @CcKeyboardItem
+ *
+ * Disables the given keyboard shortcut.
+ */
+void
+cc_keyboard_manager_disable_shortcut (CcKeyboardManager *self,
+ CcKeyboardItem *item)
+{
+ g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
+
+ g_object_set (item, "binding", NULL, NULL);
+}
+
+/**
+ * cc_keyboard_manager_reset_shortcut:
+ * @self: a #CcKeyboardManager
+ * @item: a #CcKeyboardItem
+ *
+ * Resets the keyboard shortcut managed by @item, and eventually
+ * disables any shortcut that conflicts with the new shortcut's
+ * value.
+ */
+void
+cc_keyboard_manager_reset_shortcut (CcKeyboardManager *self,
+ CcKeyboardItem *item)
+{
+ GList *l;
+
+ g_return_if_fail (CC_IS_KEYBOARD_MANAGER (self));
+ g_return_if_fail (CC_IS_KEYBOARD_ITEM (item));
+
+ /* Disables any shortcut that conflicts with the new shortcut's value */
+ for (l = cc_keyboard_item_get_default_combos (item); l; l = l->next)
+ {
+ CcKeyCombo *combo = l->data;
+ CcKeyboardItem *collision;
+
+ collision = cc_keyboard_manager_get_collision (self, NULL, combo);
+ if (collision)
+ cc_keyboard_manager_disable_shortcut (self, collision);
+ }
+
+ /* Resets the current item */
+ cc_keyboard_item_reset (item);
+}