Before d7e794e7ab
Keyboard Shortcuts dialog showed keybindings
for current WM. This commit restores this functionality.
1050 lines
28 KiB
C
1050 lines
28 KiB
C
/*
|
|
* 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 <gdk/gdk.h>
|
|
#ifdef GDK_WINDOWING_X11
|
|
#include <gdk/x11/gdkx.h>
|
|
#include <X11/Xatom.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;
|
|
};
|
|
|
|
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);
|
|
}
|