/* * 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 . * * Author: Thomas Wood * Georges Basile Stavracas Neto * */ #include #include "cc-keyboard-manager.h" #include "keyboard-shortcuts.h" #include #ifdef GDK_WINDOWING_X11 #include #include #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; }; 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 methods */ 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, §ions_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, §ions_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, §ions_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, §ions_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); } #ifdef GDK_WINDOWING_X11 static char * get_window_manager_property (GdkDisplay *display, Atom atom, Window window) { Display *xdisplay; Atom utf8_string; int result; Atom actual_type; int actual_format; unsigned long n_items; unsigned long bytes_after; unsigned char *prop; char *value; if (window == None) return NULL; xdisplay = gdk_x11_display_get_xdisplay (display); utf8_string = XInternAtom (xdisplay, "UTF8_STRING", False); gdk_x11_display_error_trap_push (display); result = XGetWindowProperty (xdisplay, window, atom, 0, G_MAXLONG, False, utf8_string, &actual_type, &actual_format, &n_items, &bytes_after, &prop); gdk_x11_display_error_trap_pop_ignored (display); if (result != Success || actual_type != utf8_string || actual_format != 8 || n_items == 0) { XFree (prop); return NULL; } value = g_strndup ((const char *) prop, n_items); XFree (prop); if (!g_utf8_validate (value, -1, NULL)) { g_free (value); return NULL; } return value; } static Window get_wm_window (GdkDisplay *display) { Display *xdisplay; Atom wm_check; int result; Atom actual_type; int actual_format; unsigned long n_items; unsigned long bytes_after; unsigned char *prop; Window wm_window; xdisplay = gdk_x11_display_get_xdisplay (display); wm_check = XInternAtom (xdisplay, "_NET_SUPPORTING_WM_CHECK", False); gdk_x11_display_error_trap_push (display); result = XGetWindowProperty (xdisplay, XDefaultRootWindow (xdisplay), wm_check, 0, G_MAXLONG, False, XA_WINDOW, &actual_type, &actual_format, &n_items, &bytes_after, &prop); gdk_x11_display_error_trap_pop_ignored (display); if (result != Success || actual_type != XA_WINDOW || n_items == 0) { XFree (prop); return None; } wm_window = *(Window *) prop; XFree (prop); return wm_window; } #endif static GStrv get_current_keybindings (void) { #ifdef GDK_WINDOWING_X11 GdkDisplay *display; Display *xdisplay; Atom keybindings_atom; Window wm_window; char *keybindings; GStrv results; display = gdk_display_get_default (); if (!GDK_IS_X11_DISPLAY (display)) return NULL; xdisplay = gdk_x11_display_get_xdisplay (display); keybindings_atom = XInternAtom (xdisplay, "_GNOME_WM_KEYBINDINGS", False); wm_window = get_wm_window (display); keybindings = get_window_manager_property (display, keybindings_atom, wm_window); if (keybindings != NULL) { GStrv p; results = g_strsplit (keybindings, ",", -1); for (p = results; p && *p; p++) g_strstrip (*p); g_free (keybindings); } else { Atom wm_atom; char *wm_name; wm_atom = XInternAtom (xdisplay, "_NET_WM_NAME", False); wm_name = get_window_manager_property (display, wm_atom, wm_window); results = g_new0 (char *, 2); results[0] = wm_name ? wm_name : g_strdup ("Unknown"); } return results; #else return NULL; #endif } 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 */ wm_keybindings = get_current_keybindings (); if (wm_keybindings == NULL) 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 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_object (&self->sections_store); 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); } 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_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_item_remove_key_combo (collision, combo); } /* Resets the current item */ cc_keyboard_item_reset (item); }