summaryrefslogtreecommitdiffstats
path: root/panels/keyboard
diff options
context:
space:
mode:
Diffstat (limited to 'panels/keyboard')
-rw-r--r--panels/keyboard/00-multimedia.xml.in27
-rw-r--r--panels/keyboard/01-input-sources.xml.in15
-rw-r--r--panels/keyboard/01-launchers.xml.in19
-rw-r--r--panels/keyboard/01-screenshot.xml.in29
-rw-r--r--panels/keyboard/01-system.xml.in9
-rw-r--r--panels/keyboard/50-accessibility.xml.in20
-rw-r--r--panels/keyboard/cc-alt-chars-key-dialog.c209
-rw-r--r--panels/keyboard/cc-alt-chars-key-dialog.h32
-rw-r--r--panels/keyboard/cc-alt-chars-key-dialog.ui155
-rw-r--r--panels/keyboard/cc-keyboard-item.c874
-rw-r--r--panels/keyboard/cc-keyboard-item.h103
-rw-r--r--panels/keyboard/cc-keyboard-manager.c923
-rw-r--r--panels/keyboard/cc-keyboard-manager.h57
-rw-r--r--panels/keyboard/cc-keyboard-option.c478
-rw-r--r--panels/keyboard/cc-keyboard-option.h46
-rw-r--r--panels/keyboard/cc-keyboard-panel.c844
-rw-r--r--panels/keyboard/cc-keyboard-panel.h34
-rw-r--r--panels/keyboard/cc-keyboard-panel.ui196
-rw-r--r--panels/keyboard/cc-keyboard-shortcut-editor.c1012
-rw-r--r--panels/keyboard/cc-keyboard-shortcut-editor.h52
-rw-r--r--panels/keyboard/cc-keyboard-shortcut-editor.ui354
-rw-r--r--panels/keyboard/enter-keyboard-shortcut.svg245
-rw-r--r--panels/keyboard/gnome-keybindings.pc.in10
-rw-r--r--panels/keyboard/gnome-keyboard-panel.desktop.in.in18
-rw-r--r--panels/keyboard/keyboard-shortcuts.c379
-rw-r--r--panels/keyboard/keyboard-shortcuts.h78
-rw-r--r--panels/keyboard/keyboard.gresource.xml9
-rw-r--r--panels/keyboard/meson.build94
-rw-r--r--panels/keyboard/wm-common.c261
-rw-r--r--panels/keyboard/wm-common.h14
30 files changed, 6596 insertions, 0 deletions
diff --git a/panels/keyboard/00-multimedia.xml.in b/panels/keyboard/00-multimedia.xml.in
new file mode 100644
index 0000000..bb1532d
--- /dev/null
+++ b/panels/keyboard/00-multimedia.xml.in
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<KeyListEntries group="system" schema="org.gnome.settings-daemon.plugins.media-keys" name="Sound and Media">
+
+ <KeyListEntry name="volume-mute" description="Volume mute/unmute"/>
+
+ <KeyListEntry name="volume-down" description="Volume down"/>
+
+ <KeyListEntry name="volume-up" description="Volume up"/>
+
+ <KeyListEntry name="mic-mute" description="Microphone mute/unmute"/>
+
+ <KeyListEntry name="media" description="Launch media player"/>
+
+ <KeyListEntry name="play" description="Play (or play/pause)"/>
+
+ <KeyListEntry name="pause" description="Pause playback"/>
+
+ <KeyListEntry name="stop" description="Stop playback"/>
+
+ <KeyListEntry name="previous" description="Previous track"/>
+
+ <KeyListEntry name="next" description="Next track"/>
+
+ <KeyListEntry name="eject" description="Eject"/>
+
+</KeyListEntries>
+
diff --git a/panels/keyboard/01-input-sources.xml.in b/panels/keyboard/01-input-sources.xml.in
new file mode 100644
index 0000000..355bba4
--- /dev/null
+++ b/panels/keyboard/01-input-sources.xml.in
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<KeyListEntries group="system"
+ schema="org.gnome.desktop.wm.keybindings"
+ name="Typing">
+
+ <KeyListEntry name="switch-input-source"
+ reverse-entry="switch-input-source-backward"
+ description="Switch to next input source"/>
+
+ <KeyListEntry name="switch-input-source-backward"
+ reverse-entry="switch-input-source"
+ is-reversed="true"
+ description="Switch to previous input source"/>
+
+</KeyListEntries>
diff --git a/panels/keyboard/01-launchers.xml.in b/panels/keyboard/01-launchers.xml.in
new file mode 100644
index 0000000..67c8325
--- /dev/null
+++ b/panels/keyboard/01-launchers.xml.in
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<KeyListEntries group="system" schema="org.gnome.settings-daemon.plugins.media-keys" name="Launchers">
+
+ <KeyListEntry name="help" description="Launch help browser"/>
+
+ <KeyListEntry name="control-center" description="Settings"/>
+
+ <KeyListEntry name="calculator" description="Launch calculator"/>
+
+ <KeyListEntry name="email" description="Launch email client"/>
+
+ <KeyListEntry name="www" description="Launch web browser"/>
+
+ <KeyListEntry name="home" description="Home folder"/>
+
+ <KeyListEntry name="search" description="Search" msgctxt="keybinding">Search</KeyListEntry>
+
+</KeyListEntries>
+
diff --git a/panels/keyboard/01-screenshot.xml.in b/panels/keyboard/01-screenshot.xml.in
new file mode 100644
index 0000000..002bd38
--- /dev/null
+++ b/panels/keyboard/01-screenshot.xml.in
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<KeyListEntries group="system" schema="org.gnome.settings-daemon.plugins.media-keys" name="Screenshots">
+
+ <!-- translators: $PICTURES will be replaced by the name of the XDG Pictures directory -->
+ <KeyListEntry name="screenshot"
+ description="Save a screenshot to $PICTURES"/>
+
+ <!-- translators: $PICTURES will be replaced by the name of the XDG Pictures directory -->
+ <KeyListEntry name="window-screenshot"
+ description="Save a screenshot of a window to $PICTURES"/>
+
+ <!-- translators: $PICTURES will be replaced by the name of the XDG Pictures directory -->
+ <KeyListEntry name="area-screenshot"
+ description="Save a screenshot of an area to $PICTURES"/>
+
+ <KeyListEntry name="screenshot-clip"
+ description="Copy a screenshot to clipboard"/>
+
+ <KeyListEntry name="window-screenshot-clip"
+ description="Copy a screenshot of a window to clipboard"/>
+
+ <KeyListEntry name="area-screenshot-clip"
+ description="Copy a screenshot of an area to clipboard"/>
+
+ <KeyListEntry name="screencast"
+ description="Record a short screencast"/>
+
+</KeyListEntries>
+
diff --git a/panels/keyboard/01-system.xml.in b/panels/keyboard/01-system.xml.in
new file mode 100644
index 0000000..1fcf78b
--- /dev/null
+++ b/panels/keyboard/01-system.xml.in
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<KeyListEntries group="system" schema="org.gnome.settings-daemon.plugins.media-keys" name="System">
+
+ <KeyListEntry name="logout" description="Log out"/>
+
+ <KeyListEntry name="screensaver" description="Lock screen"/>
+
+</KeyListEntries>
+
diff --git a/panels/keyboard/50-accessibility.xml.in b/panels/keyboard/50-accessibility.xml.in
new file mode 100644
index 0000000..34bcb04
--- /dev/null
+++ b/panels/keyboard/50-accessibility.xml.in
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<KeyListEntries group="system" name="Accessibility" schema="org.gnome.settings-daemon.plugins.media-keys">
+
+ <KeyListEntry name="magnifier" description="Turn zoom on or off"/>
+
+ <KeyListEntry name="magnifier-zoom-in" description="Zoom in"/>
+
+ <KeyListEntry name="magnifier-zoom-out" description="Zoom out"/>
+
+ <KeyListEntry name="screenreader" description="Turn screen reader on or off"/>
+
+ <KeyListEntry name="on-screen-keyboard" description="Turn on-screen keyboard on or off"/>
+
+ <KeyListEntry name="increase-text-size" description="Increase text size"/>
+
+ <KeyListEntry name="decrease-text-size" description="Decrease text size"/>
+
+ <KeyListEntry name="toggle-contrast" description="High contrast on or off"/>
+
+</KeyListEntries>
diff --git a/panels/keyboard/cc-alt-chars-key-dialog.c b/panels/keyboard/cc-alt-chars-key-dialog.c
new file mode 100644
index 0000000..c32c6ba
--- /dev/null
+++ b/panels/keyboard/cc-alt-chars-key-dialog.c
@@ -0,0 +1,209 @@
+/* cc-alt-chars-key-dialog.c
+ *
+ * Copyright 2019 Bastien Nocera <hadess@hadess.net>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "cc-alt-chars-key-dialog.h"
+
+struct _CcAltCharsKeyDialog
+{
+ GtkDialog parent_instance;
+
+ GSettings *input_source_settings;
+
+ GtkRadioButton *leftalt_radio;
+ GtkRadioButton *leftsuper_radio;
+ GtkRadioButton *menukey_radio;
+ GtkRadioButton *rightalt_radio;
+ GtkRadioButton *rightctrl_radio;
+ GtkRadioButton *rightsuper_radio;
+};
+
+G_DEFINE_TYPE (CcAltCharsKeyDialog, cc_alt_chars_key_dialog, GTK_TYPE_DIALOG)
+
+static GtkRadioButton *
+get_radio_button_from_xkb_option_name (CcAltCharsKeyDialog *self,
+ const gchar *name)
+{
+ if (g_str_equal (name, "lv3:switch"))
+ return self->rightctrl_radio;
+ else if (g_str_equal (name, "lv3:menu_switch"))
+ return self->menukey_radio;
+ else if (g_str_equal (name, "lv3:lwin_switch"))
+ return self->leftsuper_radio;
+ else if (g_str_equal (name, "lv3:rwin_switch"))
+ return self->rightsuper_radio;
+ else if (g_str_equal (name, "lv3:lalt_switch"))
+ return self->leftalt_radio;
+ else if (g_str_equal (name, "lv3:ralt_switch"))
+ return self->rightalt_radio;
+
+ return NULL;
+}
+
+static const gchar *
+get_xkb_option_name_from_radio_button (CcAltCharsKeyDialog *self,
+ GtkRadioButton *radio)
+{
+ if (radio == self->rightctrl_radio)
+ return "lv3:switch";
+ else if (radio == self->menukey_radio)
+ return "lv3:menu_switch";
+ else if (radio == self->leftsuper_radio)
+ return "lv3:lwin_switch";
+ else if (radio == self->rightsuper_radio)
+ return "lv3:rwin_switch";
+ else if (radio == self->leftalt_radio)
+ return "lv3:lalt_switch";
+ else if (radio == self->rightalt_radio)
+ return "lv3:ralt_switch";
+
+ return NULL;
+}
+
+static void
+update_active_radio (CcAltCharsKeyDialog *self)
+{
+ g_auto(GStrv) options = NULL;
+ guint i;
+
+ options = g_settings_get_strv (self->input_source_settings, "xkb-options");
+
+ for (i = 0; options != NULL && options[i] != NULL; i++)
+ {
+ GtkRadioButton *radio;
+
+ if (!g_str_has_prefix (options[i], "lv3:"))
+ continue;
+
+ radio = get_radio_button_from_xkb_option_name (self, options[i]);
+
+ if (!radio)
+ continue;
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE);
+ return;
+ }
+
+ /* Fallback to Right Alt as default */
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->rightalt_radio), TRUE);
+}
+
+static void
+on_active_lv3_changed_cb (GtkRadioButton *radio,
+ CcAltCharsKeyDialog *self)
+{
+ g_autoptr(GPtrArray) array = NULL;
+ g_auto(GStrv) options = NULL;
+ gboolean found;
+ guint i;
+
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio)))
+ return;
+
+ /* Either replace the existing "lv3:" option in the string
+ * array, or add the option at the end
+ */
+ array = g_ptr_array_new ();
+ options = g_settings_get_strv (self->input_source_settings, "xkb-options");
+ found = FALSE;
+
+ for (i = 0; options != NULL && options[i] != NULL; i++)
+ {
+ if (g_str_has_prefix (options[i], "lv3:"))
+ {
+ g_ptr_array_add (array, (gchar *)get_xkb_option_name_from_radio_button (self, radio));
+ found = TRUE;
+ }
+ else
+ {
+ g_ptr_array_add (array, options[i]);
+ }
+ }
+
+ if (!found)
+ g_ptr_array_add (array, (gchar *)get_xkb_option_name_from_radio_button (self, radio));
+
+ g_ptr_array_add (array, NULL);
+
+ g_settings_set_strv (self->input_source_settings,
+ "xkb-options",
+ (const gchar * const *) array->pdata);
+}
+
+static void
+on_xkb_options_changed_cb (CcAltCharsKeyDialog *self)
+{
+ update_active_radio (self);
+}
+
+static void
+cc_alt_chars_key_dialog_finalize (GObject *object)
+{
+ CcAltCharsKeyDialog *self = (CcAltCharsKeyDialog *)object;
+
+ g_clear_object (&self->input_source_settings);
+
+ G_OBJECT_CLASS (cc_alt_chars_key_dialog_parent_class)->finalize (object);
+}
+
+static void
+cc_alt_chars_key_dialog_class_init (CcAltCharsKeyDialogClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_alt_chars_key_dialog_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-alt-chars-key-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcAltCharsKeyDialog, leftalt_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcAltCharsKeyDialog, leftsuper_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcAltCharsKeyDialog, menukey_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcAltCharsKeyDialog, rightalt_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcAltCharsKeyDialog, rightctrl_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcAltCharsKeyDialog, rightsuper_radio);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_active_lv3_changed_cb);
+}
+
+static void
+cc_alt_chars_key_dialog_init (CcAltCharsKeyDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->input_source_settings = g_settings_new ("org.gnome.desktop.input-sources");
+ g_signal_connect_object (self->input_source_settings,
+ "changed::xkb-options",
+ G_CALLBACK (on_xkb_options_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ update_active_radio (self);
+}
+
+CcAltCharsKeyDialog *
+cc_alt_chars_key_dialog_new (GSettings *input_settings)
+{
+ CcAltCharsKeyDialog *self;
+
+ self = g_object_new (CC_TYPE_ALT_CHARS_KEY_DIALOG,
+ "use-header-bar", 1,
+ NULL);
+ self->input_source_settings = g_object_ref (input_settings);
+
+ return self;
+}
diff --git a/panels/keyboard/cc-alt-chars-key-dialog.h b/panels/keyboard/cc-alt-chars-key-dialog.h
new file mode 100644
index 0000000..fb0c853
--- /dev/null
+++ b/panels/keyboard/cc-alt-chars-key-dialog.h
@@ -0,0 +1,32 @@
+/* cc-alt-chars-key-dialog.h
+ *
+ * Copyright 2019 Bastien Nocera <hadess@hadess.net>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_ALT_CHARS_KEY_DIALOG (cc_alt_chars_key_dialog_get_type())
+G_DECLARE_FINAL_TYPE (CcAltCharsKeyDialog, cc_alt_chars_key_dialog, CC, ALT_CHARS_KEY_DIALOG, GtkDialog)
+
+CcAltCharsKeyDialog *cc_alt_chars_key_dialog_new (GSettings *input_settings);
+
+G_END_DECLS
diff --git a/panels/keyboard/cc-alt-chars-key-dialog.ui b/panels/keyboard/cc-alt-chars-key-dialog.ui
new file mode 100644
index 0000000..67d7206
--- /dev/null
+++ b/panels/keyboard/cc-alt-chars-key-dialog.ui
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcAltCharsKeyDialog" parent="GtkDialog">
+ <property name="modal">True</property>
+ <property name="can_focus">False</property>
+ <property name="resizable">False</property>
+ <property name="type_hint">dialog</property>
+ <property name="title" translatable="yes">Alternate Characters Key</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete" />
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin">18</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">12</property>
+ <property name="label" translatable="yes">The alternate characters key can be used to enter additional characters. These are sometimes printed as a third-option on your keyboard.</property>
+ <property name="wrap">True</property>
+ <property name="width_chars">40</property>
+ <property name="max_width_chars">40</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <property name="column_homogeneous">True</property>
+
+ <child>
+ <object class="GtkRadioButton" id="leftalt_radio">
+ <property name="label" translatable="yes">Left Alt</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_active_lv3_changed_cb" object="CcAltCharsKeyDialog" swapped="no" />
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkRadioButton" id="rightalt_radio">
+ <property name="label" translatable="yes">Right Alt</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">leftalt_radio</property>
+ <signal name="toggled" handler="on_active_lv3_changed_cb" object="CcAltCharsKeyDialog" swapped="no" />
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkRadioButton" id="leftsuper_radio">
+ <property name="label" translatable="yes">Left Super</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">leftalt_radio</property>
+ <signal name="toggled" handler="on_active_lv3_changed_cb" object="CcAltCharsKeyDialog" swapped="no" />
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkRadioButton" id="rightsuper_radio">
+ <property name="label" translatable="yes">Right Super</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">leftalt_radio</property>
+ <signal name="toggled" handler="on_active_lv3_changed_cb" object="CcAltCharsKeyDialog" swapped="no" />
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkRadioButton" id="menukey_radio">
+ <property name="label" translatable="yes">Menu key</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">leftalt_radio</property>
+ <signal name="toggled" handler="on_active_lv3_changed_cb" object="CcAltCharsKeyDialog" swapped="no" />
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkRadioButton" id="rightctrl_radio">
+ <property name="label" translatable="yes">Right Ctrl</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">leftalt_radio</property>
+ <signal name="toggled" handler="on_active_lv3_changed_cb" object="CcAltCharsKeyDialog" swapped="no" />
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <child internal-child="headerbar">
+ <object class="GtkHeaderBar">
+ <property name="can_focus">False</property>
+ <property name="show_close_button">True</property>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/keyboard/cc-keyboard-item.c b/panels/keyboard/cc-keyboard-item.c
new file mode 100644
index 0000000..853c598
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-item.c
@@ -0,0 +1,874 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011, 2014 Red Hat, 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/>.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+
+#include "cc-keyboard-item.h"
+
+#define CUSTOM_KEYS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys.custom-keybinding"
+
+struct _CcKeyboardItem
+{
+ GObject parent_instance;
+
+ char *binding;
+
+ CcKeyboardItem *reverse_item;
+ gboolean is_reversed;
+ gboolean hidden;
+
+ CcKeyboardItemType type;
+
+ CcKeyCombo *primary_combo;
+ BindingGroupType group;
+ char *description;
+ gboolean editable;
+ GList *key_combos;
+ GList *default_combos;
+
+ /* GSettings path */
+ char *gsettings_path;
+ gboolean desc_editable;
+ char *command;
+ gboolean cmd_editable;
+
+ /* GSettings */
+ char *schema;
+ char *key;
+ GSettings *settings;
+};
+
+enum
+{
+ PROP_0,
+ PROP_DESCRIPTION,
+ PROP_BINDING,
+ PROP_EDITABLE,
+ PROP_TYPE,
+ PROP_IS_VALUE_DEFAULT,
+ PROP_COMMAND
+};
+
+static void cc_keyboard_item_class_init (CcKeyboardItemClass *klass);
+static void cc_keyboard_item_init (CcKeyboardItem *keyboard_item);
+static void cc_keyboard_item_finalize (GObject *object);
+
+G_DEFINE_TYPE (CcKeyboardItem, cc_keyboard_item, G_TYPE_OBJECT)
+
+static const gchar *
+get_binding_from_variant (GVariant *variant)
+{
+ const char *str, **strv;
+
+ if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING))
+ return g_variant_get_string (variant, NULL);
+ else if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING_ARRAY))
+ return "";
+
+ strv = g_variant_get_strv (variant, NULL);
+ str = strv[0];
+ g_free (strv);
+
+ return str;
+}
+
+static gboolean
+binding_from_string (const char *str,
+ CcKeyCombo *combo)
+{
+ g_return_val_if_fail (combo != NULL, FALSE);
+ guint *keycodes;
+
+ if (str == NULL || strcmp (str, "disabled") == 0)
+ {
+ memset (combo, 0, sizeof(CcKeyCombo));
+ return TRUE;
+ }
+
+ gtk_accelerator_parse_with_keycode (str, &combo->keyval, &keycodes, &combo->mask);
+
+ combo->keycode = (keycodes ? keycodes[0] : 0);
+ g_free (keycodes);
+
+ if (combo->keyval == 0)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static void
+_set_description (CcKeyboardItem *item,
+ const char *value)
+{
+ g_free (item->description);
+ item->description = g_strdup (value);
+}
+
+const char *
+cc_keyboard_item_get_description (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL);
+
+ return item->description;
+}
+
+gboolean
+cc_keyboard_item_get_desc_editable (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), FALSE);
+
+ return item->desc_editable;
+}
+
+/* wrapper around g_settings_set_str[ing|v] */
+static void
+settings_set_binding (GSettings *settings,
+ const char *key,
+ const char *value)
+{
+ GVariant *variant;
+
+ variant = g_settings_get_value (settings, key);
+
+ if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING))
+ g_settings_set_string (settings, key, value ? value : "");
+ else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING_ARRAY))
+ {
+ if (value == NULL || *value == '\0')
+ g_settings_set_strv (settings, key, NULL);
+ else
+ {
+ char **str_array = g_new0 (char *, 2);
+
+ /* clear any additional bindings by only setting the first one */
+ *str_array = g_strdup (value);
+
+ g_settings_set_strv (settings, key, (const char * const *)str_array);
+ g_strfreev (str_array);
+ }
+ }
+
+ g_variant_unref (variant);
+}
+
+
+static void
+_set_binding (CcKeyboardItem *item,
+ const char *value,
+ gboolean set_backend)
+{
+ CcKeyboardItem *reverse;
+ gboolean enabled;
+
+ reverse = item->reverse_item;
+ enabled = value && strlen (value) > 0;
+
+ g_clear_pointer (&item->binding, g_free);
+ item->binding = enabled ? g_strdup (value) : g_strdup ("");
+
+ binding_from_string (item->binding, item->primary_combo);
+
+ /*
+ * Always treat the pair (item, reverse) as a unit: setting one also
+ * disables the other, disabling one up also sets the other.
+ */
+ if (reverse)
+ {
+ GdkModifierType reverse_mask;
+
+ reverse_mask = enabled ? item->primary_combo->mask ^ GDK_SHIFT_MASK
+ : item->primary_combo->mask;
+
+ g_clear_pointer (&reverse->binding, g_free);
+ if (enabled)
+ reverse->binding = gtk_accelerator_name_with_keycode (NULL,
+ item->primary_combo->keyval,
+ item->primary_combo->keycode,
+ reverse_mask);
+
+ binding_from_string (reverse->binding, reverse->primary_combo);
+ }
+
+ if (set_backend == FALSE)
+ return;
+
+ settings_set_binding (item->settings, item->key, item->binding);
+
+ g_object_notify (G_OBJECT (item), "is-value-default");
+
+ if (reverse)
+ {
+ settings_set_binding (reverse->settings, reverse->key, reverse->binding);
+ g_object_notify (G_OBJECT (reverse), "is-value-default");
+ }
+}
+
+static void
+_set_type (CcKeyboardItem *item,
+ gint value)
+{
+ item->type = value;
+}
+
+static void
+_set_command (CcKeyboardItem *item,
+ const char *value)
+{
+ g_free (item->command);
+ item->command = g_strdup (value);
+}
+
+const char *
+cc_keyboard_item_get_command (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL);
+
+ return item->command;
+}
+
+gboolean
+cc_keyboard_item_get_cmd_editable (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), FALSE);
+
+ return item->cmd_editable;
+}
+
+static void
+cc_keyboard_item_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcKeyboardItem *self;
+
+ self = CC_KEYBOARD_ITEM (object);
+
+ switch (prop_id) {
+ case PROP_DESCRIPTION:
+ _set_description (self, g_value_get_string (value));
+ break;
+ case PROP_BINDING:
+ _set_binding (self, g_value_get_string (value), TRUE);
+ break;
+ case PROP_COMMAND:
+ _set_command (self, g_value_get_string (value));
+ break;
+ case PROP_TYPE:
+ _set_type (self, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_keyboard_item_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcKeyboardItem *self;
+
+ self = CC_KEYBOARD_ITEM (object);
+
+ switch (prop_id) {
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, self->description);
+ break;
+ case PROP_BINDING:
+ g_value_set_string (value, self->binding);
+ break;
+ case PROP_EDITABLE:
+ g_value_set_boolean (value, self->editable);
+ break;
+ case PROP_COMMAND:
+ g_value_set_string (value, self->command);
+ break;
+ case PROP_IS_VALUE_DEFAULT:
+ g_value_set_boolean (value, cc_keyboard_item_is_value_default (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_keyboard_item_class_init (CcKeyboardItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = cc_keyboard_item_get_property;
+ object_class->set_property = cc_keyboard_item_set_property;
+ object_class->finalize = cc_keyboard_item_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_DESCRIPTION,
+ g_param_spec_string ("description",
+ "description",
+ "description",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_BINDING,
+ g_param_spec_string ("binding",
+ "binding",
+ "binding",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_EDITABLE,
+ g_param_spec_boolean ("editable",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_TYPE,
+ g_param_spec_int ("type",
+ NULL,
+ NULL,
+ CC_KEYBOARD_ITEM_TYPE_NONE,
+ CC_KEYBOARD_ITEM_TYPE_GSETTINGS,
+ CC_KEYBOARD_ITEM_TYPE_NONE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_COMMAND,
+ g_param_spec_string ("command",
+ "command",
+ "command",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_IS_VALUE_DEFAULT,
+ g_param_spec_boolean ("is-value-default",
+ "is value default",
+ "is value default",
+ TRUE,
+ G_PARAM_READABLE));
+}
+
+static void
+cc_keyboard_item_init (CcKeyboardItem *item)
+{
+ item->primary_combo = g_new0 (CcKeyCombo, 1);
+}
+
+static void
+cc_keyboard_item_finalize (GObject *object)
+{
+ CcKeyboardItem *item;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (CC_IS_KEYBOARD_ITEM (object));
+
+ item = CC_KEYBOARD_ITEM (object);
+
+ if (item->settings != NULL)
+ g_object_unref (item->settings);
+
+ /* Free memory */
+ g_free (item->binding);
+ g_free (item->primary_combo);
+ g_free (item->gsettings_path);
+ g_free (item->description);
+ g_free (item->command);
+ g_free (item->schema);
+ g_free (item->key);
+ g_list_free_full (item->key_combos, g_free);
+ g_list_free_full (item->default_combos, g_free);
+
+ G_OBJECT_CLASS (cc_keyboard_item_parent_class)->finalize (object);
+}
+
+CcKeyboardItem *
+cc_keyboard_item_new (CcKeyboardItemType type)
+{
+ GObject *object;
+
+ object = g_object_new (CC_TYPE_KEYBOARD_ITEM,
+ "type", type,
+ NULL);
+
+ return CC_KEYBOARD_ITEM (object);
+}
+
+static guint *
+get_above_tab_keysyms (void)
+{
+ GdkKeymap *keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
+ guint keycode = 0x29 /* KEY_GRAVE */ + 8;
+ g_autofree guint *keyvals = NULL;
+ GArray *keysyms;
+ int n_entries, i, j;
+
+ keysyms = g_array_new (TRUE, FALSE, sizeof (guint));
+
+ if (!gdk_keymap_get_entries_for_keycode (keymap, keycode, NULL, &keyvals, &n_entries))
+ goto out;
+
+ for (i = 0; i < n_entries; i++)
+ {
+ gboolean found = FALSE;
+
+ for (j = 0; j < keysyms->len; j++)
+ if (g_array_index (keysyms, guint, j) == keyvals[i])
+ {
+ found = TRUE;
+ break;
+ }
+
+ if (!found)
+ g_array_append_val (keysyms, keyvals[i]);
+ }
+
+out:
+ return (guint *)g_array_free (keysyms, FALSE);
+}
+
+/*
+ * translate_above_tab:
+ *
+ * @original_bindings: A list of accelerator strings
+ * @new_bindings: (out): Translated bindings if translation is needed
+ *
+ * Translate accelerator strings that contain the Above_Tab fake keysym
+ * used by mutter to strings that use the real keysyms that correspond
+ * to the key that is located physically above the tab key.
+ *
+ * Returns: %TRUE if strings were translated, %FALSE if @original_bindings
+ * can be used unmodified
+ */
+static gboolean
+translate_above_tab (char **original_bindings,
+ char ***new_bindings)
+{
+ GPtrArray *replaced_bindings;
+ g_autofree guint *above_tab_keysyms = NULL;
+ gboolean needs_translation = FALSE;
+ char **str;
+
+ for (str = original_bindings; *str && !needs_translation; str++)
+ needs_translation = strstr (*str, "Above_Tab") != NULL;
+
+ if (!needs_translation)
+ return FALSE;
+
+ above_tab_keysyms = get_above_tab_keysyms ();
+
+ replaced_bindings = g_ptr_array_new ();
+
+ for (str = original_bindings; *str; str++)
+ {
+ if (strstr (*str, "Above_Tab") == NULL)
+ {
+ g_ptr_array_add (replaced_bindings, g_strdup (*str));
+ }
+ else
+ {
+ g_auto (GStrv) split_str = g_strsplit (*str, "Above_Tab", -1);
+ int i;
+
+ for (i = 0; above_tab_keysyms[i]; i++)
+ {
+ g_autofree char *sym = NULL;
+
+ sym = gtk_accelerator_name (above_tab_keysyms[i], 0);
+ g_ptr_array_add (replaced_bindings, g_strjoinv (sym, split_str));
+ }
+ }
+ g_ptr_array_add (replaced_bindings, NULL);
+ }
+
+ *new_bindings = (char **)g_ptr_array_free (replaced_bindings, FALSE);
+ return TRUE;
+}
+
+static char *
+translate_binding_string (const char *str)
+{
+ g_autofree guint *above_tab_keysyms = NULL;
+ g_autofree char *symname = NULL;
+ g_auto (GStrv) split_str = NULL;
+
+ if (str == NULL || strstr (str, "Above_Tab") == NULL)
+ return g_strdup (str);
+
+ above_tab_keysyms = get_above_tab_keysyms ();
+ symname = gtk_accelerator_name (above_tab_keysyms[0], 0);
+
+ split_str = g_strsplit (str, "Above_Tab", -1);
+ return g_strjoinv (symname, split_str);
+}
+
+static GList *
+variant_get_key_combos (GVariant *variant)
+{
+ GList *combos = NULL;
+ char **bindings = NULL, **translated_bindings, **str;
+
+ if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING))
+ {
+ bindings = g_malloc0_n (2, sizeof(char *));
+ bindings[0] = g_variant_dup_string (variant, NULL);
+ }
+ else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING_ARRAY))
+ {
+ bindings = g_variant_dup_strv (variant, NULL);
+ }
+
+ if (translate_above_tab (bindings, &translated_bindings))
+ {
+ g_strfreev (bindings);
+ bindings = translated_bindings;
+ }
+
+ for (str = bindings; *str; str++)
+ {
+ CcKeyCombo *combo = g_new (CcKeyCombo, 1);
+
+ binding_from_string (*str, combo);
+ combos = g_list_prepend (combos, combo);
+ }
+ g_strfreev (bindings);
+
+ return g_list_reverse (combos);
+}
+
+static GList *
+settings_get_key_combos (GSettings *settings,
+ const char *key,
+ gboolean use_default)
+{
+ GList *key_combos;
+ GVariant *variant;
+
+ if (use_default)
+ variant = g_settings_get_default_value (settings, key);
+ else
+ variant = g_settings_get_value (settings, key);
+ key_combos = variant_get_key_combos (variant);
+ g_variant_unref (variant);
+
+ return key_combos;
+}
+
+/* wrapper around g_settings_get_str[ing|v] */
+static char *
+settings_get_binding (GSettings *settings,
+ const char *key)
+{
+ GVariant *variant;
+ char *value = NULL;
+
+ variant = g_settings_get_value (settings, key);
+ if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING))
+ value = translate_binding_string (g_variant_get_string (variant, NULL));
+ else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING_ARRAY))
+ {
+ const char **str_array;
+
+ str_array = g_variant_get_strv (variant, NULL);
+ value = translate_binding_string (str_array[0]);
+ g_free (str_array);
+ }
+ g_variant_unref (variant);
+
+ return value;
+}
+
+static void
+binding_changed (CcKeyboardItem *item,
+ const char *key)
+{
+ char *value;
+
+ g_list_free_full (item->key_combos, g_free);
+ item->key_combos = settings_get_key_combos (item->settings, item->key, FALSE);
+
+ value = settings_get_binding (item->settings, item->key);
+ item->editable = g_settings_is_writable (item->settings, item->key);
+ _set_binding (item, value, FALSE);
+ g_free (value);
+ g_object_notify (G_OBJECT (item), "binding");
+}
+
+gboolean
+cc_keyboard_item_load_from_gsettings_path (CcKeyboardItem *item,
+ const char *path,
+ gboolean reset)
+{
+ item->schema = g_strdup (CUSTOM_KEYS_SCHEMA);
+ item->gsettings_path = g_strdup (path);
+ item->key = g_strdup ("binding");
+ item->settings = g_settings_new_with_path (item->schema, path);
+ item->editable = g_settings_is_writable (item->settings, item->key);
+ item->desc_editable = g_settings_is_writable (item->settings, "name");
+ item->cmd_editable = g_settings_is_writable (item->settings, "command");
+
+ if (reset)
+ {
+ g_settings_reset (item->settings, "name");
+ g_settings_reset (item->settings, "command");
+ g_settings_reset (item->settings, "binding");
+ }
+
+ g_settings_bind (item->settings, "name",
+ G_OBJECT (item), "description", G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (item->settings, "command",
+ G_OBJECT (item), "command", G_SETTINGS_BIND_DEFAULT);
+
+ g_list_free_full (item->key_combos, g_free);
+ item->key_combos = settings_get_key_combos (item->settings, item->key, FALSE);
+
+ g_free (item->binding);
+ item->binding = settings_get_binding (item->settings, item->key);
+ binding_from_string (item->binding, item->primary_combo);
+ g_signal_connect_object (G_OBJECT (item->settings), "changed::binding",
+ G_CALLBACK (binding_changed), item, G_CONNECT_SWAPPED);
+
+ return TRUE;
+}
+
+gboolean
+cc_keyboard_item_load_from_gsettings (CcKeyboardItem *item,
+ const char *description,
+ const char *schema,
+ const char *key)
+{
+ char *signal_name;
+
+ item->schema = g_strdup (schema);
+ item->key = g_strdup (key);
+ item->description = g_strdup (description);
+
+ item->settings = g_settings_new (item->schema);
+ g_free (item->binding);
+ item->binding = settings_get_binding (item->settings, item->key);
+ item->editable = g_settings_is_writable (item->settings, item->key);
+ binding_from_string (item->binding, item->primary_combo);
+
+ g_list_free_full (item->key_combos, g_free);
+ item->key_combos = settings_get_key_combos (item->settings, item->key, FALSE);
+
+ g_list_free_full (item->default_combos, g_free);
+ item->default_combos = settings_get_key_combos (item->settings, item->key, TRUE);
+
+ signal_name = g_strdup_printf ("changed::%s", item->key);
+ g_signal_connect_object (G_OBJECT (item->settings), signal_name,
+ G_CALLBACK (binding_changed), item, G_CONNECT_SWAPPED);
+ g_free (signal_name);
+
+ return TRUE;
+}
+
+gboolean
+cc_keyboard_item_equal (CcKeyboardItem *a,
+ CcKeyboardItem *b)
+{
+ if (a->type != b->type)
+ return FALSE;
+ switch (a->type)
+ {
+ case CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH:
+ return g_str_equal (a->gsettings_path, b->gsettings_path);
+ case CC_KEYBOARD_ITEM_TYPE_GSETTINGS:
+ return (g_str_equal (a->schema, b->schema) &&
+ g_str_equal (a->key, b->key));
+ default:
+ g_assert_not_reached ();
+ }
+
+}
+
+void
+cc_keyboard_item_add_reverse_item (CcKeyboardItem *item,
+ CcKeyboardItem *reverse_item,
+ gboolean is_reversed)
+{
+ g_return_if_fail (item->key != NULL);
+
+ item->reverse_item = reverse_item;
+ if (reverse_item->reverse_item == NULL)
+ {
+ reverse_item->reverse_item = item;
+ reverse_item->is_reversed = !is_reversed;
+ }
+ else
+ g_warn_if_fail (reverse_item->is_reversed == !!is_reversed);
+
+ item->is_reversed = !!is_reversed;
+}
+
+CcKeyboardItem *
+cc_keyboard_item_get_reverse_item (CcKeyboardItem *item)
+{
+ return item->reverse_item;
+}
+
+
+void
+cc_keyboard_item_set_hidden (CcKeyboardItem *item, gboolean hidden)
+{
+ item->hidden = !!hidden;
+}
+
+
+gboolean
+cc_keyboard_item_is_hidden (CcKeyboardItem *item)
+{
+ return item->hidden;
+}
+
+/**
+ * cc_keyboard_item_is_value_default:
+ * @self: a #CcKeyboardItem
+ *
+ * Retrieves whether the shortcut is the default value or not.
+ *
+ * Returns: %TRUE if the shortcut is the default value, %FALSE otherwise.
+ */
+gboolean
+cc_keyboard_item_is_value_default (CcKeyboardItem *self)
+{
+ GVariant *user_value;
+ gboolean is_value_default;
+
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (self), FALSE);
+
+ /*
+ * When the shortcut is custom, we don't treat it as modified
+ * since we don't know what would be its default value.
+ */
+ if (self->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
+ return TRUE;
+
+ user_value = g_settings_get_user_value (self->settings, self->key);
+
+ is_value_default = TRUE;
+
+ if (user_value)
+ {
+ GVariant *default_value;
+ const gchar *default_binding, *user_binding;
+
+ default_value = g_settings_get_default_value (self->settings, self->key);
+
+ default_binding = get_binding_from_variant (default_value);
+ user_binding = get_binding_from_variant (user_value);
+
+ is_value_default = (g_strcmp0 (default_binding, user_binding) == 0);
+
+ g_clear_pointer (&default_value, g_variant_unref);
+ }
+
+ g_clear_pointer (&user_value, g_variant_unref);
+
+ return is_value_default;
+}
+
+/**
+ * cc_keyboard_item_reset:
+ * @self: a #CcKeyboardItem
+ *
+ * Reset the keyboard binding to the default value.
+ */
+void
+cc_keyboard_item_reset (CcKeyboardItem *self)
+{
+ CcKeyboardItem *reverse;
+
+ g_return_if_fail (CC_IS_KEYBOARD_ITEM (self));
+
+ reverse = self->reverse_item;
+
+ g_settings_reset (self->settings, self->key);
+ g_object_notify (G_OBJECT (self), "is-value-default");
+
+ /* Also reset the reverse item */
+ if (reverse)
+ {
+ g_settings_reset (reverse->settings, reverse->key);
+ g_object_notify (G_OBJECT (reverse), "is-value-default");
+ }
+}
+
+GList *
+cc_keyboard_item_get_key_combos (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL);
+ return item->key_combos;
+}
+
+GList *
+cc_keyboard_item_get_default_combos (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL);
+ return item->default_combos;
+}
+
+CcKeyCombo *
+cc_keyboard_item_get_primary_combo (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL);
+ return item->primary_combo;
+}
+
+const gchar *
+cc_keyboard_item_get_key (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL);
+ return item->key;
+}
+
+CcKeyboardItemType
+cc_keyboard_item_get_item_type (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), CC_KEYBOARD_ITEM_TYPE_NONE);
+ return item->type;
+}
+
+const gchar *
+cc_keyboard_item_get_gsettings_path (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL);
+ return item->gsettings_path;
+}
+
+GSettings *
+cc_keyboard_item_get_settings (CcKeyboardItem *item)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_ITEM (item), NULL);
+ return item->settings;
+}
+
diff --git a/panels/keyboard/cc-keyboard-item.h b/panels/keyboard/cc-keyboard-item.h
new file mode 100644
index 0000000..2c344a7
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-item.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Red Hat, 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/>.
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_KEYBOARD_ITEM (cc_keyboard_item_get_type ())
+G_DECLARE_FINAL_TYPE (CcKeyboardItem, cc_keyboard_item, CC, KEYBOARD_ITEM, GObject)
+
+typedef enum
+{
+ BINDING_GROUP_SYSTEM,
+ BINDING_GROUP_APPS,
+ BINDING_GROUP_SEPARATOR,
+ BINDING_GROUP_USER,
+} BindingGroupType;
+
+typedef enum
+{
+ CC_KEYBOARD_ITEM_TYPE_NONE = 0,
+ CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH,
+ CC_KEYBOARD_ITEM_TYPE_GSETTINGS
+} CcKeyboardItemType;
+
+typedef struct
+{
+ guint keyval;
+ guint keycode;
+ GdkModifierType mask;
+} CcKeyCombo;
+
+CcKeyboardItem* cc_keyboard_item_new (CcKeyboardItemType type);
+
+gboolean cc_keyboard_item_load_from_gsettings_path (CcKeyboardItem *item,
+ const char *path,
+ gboolean reset);
+
+gboolean cc_keyboard_item_load_from_gsettings (CcKeyboardItem *item,
+ const char *description,
+ const char *schema,
+ const char *key);
+
+const char* cc_keyboard_item_get_description (CcKeyboardItem *item);
+
+gboolean cc_keyboard_item_get_desc_editable (CcKeyboardItem *item);
+
+const char* cc_keyboard_item_get_command (CcKeyboardItem *item);
+
+gboolean cc_keyboard_item_get_cmd_editable (CcKeyboardItem *item);
+
+gboolean cc_keyboard_item_equal (CcKeyboardItem *a,
+ CcKeyboardItem *b);
+
+void cc_keyboard_item_add_reverse_item (CcKeyboardItem *item,
+ CcKeyboardItem *reverse_item,
+ gboolean is_reversed);
+
+CcKeyboardItem* cc_keyboard_item_get_reverse_item (CcKeyboardItem *item);
+
+void cc_keyboard_item_set_hidden (CcKeyboardItem *item,
+ gboolean hidden);
+
+gboolean cc_keyboard_item_is_hidden (CcKeyboardItem *item);
+
+gboolean cc_keyboard_item_is_value_default (CcKeyboardItem *self);
+
+void cc_keyboard_item_reset (CcKeyboardItem *self);
+
+GList* cc_keyboard_item_get_key_combos (CcKeyboardItem *self);
+
+GList* cc_keyboard_item_get_default_combos (CcKeyboardItem *self);
+
+CcKeyCombo* cc_keyboard_item_get_primary_combo (CcKeyboardItem *self);
+
+const gchar* cc_keyboard_item_get_key (CcKeyboardItem *self);
+
+CcKeyboardItemType cc_keyboard_item_get_item_type (CcKeyboardItem *self);
+
+const gchar* cc_keyboard_item_get_gsettings_path (CcKeyboardItem *self);
+
+GSettings* cc_keyboard_item_get_settings (CcKeyboardItem *self);
+
+G_END_DECLS
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);
+}
diff --git a/panels/keyboard/cc-keyboard-manager.h b/panels/keyboard/cc-keyboard-manager.h
new file mode 100644
index 0000000..2b03752
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-manager.h
@@ -0,0 +1,57 @@
+/*
+ * 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>
+ *
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "cc-keyboard-item.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_KEYBOARD_MANAGER (cc_keyboard_manager_get_type ())
+G_DECLARE_FINAL_TYPE (CcKeyboardManager, cc_keyboard_manager, CC, KEYBOARD_MANAGER, GObject)
+
+CcKeyboardManager* cc_keyboard_manager_new (void);
+
+void cc_keyboard_manager_load_shortcuts (CcKeyboardManager *self);
+
+CcKeyboardItem* cc_keyboard_manager_create_custom_shortcut (CcKeyboardManager *self);
+
+void cc_keyboard_manager_add_custom_shortcut (CcKeyboardManager *self,
+ CcKeyboardItem *item);
+
+void cc_keyboard_manager_remove_custom_shortcut (CcKeyboardManager *self,
+ CcKeyboardItem *item);
+
+CcKeyboardItem* cc_keyboard_manager_get_collision (CcKeyboardManager *self,
+ CcKeyboardItem *item,
+ CcKeyCombo *combo);
+
+void cc_keyboard_manager_disable_shortcut (CcKeyboardManager *self,
+ CcKeyboardItem *item);
+
+void cc_keyboard_manager_reset_shortcut (CcKeyboardManager *self,
+ CcKeyboardItem *item);
+
+G_END_DECLS
+
diff --git a/panels/keyboard/cc-keyboard-option.c b/panels/keyboard/cc-keyboard-option.c
new file mode 100644
index 0000000..5730fdc
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-option.c
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Written by: Rui Matos <rmatos@redhat.com>
+ *
+ * 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, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-xkb-info.h>
+
+#include "cc-keyboard-option.h"
+
+#define INPUT_SOURCES_SCHEMA "org.gnome.desktop.input-sources"
+#define XKB_OPTIONS_KEY "xkb-options"
+
+#define XKB_OPTION_GROUP_LVL3 "lv3"
+#define XKB_OPTION_GROUP_COMP "Compose key"
+#define XKB_OPTION_GROUP_GRP "grp"
+
+enum
+{
+ PROP_0,
+ PROP_GROUP,
+ PROP_DESCRIPTION
+};
+
+enum
+{
+ CHANGED_SIGNAL,
+ LAST_SIGNAL
+};
+
+struct _CcKeyboardOption
+{
+ gchar *group;
+ gchar *description;
+ gchar *current_value;
+ GtkListStore *store;
+
+ const gchar * const *allowed_xkb_options;
+};
+
+G_DEFINE_TYPE (CcKeyboardOption, cc_keyboard_option, G_TYPE_OBJECT);
+
+static guint keyboard_option_signals[LAST_SIGNAL] = { 0 };
+
+static GnomeXkbInfo *xkb_info = NULL;
+static GSettings *input_sources_settings = NULL;
+static gchar **current_xkb_options = NULL;
+
+static const gchar *allowed_xkb_lvl3_options[] = {
+ "lv3:switch",
+ "lv3:menu_switch",
+ "lv3:rwin_switch",
+ "lv3:lalt_switch",
+ "lv3:ralt_switch",
+ "lv3:caps_switch",
+ "lv3:enter_switch",
+ "lv3:bksl_switch",
+ "lv3:lsgt_switch",
+ NULL
+};
+
+static const gchar *allowedd_xkb_compose_options[] = {
+ "compose:ralt",
+ "compose:rwin",
+ "compose:menu",
+ "compose:lctrl",
+ "compose:rctrl",
+ "compose:caps",
+ "compose:prsc",
+ "compose:sclk",
+ NULL
+};
+
+/* This list must be kept in sync with what mutter is able to
+ * handle. */
+static const gchar *allowed_xkb_grp_options[] = {
+ "grp:toggle",
+ "grp:lalt_toggle",
+ "grp:lwin_toggle",
+ "grp:rwin_toggle",
+ "grp:lshift_toggle",
+ "grp:rshift_toggle",
+ "grp:lctrl_toggle",
+ "grp:rctrl_toggle",
+ "grp:sclk_toggle",
+ "grp:menu_toggle",
+ "grp:caps_toggle",
+ "grp:shift_caps_toggle",
+ "grp:alt_caps_toggle",
+ "grp:alt_space_toggle",
+ "grp:ctrl_shift_toggle",
+ "grp:lctrl_lshift_toggle",
+ "grp:rctrl_rshift_toggle",
+ "grp:ctrl_alt_toggle",
+ "grp:alt_shift_toggle",
+ "grp:lalt_lshift_toggle",
+ NULL
+};
+
+static GList *objects_list = NULL;
+
+static gboolean
+strv_contains (const gchar * const *strv,
+ const gchar *str)
+{
+ const gchar * const *p = strv;
+ for (p = strv; *p; p++)
+ if (g_strcmp0 (*p, str) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+reload_setting (CcKeyboardOption *self)
+{
+ gchar **iter;
+
+ for (iter = current_xkb_options; *iter; ++iter)
+ if (strv_contains (self->allowed_xkb_options, *iter))
+ {
+ if (g_strcmp0 (self->current_value, *iter) != 0)
+ {
+ g_free (self->current_value);
+ self->current_value = g_strdup (*iter);
+ g_signal_emit (self, keyboard_option_signals[CHANGED_SIGNAL], 0);
+ }
+ break;
+ }
+
+ if (*iter == NULL && self->current_value != NULL)
+ {
+ g_clear_pointer (&self->current_value, g_free);
+ g_signal_emit (self, keyboard_option_signals[CHANGED_SIGNAL], 0);
+ }
+}
+
+static void
+xkb_options_changed (GSettings *settings,
+ gchar *key,
+ gpointer data)
+{
+ GList *l;
+
+ g_strfreev (current_xkb_options);
+ current_xkb_options = g_settings_get_strv (settings, key);
+
+ for (l = objects_list; l; l = l->next)
+ reload_setting (CC_KEYBOARD_OPTION (l->data));
+}
+
+static void
+cc_keyboard_option_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcKeyboardOption *self;
+
+ self = CC_KEYBOARD_OPTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_GROUP:
+ g_value_set_string (value, self->group);
+ break;
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, self->description);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_keyboard_option_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcKeyboardOption *self;
+
+ self = CC_KEYBOARD_OPTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_GROUP:
+ self->group = g_value_dup_string (value);
+ break;
+ case PROP_DESCRIPTION:
+ self->description = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_keyboard_option_init (CcKeyboardOption *self)
+{
+}
+
+static void
+cc_keyboard_option_finalize (GObject *object)
+{
+ CcKeyboardOption *self = CC_KEYBOARD_OPTION (object);
+
+ g_clear_pointer (&self->group, g_free);
+ g_clear_pointer (&self->description, g_free);
+ g_clear_pointer (&self->current_value, g_free);
+ g_clear_object (&self->store);
+
+ G_OBJECT_CLASS (cc_keyboard_option_parent_class)->finalize (object);
+}
+
+static void
+cc_keyboard_option_constructed (GObject *object)
+{
+ GtkTreeIter iter;
+ GList *options, *l;
+ gchar *option_id;
+ CcKeyboardOption *self = CC_KEYBOARD_OPTION (object);
+
+ G_OBJECT_CLASS (cc_keyboard_option_parent_class)->constructed (object);
+
+ if (g_str_equal (self->group, XKB_OPTION_GROUP_LVL3))
+ self->allowed_xkb_options = allowed_xkb_lvl3_options;
+ else if (g_str_equal (self->group, XKB_OPTION_GROUP_COMP))
+ self->allowed_xkb_options = allowedd_xkb_compose_options;
+ else if (g_str_equal (self->group, XKB_OPTION_GROUP_GRP))
+ self->allowed_xkb_options = allowed_xkb_grp_options;
+ else
+ g_assert_not_reached ();
+
+ self->store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_list_store_append (self->store, &iter);
+ gtk_list_store_set (self->store, &iter,
+ XKB_OPTION_DESCRIPTION_COLUMN, _("Disabled"),
+ XKB_OPTION_ID_COLUMN, NULL,
+ -1);
+ options = gnome_xkb_info_get_options_for_group (xkb_info, self->group);
+ for (l = options; l; l = l->next)
+ {
+ option_id = l->data;
+ if (strv_contains (self->allowed_xkb_options, option_id))
+ {
+ gtk_list_store_append (self->store, &iter);
+ gtk_list_store_set (self->store, &iter,
+ XKB_OPTION_DESCRIPTION_COLUMN,
+ gnome_xkb_info_description_for_option (xkb_info, self->group, option_id),
+ XKB_OPTION_ID_COLUMN,
+ option_id,
+ -1);
+ }
+ }
+ g_list_free (options);
+
+ reload_setting (self);
+}
+
+static void
+cc_keyboard_option_class_init (CcKeyboardOptionClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = cc_keyboard_option_get_property;
+ gobject_class->set_property = cc_keyboard_option_set_property;
+ gobject_class->finalize = cc_keyboard_option_finalize;
+ gobject_class->constructed = cc_keyboard_option_constructed;
+
+ g_object_class_install_property (gobject_class,
+ PROP_GROUP,
+ g_param_spec_string ("group",
+ "group",
+ "xkb option group identifier",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+ g_object_class_install_property (gobject_class,
+ PROP_DESCRIPTION,
+ g_param_spec_string ("description",
+ "description",
+ "translated option description",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+ keyboard_option_signals[CHANGED_SIGNAL] = g_signal_new ("changed",
+ CC_TYPE_KEYBOARD_OPTION,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+GList *
+cc_keyboard_option_get_all (void)
+{
+ if (objects_list)
+ return objects_list;
+
+ xkb_info = gnome_xkb_info_new ();
+
+ input_sources_settings = g_settings_new (INPUT_SOURCES_SCHEMA);
+
+ g_signal_connect (input_sources_settings, "changed::" XKB_OPTIONS_KEY,
+ G_CALLBACK (xkb_options_changed), NULL);
+
+ xkb_options_changed (input_sources_settings, XKB_OPTIONS_KEY, NULL);
+
+ objects_list = g_list_prepend (objects_list,
+ g_object_new (CC_TYPE_KEYBOARD_OPTION,
+ "group", XKB_OPTION_GROUP_LVL3,
+ /* Translators: This key is also known as 'third level
+ * chooser'. AltGr is often used for this purpose. See
+ * https://live.gnome.org/Design/SystemSettings/RegionAndLanguage
+ */
+ "description", _("Alternative Characters Key"),
+ NULL));
+ objects_list = g_list_prepend (objects_list,
+ g_object_new (CC_TYPE_KEYBOARD_OPTION,
+ "group", XKB_OPTION_GROUP_COMP,
+ /* Translators: The Compose key is used to initiate key
+ * sequences that are combined to form a single character.
+ * See http://en.wikipedia.org/wiki/Compose_key
+ */
+ "description", _("Compose Key"),
+ NULL));
+ objects_list = g_list_prepend (objects_list,
+ g_object_new (CC_TYPE_KEYBOARD_OPTION,
+ "group", XKB_OPTION_GROUP_GRP,
+ "description", _("Modifiers-only switch to next source"),
+ NULL));
+ return objects_list;
+}
+
+const gchar *
+cc_keyboard_option_get_description (CcKeyboardOption *self)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_OPTION (self), NULL);
+
+ return self->description;
+}
+
+GtkListStore *
+cc_keyboard_option_get_store (CcKeyboardOption *self)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_OPTION (self), NULL);
+
+ return self->store;
+}
+
+const gchar *
+cc_keyboard_option_get_current_value_description (CcKeyboardOption *self)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_OPTION (self), NULL);
+
+ if (!self->current_value)
+ return _("Disabled");
+
+ return gnome_xkb_info_description_for_option (xkb_info, self->group, self->current_value);
+}
+
+static void
+remove_value (const gchar *value)
+{
+ gchar **p;
+
+ for (p = current_xkb_options; *p; ++p)
+ if (g_str_equal (*p, value))
+ {
+ g_free (*p);
+ break;
+ }
+
+ for (++p; *p; ++p)
+ *(p - 1) = *p;
+
+ *(p - 1) = NULL;
+}
+
+static void
+add_value (const gchar *value)
+{
+ gchar **new_xkb_options;
+ gchar **a, **b;
+
+ new_xkb_options = g_new0 (gchar *, g_strv_length (current_xkb_options) + 2);
+
+ a = new_xkb_options;
+ for (b = current_xkb_options; *b; ++a, ++b)
+ *a = g_strdup (*b);
+
+ *a = g_strdup (value);
+
+ g_strfreev (current_xkb_options);
+ current_xkb_options = new_xkb_options;
+}
+
+static void
+replace_value (const gchar *old,
+ const gchar *new)
+{
+ gchar **iter;
+
+ if (g_str_equal (old, new))
+ return;
+
+ for (iter = current_xkb_options; *iter; ++iter)
+ if (g_str_equal (*iter, old))
+ {
+ g_free (*iter);
+ *iter = g_strdup (new);
+ break;
+ }
+}
+
+void
+cc_keyboard_option_set_selection (CcKeyboardOption *self,
+ GtkTreeIter *iter)
+{
+ g_autofree gchar *new_value = NULL;
+
+ g_return_if_fail (CC_IS_KEYBOARD_OPTION (self));
+
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store), iter,
+ XKB_OPTION_ID_COLUMN, &new_value,
+ -1);
+
+ if (!new_value)
+ {
+ if (self->current_value)
+ remove_value (self->current_value);
+ }
+ else
+ {
+ if (self->current_value)
+ replace_value (self->current_value, new_value);
+ else
+ add_value (new_value);
+ }
+
+ g_settings_set_strv (input_sources_settings, XKB_OPTIONS_KEY,
+ (const gchar * const *) current_xkb_options);
+}
+
+void
+cc_keyboard_option_clear_all (void)
+{
+ GList *l;
+
+ for (l = objects_list; l; l = l->next)
+ g_object_unref (l->data);
+
+ g_clear_pointer (&objects_list, g_list_free);
+ g_clear_pointer (&current_xkb_options, g_strfreev);
+ g_clear_object (&input_sources_settings);
+ g_clear_object (&xkb_info);
+}
diff --git a/panels/keyboard/cc-keyboard-option.h b/panels/keyboard/cc-keyboard-option.h
new file mode 100644
index 0000000..8bd0cd6
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-option.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Written by: Rui Matos <rmatos@redhat.com>
+ *
+ * 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, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_KEYBOARD_OPTION (cc_keyboard_option_get_type ())
+G_DECLARE_FINAL_TYPE (CcKeyboardOption, cc_keyboard_option, CC, KEYBOARD_OPTION, GObject)
+
+enum
+{
+ XKB_OPTION_DESCRIPTION_COLUMN,
+ XKB_OPTION_ID_COLUMN,
+ XKB_OPTION_N_COLUMNS
+};
+
+GList * cc_keyboard_option_get_all (void);
+const gchar * cc_keyboard_option_get_description (CcKeyboardOption *self);
+GtkListStore * cc_keyboard_option_get_store (CcKeyboardOption *self);
+const gchar * cc_keyboard_option_get_current_value_description (CcKeyboardOption *self);
+void cc_keyboard_option_set_selection (CcKeyboardOption *self,
+ GtkTreeIter *iter);
+void cc_keyboard_option_clear_all (void);
+
+G_END_DECLS
diff --git a/panels/keyboard/cc-keyboard-panel.c b/panels/keyboard/cc-keyboard-panel.c
new file mode 100644
index 0000000..f47e34c
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-panel.c
@@ -0,0 +1,844 @@
+/*
+ * 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 <gbsneto@gnome.org>
+ *
+ */
+
+#include <glib/gi18n.h>
+
+#include "cc-alt-chars-key-dialog.h"
+#include "cc-keyboard-item.h"
+#include "cc-keyboard-manager.h"
+#include "cc-keyboard-option.h"
+#include "cc-keyboard-panel.h"
+#include "cc-keyboard-resources.h"
+#include "cc-keyboard-shortcut-editor.h"
+
+#include "keyboard-shortcuts.h"
+
+#include "cc-util.h"
+
+#define SHORTCUT_DELIMITERS "+ "
+
+typedef struct {
+ CcKeyboardItem *item;
+ gchar *section_title;
+ gchar *section_id;
+} RowData;
+
+struct _CcKeyboardPanel
+{
+ CcPanel parent_instance;
+
+ /* Search */
+ GtkWidget *empty_search_placeholder;
+ GtkWidget *reset_button;
+ GtkWidget *search_bar;
+ GtkWidget *search_button;
+ GtkWidget *search_entry;
+ guint search_bar_handler_id;
+
+ /* Shortcuts */
+ GtkWidget *shortcuts_listbox;
+ GtkListBoxRow *add_shortcut_row;
+ GtkSizeGroup *accelerator_sizegroup;
+
+ /* Alternate characters key */
+ CcAltCharsKeyDialog *alt_chars_key_dialog;
+ GSettings *input_source_settings;
+ GtkWidget *value_alternate_chars;
+
+ /* Custom shortcut dialog */
+ GtkWidget *shortcut_editor;
+
+ GRegex *pictures_regex;
+
+ CcKeyboardManager *manager;
+};
+
+CC_PANEL_REGISTER (CcKeyboardPanel, cc_keyboard_panel)
+
+enum {
+ PROP_0,
+ PROP_PARAMETERS
+};
+
+static const gchar* custom_css =
+"button.reset-shortcut-button {"
+" padding: 0;"
+"}";
+
+
+#define DEFAULT_LV3_OPTION 5
+static struct {
+ const char *xkb_option;
+ const char *label;
+ const char *widget_name;
+} lv3_xkb_options[] = {
+ { "lv3:switch", NC_("keyboard key", "Right Ctrl"), "radiobutton_rightctrl" },
+ { "lv3:menu_switch", NC_("keyboard key", "Menu Key"), "radiobutton_menukey" },
+ { "lv3:lwin_switch", NC_("keyboard key", "Left Super"), "radiobutton_leftsuper" },
+ { "lv3:rwin_switch", NC_("keyboard key", "Right Super"), "radiobutton_rightsuper" },
+ { "lv3:lalt_switch", NC_("keyboard key", "Left Alt"), "radiobutton_leftalt" },
+ { "lv3:ralt_switch", NC_("keyboard key", "Right Alt"), "radiobutton_rightalt" },
+};
+
+/* RowData functions */
+static RowData *
+row_data_new (CcKeyboardItem *item,
+ const gchar *section_id,
+ const gchar *section_title)
+{
+ RowData *data;
+
+ data = g_new0 (RowData, 1);
+ data->item = g_object_ref (item);
+ data->section_id = g_strdup (section_id);
+ data->section_title = g_strdup (section_title);
+
+ return data;
+}
+
+static void
+row_data_free (RowData *data)
+{
+ g_object_unref (data->item);
+ g_free (data->section_id);
+ g_free (data->section_title);
+ g_free (data);
+}
+
+static gboolean
+transform_binding_to_accel (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ CcKeyboardItem *item;
+ CcKeyCombo *combo;
+ gchar *accelerator;
+
+ item = CC_KEYBOARD_ITEM (g_binding_get_source (binding));
+ combo = cc_keyboard_item_get_primary_combo (item);
+
+ /* Embolden the label when the shortcut is modified */
+ if (!cc_keyboard_item_is_value_default (item))
+ {
+ g_autofree gchar *tmp = NULL;
+
+ tmp = convert_keysym_state_to_string (combo);
+
+ accelerator = g_strdup_printf ("<b>%s</b>", tmp);
+ }
+ else
+ {
+ accelerator = convert_keysym_state_to_string (combo);
+ }
+
+ g_value_take_string (to_value, accelerator);
+
+ return TRUE;
+}
+
+static void
+shortcut_modified_changed_cb (CcKeyboardItem *item,
+ GParamSpec *pspec,
+ GtkWidget *button)
+{
+ gtk_widget_set_child_visible (button, !cc_keyboard_item_is_value_default (item));
+}
+
+static void
+reset_all_shortcuts_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ CcKeyboardPanel *self;
+ RowData *data;
+
+ self = user_data;
+
+ if (widget == (GtkWidget *) self->add_shortcut_row)
+ return;
+
+ data = g_object_get_data (G_OBJECT (widget), "data");
+
+ /* Don't reset custom shortcuts */
+ if (cc_keyboard_item_get_item_type (data->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
+ return;
+
+ /* cc_keyboard_manager_reset_shortcut() already resets conflicting shortcuts,
+ * so no other check is needed here. */
+ cc_keyboard_manager_reset_shortcut (self->manager, data->item);
+}
+
+static void
+reset_all_clicked_cb (CcKeyboardPanel *self)
+{
+ GtkWidget *dialog, *toplevel, *button;
+ guint response;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+ dialog = gtk_message_dialog_new (GTK_WINDOW (toplevel),
+ GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ _("Reset All Shortcuts?"));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("Resetting the shortcuts may affect your custom shortcuts. "
+ "This cannot be undone."));
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("Cancel"), GTK_RESPONSE_CANCEL,
+ _("Reset All"), GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+
+ /* Make the "Reset All" button destructive */
+ button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+ gtk_style_context_add_class (gtk_widget_get_style_context (button), "destructive-action");
+
+ /* Reset shortcuts if accepted */
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (response == GTK_RESPONSE_ACCEPT)
+ {
+ gtk_container_foreach (GTK_CONTAINER (self->shortcuts_listbox),
+ reset_all_shortcuts_cb,
+ self);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+reset_shortcut_cb (GtkWidget *reset_button,
+ CcKeyboardItem *item)
+{
+ CcKeyboardPanel *self;
+
+ self = CC_KEYBOARD_PANEL (gtk_widget_get_ancestor (reset_button, CC_TYPE_KEYBOARD_PANEL));
+
+ cc_keyboard_manager_reset_shortcut (self->manager, item);
+}
+
+static void
+add_item (CcKeyboardPanel *self,
+ CcKeyboardItem *item,
+ const gchar *section_id,
+ const gchar *section_title)
+{
+ GtkWidget *row, *box, *label, *reset_button;
+
+ /* Horizontal box */
+ box = g_object_new (GTK_TYPE_BOX,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "spacing", 18,
+ "margin-start", 6,
+ "margin-end", 6,
+ "margin-bottom", 4,
+ "margin-top", 4,
+ NULL);
+ gtk_widget_show (box);
+
+ /* Shortcut title */
+ label = gtk_label_new (cc_keyboard_item_get_description (item));
+ gtk_widget_show (label);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR);
+ gtk_widget_set_hexpand (label, TRUE);
+
+ g_object_bind_property (item,
+ "description",
+ label,
+ "label",
+ G_BINDING_DEFAULT);
+
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ /* Shortcut accelerator */
+ label = gtk_label_new ("");
+ gtk_widget_show (label);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+
+ gtk_size_group_add_widget (self->accelerator_sizegroup, label);
+
+ g_object_bind_property_full (item,
+ "binding",
+ label,
+ "label",
+ G_SETTINGS_BIND_GET | G_BINDING_SYNC_CREATE,
+ transform_binding_to_accel,
+ NULL, NULL, NULL);
+
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+
+ /* Reset shortcut button */
+ reset_button = gtk_button_new_from_icon_name ("edit-clear-symbolic", GTK_ICON_SIZE_BUTTON);
+ gtk_widget_show (reset_button);
+ gtk_widget_set_valign (reset_button, GTK_ALIGN_CENTER);
+
+ gtk_button_set_relief (GTK_BUTTON (reset_button), GTK_RELIEF_NONE);
+ gtk_widget_set_child_visible (reset_button, !cc_keyboard_item_is_value_default (item));
+
+ gtk_widget_set_tooltip_text (reset_button, _("Reset the shortcut to its default value"));
+
+ gtk_container_add (GTK_CONTAINER (box), reset_button);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (reset_button), "flat");
+ gtk_style_context_add_class (gtk_widget_get_style_context (reset_button), "circular");
+ gtk_style_context_add_class (gtk_widget_get_style_context (reset_button), "reset-shortcut-button");
+
+ g_signal_connect_object (item,
+ "notify::is-value-default",
+ G_CALLBACK (shortcut_modified_changed_cb),
+ reset_button, 0);
+
+ g_signal_connect_object (reset_button,
+ "clicked",
+ G_CALLBACK (reset_shortcut_cb),
+ item, 0);
+
+ /* The row */
+ row = gtk_list_box_row_new ();
+ gtk_widget_show (row);
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ g_object_set_data_full (G_OBJECT (row),
+ "data",
+ row_data_new (item, section_id, section_title),
+ (GDestroyNotify) row_data_free);
+
+ gtk_container_add (GTK_CONTAINER (self->shortcuts_listbox), row);
+}
+
+static void
+remove_item (CcKeyboardPanel *self,
+ CcKeyboardItem *item)
+{
+ GList *children, *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->shortcuts_listbox));
+
+ for (l = children; l != NULL; l = l->next)
+ {
+ RowData *row_data;
+
+ row_data = g_object_get_data (l->data, "data");
+
+ if (row_data->item == item)
+ {
+ gtk_container_remove (GTK_CONTAINER (self->shortcuts_listbox), l->data);
+ break;
+ }
+ }
+
+ g_list_free (children);
+}
+
+static gboolean
+strv_contains_prefix_or_match (gchar **strv,
+ const gchar *prefix)
+{
+ guint i;
+
+ const struct {
+ const gchar *key;
+ const gchar *untranslated;
+ const gchar *synonym;
+ } key_aliases[] =
+ {
+ { "ctrl", "Ctrl", "ctrl" },
+ { "win", "Super", "super" },
+ { "option", NULL, "alt" },
+ { "command", NULL, "super" },
+ { "apple", NULL, "super" },
+ };
+
+ for (i = 0; strv[i]; i++)
+ {
+ if (g_str_has_prefix (strv[i], prefix))
+ return TRUE;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (key_aliases); i++)
+ {
+ g_autofree gchar *alias = NULL;
+ const gchar *synonym;
+
+ if (!g_str_has_prefix (key_aliases[i].key, prefix))
+ continue;
+
+ if (key_aliases[i].untranslated)
+ {
+ const gchar *translated_label;
+
+ /* Steal GTK+'s translation */
+ translated_label = g_dpgettext2 ("gtk30", "keyboard label", key_aliases[i].untranslated);
+ alias = g_utf8_strdown (translated_label, -1);
+ }
+
+ synonym = key_aliases[i].synonym;
+
+ /* If a translation or synonym of the key is in the accelerator, and we typed
+ * the key, also consider that a prefix */
+ if ((alias && g_strv_contains ((const gchar * const *) strv, alias)) ||
+ (synonym && g_strv_contains ((const gchar * const *) strv, synonym)))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+search_match_shortcut (CcKeyboardItem *item,
+ const gchar *search)
+{
+ CcKeyCombo *combo = cc_keyboard_item_get_primary_combo (item);
+ GStrv shortcut_tokens, search_tokens;
+ g_autofree gchar *normalized_accel = NULL;
+ g_autofree gchar *accel = NULL;
+ gboolean match;
+ guint i;
+
+ if (is_empty_binding (combo))
+ return FALSE;
+
+ match = TRUE;
+ accel = convert_keysym_state_to_string (combo);
+ normalized_accel = cc_util_normalize_casefold_and_unaccent (accel);
+
+ shortcut_tokens = g_strsplit_set (normalized_accel, SHORTCUT_DELIMITERS, -1);
+ search_tokens = g_strsplit_set (search, SHORTCUT_DELIMITERS, -1);
+
+ for (i = 0; search_tokens[i] != NULL; i++)
+ {
+ const gchar *token;
+
+ /* Strip leading and trailing whitespaces */
+ token = g_strstrip (search_tokens[i]);
+
+ if (g_utf8_strlen (token, -1) == 0)
+ continue;
+
+ match = match && strv_contains_prefix_or_match (shortcut_tokens, token);
+
+ if (!match)
+ break;
+ }
+
+ g_strfreev (shortcut_tokens);
+ g_strfreev (search_tokens);
+
+ return match;
+}
+
+static gint
+sort_function (GtkListBoxRow *a,
+ GtkListBoxRow *b,
+ gpointer user_data)
+{
+ CcKeyboardPanel *self;
+ RowData *a_data, *b_data;
+ gint retval;
+
+ self = user_data;
+
+ if (a == self->add_shortcut_row)
+ return 1;
+
+ if (b == self->add_shortcut_row)
+ return -1;
+
+ a_data = g_object_get_data (G_OBJECT (a), "data");
+ b_data = g_object_get_data (G_OBJECT (b), "data");
+
+ /* Put custom shortcuts below everything else */
+ if (cc_keyboard_item_get_item_type (a_data->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
+ return 1;
+ else if (cc_keyboard_item_get_item_type (b_data->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
+ return -1;
+
+ retval = g_strcmp0 (a_data->section_title, b_data->section_title);
+
+ if (retval != 0)
+ return retval;
+
+ return g_strcmp0 (cc_keyboard_item_get_description (a_data->item), cc_keyboard_item_get_description (b_data->item));
+}
+
+static void
+header_function (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ CcKeyboardPanel *self;
+ gboolean add_header;
+ RowData *data;
+
+ self = user_data;
+ add_header = FALSE;
+
+ /* The + row always has a separator */
+ if (row == self->add_shortcut_row)
+ {
+ GtkWidget *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show (separator);
+
+ gtk_list_box_row_set_header (row, separator);
+
+ return;
+ }
+
+ data = g_object_get_data (G_OBJECT (row), "data");
+
+ if (before)
+ {
+ RowData *before_data = g_object_get_data (G_OBJECT (before), "data");
+
+ if (before_data)
+ add_header = g_strcmp0 (before_data->section_id, data->section_id) != 0;
+ }
+ else
+ {
+ add_header = TRUE;
+ }
+
+ if (add_header)
+ {
+ GtkWidget *box, *label, *separator;
+ g_autofree gchar *markup = NULL;
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_show (box);
+ gtk_widget_set_margin_top (box, before ? 18 : 6);
+
+ markup = g_strdup_printf ("<b>%s</b>", _(data->section_title));
+ label = g_object_new (GTK_TYPE_LABEL,
+ "label", markup,
+ "use-markup", TRUE,
+ "xalign", 0.0,
+ "margin-start", 6,
+ NULL);
+ gtk_widget_show (label);
+ gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show (separator);
+ gtk_container_add (GTK_CONTAINER (box), separator);
+
+ gtk_list_box_row_set_header (row, box);
+ }
+ else
+ {
+ gtk_list_box_row_set_header (row, NULL);
+ }
+}
+
+static gboolean
+filter_function (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ CcKeyboardPanel *self = user_data;
+ CcKeyboardItem *item;
+ RowData *data;
+ gboolean retval;
+ g_autofree gchar *search = NULL;
+ g_autofree gchar *name = NULL;
+ g_auto(GStrv) terms = NULL;
+ guint i;
+
+ if (gtk_entry_get_text_length (GTK_ENTRY (self->search_entry)) == 0)
+ return TRUE;
+
+ /* When searching, the '+' row is always hidden */
+ if (row == self->add_shortcut_row)
+ return FALSE;
+
+ data = g_object_get_data (G_OBJECT (row), "data");
+ item = data->item;
+ name = cc_util_normalize_casefold_and_unaccent (cc_keyboard_item_get_description (item));
+ search = cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (self->search_entry)));
+ terms = g_strsplit (search, " ", -1);
+
+ for (i = 0; terms && terms[i]; i++)
+ {
+ retval = strstr (name, terms[i]) || search_match_shortcut (item, terms[i]);
+ if (!retval)
+ break;
+ }
+
+ return retval;
+}
+
+static void
+shortcut_row_activated (GtkWidget *button,
+ GtkListBoxRow *row,
+ CcKeyboardPanel *self)
+{
+ CcKeyboardShortcutEditor *editor;
+
+ editor = CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor);
+
+ if (row != self->add_shortcut_row)
+ {
+ RowData *data = g_object_get_data (G_OBJECT (row), "data");
+
+ cc_keyboard_shortcut_editor_set_mode (editor, CC_SHORTCUT_EDITOR_EDIT);
+ cc_keyboard_shortcut_editor_set_item (editor, data->item);
+ }
+ else
+ {
+ cc_keyboard_shortcut_editor_set_mode (editor, CC_SHORTCUT_EDITOR_CREATE);
+ cc_keyboard_shortcut_editor_set_item (editor, NULL);
+ }
+
+ gtk_widget_show (self->shortcut_editor);
+}
+
+static void
+alternate_chars_activated (GtkWidget *button,
+ GtkListBoxRow *row,
+ CcKeyboardPanel *self)
+{
+ GtkWindow *window;
+
+ window = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self))));
+
+ gtk_window_set_transient_for (GTK_WINDOW (self->alt_chars_key_dialog), window);
+ gtk_widget_show (GTK_WIDGET (self->alt_chars_key_dialog));
+}
+
+static gboolean
+transform_binding_to_alt_chars (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ const char **items;
+ guint i;
+
+ items = g_variant_get_strv (variant, NULL);
+ if (!items)
+ goto bail;
+
+ for (i = 0; items[i] != NULL; i++)
+ {
+ guint j;
+
+ if (!g_str_has_prefix (items[i], "lv3:"))
+ continue;
+
+ for (j = 0; j < G_N_ELEMENTS (lv3_xkb_options); j++)
+ {
+ if (!g_str_equal (items[i], lv3_xkb_options[j].xkb_option))
+ continue;
+
+ g_value_set_string (value,
+ g_dpgettext2 (NULL, "keyboard key", lv3_xkb_options[j].label));
+ return TRUE;
+ }
+ }
+
+bail:
+ g_value_set_string (value,
+ g_dpgettext2 (NULL, "keyboard key", lv3_xkb_options[DEFAULT_LV3_OPTION].label));
+ return TRUE;
+}
+
+static void
+cc_keyboard_panel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_PARAMETERS:
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static const char *
+cc_keyboard_panel_get_help_uri (CcPanel *panel)
+{
+ return "help:gnome-help/keyboard";
+}
+
+static void
+cc_keyboard_panel_finalize (GObject *object)
+{
+ CcKeyboardPanel *self = CC_KEYBOARD_PANEL (object);
+ GtkWidget *window;
+
+ g_clear_pointer (&self->pictures_regex, g_regex_unref);
+ g_clear_object (&self->accelerator_sizegroup);
+ g_clear_object (&self->input_source_settings);
+
+ cc_keyboard_option_clear_all ();
+
+ if (self->search_bar_handler_id != 0)
+ {
+ window = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)));
+ g_signal_handler_disconnect (window, self->search_bar_handler_id);
+ }
+
+ G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->finalize (object);
+}
+
+static void
+cc_keyboard_panel_constructed (GObject *object)
+{
+ CcKeyboardPanel *self = CC_KEYBOARD_PANEL (object);
+ GtkWindow *toplevel;
+ CcShell *shell;
+
+ G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->constructed (object);
+
+ /* Setup the dialog's transient parent */
+ shell = cc_panel_get_shell (CC_PANEL (self));
+ toplevel = GTK_WINDOW (cc_shell_get_toplevel (shell));
+ gtk_window_set_transient_for (GTK_WINDOW (self->shortcut_editor), toplevel);
+
+ cc_shell_embed_widget_in_header (shell, self->reset_button, GTK_POS_LEFT);
+ cc_shell_embed_widget_in_header (shell, self->search_button, GTK_POS_RIGHT);
+
+ self->search_bar_handler_id =
+ g_signal_connect_swapped (toplevel,
+ "key-press-event",
+ G_CALLBACK (gtk_search_bar_handle_event),
+ self->search_bar);
+}
+
+static void
+cc_keyboard_panel_class_init (CcKeyboardPanelClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
+
+ panel_class->get_help_uri = cc_keyboard_panel_get_help_uri;
+
+ object_class->set_property = cc_keyboard_panel_set_property;
+ object_class->finalize = cc_keyboard_panel_finalize;
+ object_class->constructed = cc_keyboard_panel_constructed;
+
+ g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-keyboard-panel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, add_shortcut_row);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, empty_search_placeholder);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, reset_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, search_bar);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, search_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, search_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, shortcuts_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, value_alternate_chars);
+
+ gtk_widget_class_bind_template_callback (widget_class, reset_all_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, shortcut_row_activated);
+ gtk_widget_class_bind_template_callback (widget_class, alternate_chars_activated);
+}
+
+static void
+cc_keyboard_panel_init (CcKeyboardPanel *self)
+{
+ GtkCssProvider *provider;
+
+ g_resources_register (cc_keyboard_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ /* Custom CSS */
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (provider, custom_css, -1, NULL);
+
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1);
+
+ g_object_unref (provider);
+
+ /* Alternate characters key */
+ self->input_source_settings = g_settings_new ("org.gnome.desktop.input-sources");
+ g_settings_bind_with_mapping (self->input_source_settings,
+ "xkb-options",
+ self->value_alternate_chars,
+ "label",
+ G_SETTINGS_BIND_GET,
+ transform_binding_to_alt_chars,
+ NULL,
+ self->value_alternate_chars,
+ NULL);
+
+ self->alt_chars_key_dialog = cc_alt_chars_key_dialog_new (self->input_source_settings);
+
+ /* Shortcut manager */
+ self->manager = cc_keyboard_manager_new ();
+
+ /* Use a sizegroup to make the accelerator labels the same width */
+ self->accelerator_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ g_signal_connect_object (self->manager,
+ "shortcut-added",
+ G_CALLBACK (add_item),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->manager,
+ "shortcut-removed",
+ G_CALLBACK (remove_item),
+ self,
+ G_CONNECT_SWAPPED);
+
+ cc_keyboard_manager_load_shortcuts (self->manager);
+
+ /* Shortcut editor dialog */
+ self->shortcut_editor = cc_keyboard_shortcut_editor_new (self->manager);
+
+ /* Setup the shortcuts shortcuts_listbox */
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (self->shortcuts_listbox),
+ sort_function,
+ self,
+ NULL);
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->shortcuts_listbox),
+ header_function,
+ self,
+ NULL);
+
+ gtk_list_box_set_filter_func (GTK_LIST_BOX (self->shortcuts_listbox),
+ filter_function,
+ self,
+ NULL);
+
+ gtk_list_box_set_placeholder (GTK_LIST_BOX (self->shortcuts_listbox), self->empty_search_placeholder);
+}
+
diff --git a/panels/keyboard/cc-keyboard-panel.h b/panels/keyboard/cc-keyboard-panel.h
new file mode 100644
index 0000000..db6e352
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-panel.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Intel, 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>
+ *
+ */
+
+
+#pragma once
+
+#include <shell/cc-panel.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_KEYBOARD_PANEL (cc_keyboard_panel_get_type ())
+G_DECLARE_FINAL_TYPE (CcKeyboardPanel, cc_keyboard_panel, CC, KEYBOARD_PANEL, CcPanel)
+
+CcKeyboardItem* cc_keyboard_panel_create_custom_item (CcKeyboardPanel *self);
+
+G_END_DECLS
diff --git a/panels/keyboard/cc-keyboard-panel.ui b/panels/keyboard/cc-keyboard-panel.ui
new file mode 100644
index 0000000..16e522c
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-panel.ui
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkAdjustment" id="cursor_blink_time_adjustment">
+ <property name="lower">100</property>
+ <property name="upper">2500</property>
+ <property name="value">1000</property>
+ <property name="step_increment">200</property>
+ <property name="page_increment">200</property>
+ </object>
+ <template class="CcKeyboardPanel" parent="CcPanel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="expand">True</property>
+ <signal name="key-press-event" handler="gtk_search_bar_handle_event" object="search_bar" swapped="yes" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkSearchBar" id="search_bar">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="search_mode_enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional" />
+ <child>
+ <object class="GtkSearchEntry" id="search_entry">
+ <property name="visible">True</property>
+ <property name="width_chars">30</property>
+ <signal name="notify::text" handler="gtk_list_box_invalidate_filter" object="shortcuts_listbox" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="propagate_natural_width">True</property>
+ <property name="propagate_natural_height">True</property>
+ <property name="max_content_height">350</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="margin_top">32</property>
+ <property name="margin_bottom">32</property>
+ <property name="margin_left">18</property>
+ <property name="margin_right">18</property>
+ <property name="spacing">12</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkListBox" id="alternate_chars_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="selection-mode">none</property>
+ <property name="width-request">250</property>
+ <signal name="row-activated" handler="alternate_chars_activated" object="CcKeyboardPanel" swapped="no" />
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="use-underline">true</property>
+ <property name="title" translatable="yes">Alternate Characters Key</property>
+ <property name="subtitle" translatable="yes">Hold down and type to enter different characters</property>
+ <property name="activatable">True</property>
+ <child>
+ <object class="GtkLabel" id="value_alternate_chars">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="no">Right Alt</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkListBox" id="shortcuts_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="selection-mode">none</property>
+ <property name="width-request">250</property>
+ <signal name="row-activated" handler="shortcut_row_activated" object="CcKeyboardPanel" swapped="no" />
+ <child>
+ <object class="GtkListBoxRow" id="add_shortcut_row">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="border_width">6</property>
+ <child type="center">
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-add-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+
+ <!-- Header widgets -->
+ <object class="GtkToggleButton" id="search_button">
+ <property name="visible">True</property>
+ <style>
+ <class name="image-button" />
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">system-search-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <object class="GtkButton" id="reset_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Reset All…</property>
+ <property name="tooltip-text" translatable="yes">Reset all shortcuts to their default keybindings</property>
+ <signal name="clicked" handler="reset_all_clicked_cb" object="CcKeyboardPanel" swapped="yes" />
+ </object>
+
+ <object class="GtkBox" id="empty_search_placeholder">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">80</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No keyboard shortcut found</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Try a different search</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/panels/keyboard/cc-keyboard-shortcut-editor.c b/panels/keyboard/cc-keyboard-shortcut-editor.c
new file mode 100644
index 0000000..e53bb01
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-shortcut-editor.c
@@ -0,0 +1,1012 @@
+/* cc-keyboard-shortcut-editor.h
+ *
+ * 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/>.
+ *
+ * Authors: Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ */
+
+#include <glib-object.h>
+#include <glib/gi18n.h>
+
+#include "cc-keyboard-shortcut-editor.h"
+#include "keyboard-shortcuts.h"
+
+struct _CcKeyboardShortcutEditor
+{
+ GtkDialog parent;
+
+ GtkButton *add_button;
+ GtkButton *cancel_button;
+ GtkButton *change_custom_shortcut_button;
+ GtkEntry *command_entry;
+ GtkGrid *custom_grid;
+ GtkShortcutLabel *custom_shortcut_accel_label;
+ GtkStack *custom_shortcut_stack;
+ GtkBox *edit_box;
+ GtkHeaderBar *headerbar;
+ GtkEntry *name_entry;
+ GtkLabel *new_shortcut_conflict_label;
+ GtkButton *remove_button;
+ GtkButton *replace_button;
+ GtkButton *reset_button;
+ GtkButton *reset_custom_button;
+ GtkButton *set_button;
+ GtkShortcutLabel *shortcut_accel_label;
+ GtkLabel *shortcut_conflict_label;
+ GtkBox *standard_box;
+ GtkStack *stack;
+ GtkLabel *top_info_label;
+
+ CcShortcutEditorMode mode;
+
+ CcKeyboardManager *manager;
+ CcKeyboardItem *item;
+ GBinding *reset_item_binding;
+
+ CcKeyboardItem *collision_item;
+
+ /* Custom shortcuts */
+ GdkDevice *grab_pointer;
+ guint grab_idle_id;
+
+ CcKeyCombo *custom_combo;
+ gboolean custom_is_modifier;
+ gboolean edited : 1;
+};
+
+static void command_entry_changed_cb (CcKeyboardShortcutEditor *self);
+static void name_entry_changed_cb (CcKeyboardShortcutEditor *self);
+static void set_button_clicked_cb (CcKeyboardShortcutEditor *self);
+
+G_DEFINE_TYPE (CcKeyboardShortcutEditor, cc_keyboard_shortcut_editor, GTK_TYPE_DIALOG)
+
+enum
+{
+ PROP_0,
+ PROP_KEYBOARD_ITEM,
+ PROP_MANAGER,
+ N_PROPS
+};
+
+typedef enum
+{
+ HEADER_MODE_NONE,
+ HEADER_MODE_ADD,
+ HEADER_MODE_SET,
+ HEADER_MODE_REPLACE,
+ HEADER_MODE_CUSTOM_CANCEL,
+ HEADER_MODE_CUSTOM_EDIT
+} HeaderMode;
+
+typedef enum
+{
+ PAGE_CUSTOM,
+ PAGE_EDIT,
+ PAGE_STANDARD,
+} ShortcutEditorPage;
+
+static GParamSpec *properties [N_PROPS] = { NULL, };
+
+/* Getter and setter for ShortcutEditorPage */
+static ShortcutEditorPage
+get_shortcut_editor_page (CcKeyboardShortcutEditor *self)
+{
+ if (gtk_stack_get_visible_child (self->stack) == GTK_WIDGET (self->edit_box))
+ return PAGE_EDIT;
+
+ if (gtk_stack_get_visible_child (self->stack) == GTK_WIDGET (self->custom_grid))
+ return PAGE_CUSTOM;
+
+ return PAGE_STANDARD;
+}
+
+static void
+set_shortcut_editor_page (CcKeyboardShortcutEditor *self,
+ ShortcutEditorPage page)
+{
+ switch (page)
+ {
+ case PAGE_CUSTOM:
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->custom_grid));
+ break;
+
+ case PAGE_EDIT:
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->edit_box));
+ break;
+
+ case PAGE_STANDARD:
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->standard_box));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->top_info_label), page != PAGE_CUSTOM);
+}
+
+static void
+apply_custom_item_fields (CcKeyboardShortcutEditor *self,
+ CcKeyboardItem *item)
+{
+ /* Only setup the binding when it was actually edited */
+ if (self->edited)
+ {
+ CcKeyCombo *combo = cc_keyboard_item_get_primary_combo (item);
+ g_autofree gchar *binding = NULL;
+
+ combo->keycode = self->custom_combo->keycode;
+ combo->keyval = self->custom_combo->keyval;
+ combo->mask = self->custom_combo->mask;
+
+ if (combo->keycode == 0 && combo->keyval == 0 && combo->mask == 0)
+ binding = g_strdup ("");
+ else
+ binding = gtk_accelerator_name_with_keycode (NULL,
+ combo->keyval,
+ combo->keycode,
+ combo->mask);
+
+ g_object_set (G_OBJECT (item), "binding", binding, NULL);
+ }
+
+ /* Set the keyboard shortcut name and command for custom entries */
+ if (cc_keyboard_item_get_item_type (item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
+ {
+ g_settings_set_string (cc_keyboard_item_get_settings (item), "name", gtk_entry_get_text (self->name_entry));
+ g_settings_set_string (cc_keyboard_item_get_settings (item), "command", gtk_entry_get_text (self->command_entry));
+ }
+}
+
+static void
+clear_custom_entries (CcKeyboardShortcutEditor *self)
+{
+ g_signal_handlers_block_by_func (self->command_entry, command_entry_changed_cb, self);
+ g_signal_handlers_block_by_func (self->name_entry, name_entry_changed_cb, self);
+
+ gtk_entry_set_text (self->name_entry, "");
+ gtk_entry_set_text (self->command_entry, "");
+
+ gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->custom_shortcut_accel_label), "");
+ gtk_label_set_label (self->new_shortcut_conflict_label, "");
+ gtk_label_set_label (self->shortcut_conflict_label, "");
+
+ memset (self->custom_combo, 0, sizeof (CcKeyCombo));
+ self->custom_is_modifier = TRUE;
+ self->edited = FALSE;
+
+ self->collision_item = NULL;
+
+ g_signal_handlers_unblock_by_func (self->command_entry, command_entry_changed_cb, self);
+ g_signal_handlers_unblock_by_func (self->name_entry, name_entry_changed_cb, self);
+}
+
+static void
+cancel_editing (CcKeyboardShortcutEditor *self)
+{
+ cc_keyboard_shortcut_editor_set_item (self, NULL);
+ clear_custom_entries (self);
+
+ gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static gboolean
+is_custom_shortcut (CcKeyboardShortcutEditor *self) {
+ return self->item == NULL || cc_keyboard_item_get_item_type (self->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH;
+}
+
+static void
+grab_seat (CcKeyboardShortcutEditor *self)
+{
+ GdkGrabStatus status;
+ GdkWindow *window;
+ GdkSeat *seat;
+
+ window = gtk_widget_get_window (GTK_WIDGET (self));
+ g_assert (window);
+
+ seat = gdk_display_get_default_seat (gdk_window_get_display (window));
+
+ status = gdk_seat_grab (seat,
+ window,
+ GDK_SEAT_CAPABILITY_KEYBOARD,
+ FALSE,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ if (status != GDK_GRAB_SUCCESS) {
+ g_warning ("Grabbing keyboard failed");
+ return;
+ }
+
+ self->grab_pointer = gdk_seat_get_keyboard (seat);
+ if (!self->grab_pointer)
+ self->grab_pointer = gdk_seat_get_pointer (seat);
+
+ gtk_grab_add (GTK_WIDGET (self));
+}
+
+static void
+release_grab (CcKeyboardShortcutEditor *self)
+{
+ if (self->grab_pointer)
+ {
+ gdk_seat_ungrab (gdk_device_get_seat (self->grab_pointer));
+ self->grab_pointer = NULL;
+
+ gtk_grab_remove (GTK_WIDGET (self));
+ }
+}
+
+static void
+update_shortcut (CcKeyboardShortcutEditor *self)
+{
+ if (!self->item)
+ return;
+
+ /* Setup the binding */
+ apply_custom_item_fields (self, self->item);
+
+ /* Eventually disable the conflict shortcut */
+ if (self->collision_item)
+ cc_keyboard_manager_disable_shortcut (self->manager, self->collision_item);
+
+ /* Cleanup whatever was set before */
+ clear_custom_entries (self);
+
+ cc_keyboard_shortcut_editor_set_item (self, NULL);
+}
+
+static GtkShortcutLabel*
+get_current_shortcut_label (CcKeyboardShortcutEditor *self)
+{
+ if (is_custom_shortcut (self))
+ return GTK_SHORTCUT_LABEL (self->custom_shortcut_accel_label);
+
+ return GTK_SHORTCUT_LABEL (self->shortcut_accel_label);
+}
+
+static void
+set_header_mode (CcKeyboardShortcutEditor *self,
+ HeaderMode mode)
+{
+ gtk_header_bar_set_show_close_button (self->headerbar, mode == HEADER_MODE_CUSTOM_EDIT);
+
+ gtk_widget_set_visible (GTK_WIDGET (self->add_button), mode == HEADER_MODE_ADD);
+ gtk_widget_set_visible (GTK_WIDGET (self->cancel_button), mode != HEADER_MODE_NONE &&
+ mode != HEADER_MODE_CUSTOM_EDIT);
+ gtk_widget_set_visible (GTK_WIDGET (self->replace_button), mode == HEADER_MODE_REPLACE);
+ gtk_widget_set_visible (GTK_WIDGET (self->set_button), mode == HEADER_MODE_SET);
+ gtk_widget_set_visible (GTK_WIDGET (self->remove_button), mode == HEADER_MODE_CUSTOM_EDIT);
+
+ /* By setting the default response, the action button gets the 'suggested-action' applied */
+ switch (mode)
+ {
+ case HEADER_MODE_SET:
+ gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_APPLY);
+ break;
+
+ case HEADER_MODE_REPLACE:
+ gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT);
+ break;
+
+ case HEADER_MODE_ADD:
+ gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_OK);
+ break;
+
+ default:
+ gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_NONE);
+ }
+}
+
+static void
+setup_custom_shortcut (CcKeyboardShortcutEditor *self)
+{
+ GtkShortcutLabel *shortcut_label;
+ CcKeyboardItem *collision_item;
+ HeaderMode mode;
+ gboolean is_custom, is_accel_empty;
+ gboolean valid, accel_valid;
+ gchar *accel;
+
+ is_custom = is_custom_shortcut (self);
+ accel_valid = is_valid_binding (self->custom_combo) &&
+ is_valid_accel (self->custom_combo) &&
+ !self->custom_is_modifier;
+
+ is_accel_empty = is_empty_binding (self->custom_combo);
+
+ if (is_accel_empty)
+ accel_valid = TRUE;
+ valid = accel_valid;
+
+ /* Additional checks for custom shortcuts */
+ if (is_custom)
+ {
+ if (accel_valid)
+ {
+ set_shortcut_editor_page (self, PAGE_CUSTOM);
+
+ /* We have to check if the current accelerator is empty in order to
+ * decide if we show the "Set Shortcut" button or the accelerator label */
+ gtk_stack_set_visible_child (self->custom_shortcut_stack,
+ is_accel_empty ? GTK_WIDGET (self->change_custom_shortcut_button) : GTK_WIDGET (self->custom_shortcut_accel_label));
+ gtk_widget_set_visible (GTK_WIDGET (self->reset_custom_button), !is_accel_empty);
+ }
+
+ valid = accel_valid &&
+ gtk_entry_get_text_length (self->name_entry) > 0 &&
+ gtk_entry_get_text_length (self->command_entry) > 0;
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->replace_button), valid);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), valid);
+ if (valid)
+ set_header_mode (self, HEADER_MODE_ADD);
+ else
+ set_header_mode (self, is_custom ? HEADER_MODE_CUSTOM_CANCEL : HEADER_MODE_NONE);
+
+ /* Nothing else to do if the shortcut is invalid */
+ if (!accel_valid)
+ return;
+
+ /* Valid shortcut, show it in the standard page */
+ if (!is_custom)
+ set_shortcut_editor_page (self, PAGE_STANDARD);
+
+ shortcut_label = get_current_shortcut_label (self);
+
+ collision_item = cc_keyboard_manager_get_collision (self->manager,
+ self->item,
+ self->custom_combo);
+
+ accel = gtk_accelerator_name (self->custom_combo->keyval, self->custom_combo->mask);
+
+
+ /* Setup the accelerator label */
+ gtk_shortcut_label_set_accelerator (shortcut_label, accel);
+
+ self->edited = TRUE;
+
+ release_grab (self);
+
+ /*
+ * Oops! Looks like the accelerator is already being used, so we
+ * must warn the user and let it be very clear that adding this
+ * shortcut will disable the other.
+ */
+ gtk_widget_set_visible (GTK_WIDGET (self->new_shortcut_conflict_label), collision_item != NULL);
+
+ if (collision_item)
+ {
+ GtkLabel *label;
+ g_autofree gchar *friendly_accelerator = NULL;
+ g_autofree gchar *accelerator_text = NULL;
+ g_autofree gchar *collision_text = NULL;
+
+ friendly_accelerator = convert_keysym_state_to_string (self->custom_combo);
+
+ accelerator_text = g_strdup_printf ("<b>%s</b>", friendly_accelerator);
+ collision_text = g_strdup_printf (_("%s is already being used for %s. If you "
+ "replace it, %s will be disabled"),
+ accelerator_text,
+ cc_keyboard_item_get_description (collision_item),
+ cc_keyboard_item_get_description (collision_item));
+
+ label = is_custom_shortcut (self) ? self->new_shortcut_conflict_label : self->shortcut_conflict_label;
+
+ gtk_label_set_markup (label, collision_text);
+ }
+
+ /*
+ * When there is a collision between the current shortcut and another shortcut,
+ * and we're editing an existing shortcut (rather than creating a new one), setup
+ * the headerbar to display "Cancel" and "Replace". Otherwise, make sure to set
+ * only the close button again.
+ */
+ if (collision_item)
+ {
+ mode = HEADER_MODE_REPLACE;
+ }
+ else
+ {
+ if (self->mode == CC_SHORTCUT_EDITOR_EDIT)
+ mode = is_custom ? HEADER_MODE_CUSTOM_EDIT : HEADER_MODE_SET;
+ else
+ mode = is_custom ? HEADER_MODE_ADD : HEADER_MODE_SET;
+ }
+
+ set_header_mode (self, mode);
+
+ self->collision_item = collision_item;
+}
+
+static void
+add_button_clicked_cb (CcKeyboardShortcutEditor *self)
+{
+ CcKeyboardItem *item;
+
+ item = cc_keyboard_manager_create_custom_shortcut (self->manager);
+
+ /* Apply the custom shortcut setup at the new item */
+ apply_custom_item_fields (self, item);
+
+ /* Eventually disable the conflict shortcut */
+ if (self->collision_item)
+ cc_keyboard_manager_disable_shortcut (self->manager, self->collision_item);
+
+ /* Cleanup everything once we're done */
+ clear_custom_entries (self);
+
+ cc_keyboard_manager_add_custom_shortcut (self->manager, item);
+
+ gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static void
+cancel_button_clicked_cb (GtkWidget *button,
+ CcKeyboardShortcutEditor *self)
+{
+ cancel_editing (self);
+}
+
+static void
+change_custom_shortcut_button_clicked_cb (CcKeyboardShortcutEditor *self)
+{
+ grab_seat (self);
+ set_shortcut_editor_page (self, PAGE_EDIT);
+ set_header_mode (self, HEADER_MODE_NONE);
+}
+
+static void
+command_entry_changed_cb (CcKeyboardShortcutEditor *self)
+{
+ setup_custom_shortcut (self);
+}
+
+static void
+name_entry_changed_cb (CcKeyboardShortcutEditor *self)
+{
+ setup_custom_shortcut (self);
+}
+
+static void
+remove_button_clicked_cb (CcKeyboardShortcutEditor *self)
+{
+ gtk_widget_hide (GTK_WIDGET (self));
+
+ cc_keyboard_manager_remove_custom_shortcut (self->manager, self->item);
+}
+
+static void
+replace_button_clicked_cb (CcKeyboardShortcutEditor *self)
+{
+ if (self->mode == CC_SHORTCUT_EDITOR_CREATE)
+ add_button_clicked_cb (self);
+ else
+ set_button_clicked_cb (self);
+}
+
+static void
+reset_custom_clicked_cb (CcKeyboardShortcutEditor *self)
+{
+ if (self->item)
+ cc_keyboard_manager_reset_shortcut (self->manager, self->item);
+
+ gtk_stack_set_visible_child (self->custom_shortcut_stack, GTK_WIDGET (self->change_custom_shortcut_button));
+ gtk_widget_hide (GTK_WIDGET (self->reset_custom_button));
+}
+
+static void
+reset_item_clicked_cb (CcKeyboardShortcutEditor *self)
+{
+ CcKeyCombo *combo;
+ gchar *accel;
+
+ /* Reset first, then update the shortcut */
+ cc_keyboard_manager_reset_shortcut (self->manager, self->item);
+
+ combo = cc_keyboard_item_get_primary_combo (self->item);
+ accel = gtk_accelerator_name (combo->keyval, combo->mask);
+ gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->shortcut_accel_label), accel);
+
+ g_free (accel);
+}
+
+static void
+set_button_clicked_cb (CcKeyboardShortcutEditor *self)
+{
+ update_shortcut (self);
+ gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static void
+setup_keyboard_item (CcKeyboardShortcutEditor *self,
+ CcKeyboardItem *item)
+{
+ CcKeyCombo *combo;
+ gboolean is_custom;
+ g_autofree gchar *accel = NULL;
+ g_autofree gchar *description_text = NULL;
+ g_autofree gchar *text = NULL;
+
+ if (!item) {
+ gtk_label_set_text (self->top_info_label, _("Enter the new shortcut"));
+ return;
+ }
+
+ combo = cc_keyboard_item_get_primary_combo (item);
+ is_custom = cc_keyboard_item_get_item_type (item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH;
+ accel = gtk_accelerator_name (combo->keyval, combo->mask);
+
+ /* To avoid accidentally thinking we unset the current keybinding, set the values
+ * of the keyboard item that is being edited */
+ self->custom_is_modifier = FALSE;
+ self->custom_combo->keycode = combo->keycode;
+ self->custom_combo->keyval = combo->keyval;
+ self->custom_combo->mask = combo->mask;
+
+ /* Headerbar */
+ gtk_header_bar_set_title (self->headerbar,
+ is_custom ? _("Set Custom Shortcut") : _("Set Shortcut"));
+
+ set_header_mode (self, is_custom ? HEADER_MODE_CUSTOM_EDIT : HEADER_MODE_NONE);
+
+ gtk_widget_hide (GTK_WIDGET (self->add_button));
+ gtk_widget_hide (GTK_WIDGET (self->cancel_button));
+ gtk_widget_hide (GTK_WIDGET (self->replace_button));
+
+ /* Setup the top label */
+ description_text = g_strdup_printf ("<b>%s</b>", cc_keyboard_item_get_description (item));
+ /* TRANSLATORS: %s is replaced with a description of the keyboard shortcut */
+ text = g_strdup_printf (_("Enter new shortcut to change %s."), description_text);
+
+ gtk_label_set_markup (self->top_info_label, text);
+
+ /* Accelerator labels */
+ gtk_shortcut_label_set_accelerator (self->shortcut_accel_label, accel);
+ gtk_shortcut_label_set_accelerator (self->custom_shortcut_accel_label, accel);
+
+ g_clear_pointer (&self->reset_item_binding, g_binding_unbind);
+ self->reset_item_binding = g_object_bind_property (item,
+ "is-value-default",
+ self->reset_button,
+ "visible",
+ G_BINDING_DEFAULT | G_BINDING_INVERT_BOOLEAN | G_BINDING_SYNC_CREATE);
+
+ /* Setup the custom entries */
+ if (is_custom)
+ {
+ gboolean is_accel_empty;
+
+ g_signal_handlers_block_by_func (self->command_entry, command_entry_changed_cb, self);
+ g_signal_handlers_block_by_func (self->name_entry, name_entry_changed_cb, self);
+
+ /* Name entry */
+ gtk_entry_set_text (self->name_entry, cc_keyboard_item_get_description (item));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->name_entry), cc_keyboard_item_get_desc_editable (item));
+
+ /* Command entry */
+ gtk_entry_set_text (self->command_entry, cc_keyboard_item_get_command (item));
+ gtk_widget_set_sensitive (GTK_WIDGET (self->command_entry), cc_keyboard_item_get_cmd_editable (item));
+
+ /* If there is no accelerator set for this custom shortcut, show the "Set Shortcut" button. */
+ is_accel_empty = !accel || accel[0] == '\0';
+
+ gtk_stack_set_visible_child (self->custom_shortcut_stack,
+ is_accel_empty ? GTK_WIDGET (self->change_custom_shortcut_button) : GTK_WIDGET (self->custom_shortcut_accel_label));
+
+ gtk_widget_set_visible (GTK_WIDGET (self->reset_custom_button), !is_accel_empty);
+
+ g_signal_handlers_unblock_by_func (self->command_entry, command_entry_changed_cb, self);
+ g_signal_handlers_unblock_by_func (self->name_entry, name_entry_changed_cb, self);
+
+ release_grab (self);
+ }
+
+ /* Show the appropriate view */
+ set_shortcut_editor_page (self, is_custom ? PAGE_CUSTOM : PAGE_EDIT);
+}
+
+static void
+cc_keyboard_shortcut_editor_finalize (GObject *object)
+{
+ CcKeyboardShortcutEditor *self = (CcKeyboardShortcutEditor *)object;
+
+ g_clear_object (&self->item);
+ g_clear_object (&self->manager);
+
+ g_clear_pointer (&self->custom_combo, g_free);
+ g_clear_pointer (&self->reset_item_binding, g_binding_unbind);
+
+ G_OBJECT_CLASS (cc_keyboard_shortcut_editor_parent_class)->finalize (object);
+}
+
+static void
+cc_keyboard_shortcut_editor_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEYBOARD_ITEM:
+ g_value_set_object (value, self->item);
+ break;
+
+ case PROP_MANAGER:
+ g_value_set_object (value, self->manager);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_keyboard_shortcut_editor_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEYBOARD_ITEM:
+ cc_keyboard_shortcut_editor_set_item (self, g_value_get_object (value));
+ break;
+
+ case PROP_MANAGER:
+ g_set_object (&self->manager, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static gboolean
+cc_keyboard_shortcut_editor_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ CcKeyboardShortcutEditor *self;
+ GdkModifierType real_mask;
+ gboolean editing;
+ guint keyval_lower;
+
+ self = CC_KEYBOARD_SHORTCUT_EDITOR (widget);
+
+ /* Being in the "change-shortcut" page is the only check we must
+ * perform to decide if we're editing a shortcut. */
+ editing = get_shortcut_editor_page (self) == PAGE_EDIT;
+
+ if (!editing)
+ return GTK_WIDGET_CLASS (cc_keyboard_shortcut_editor_parent_class)->key_press_event (widget, event);
+
+ real_mask = event->state & gtk_accelerator_get_default_mod_mask ();
+
+ keyval_lower = gdk_keyval_to_lower (event->keyval);
+
+ /* Normalise <Tab> */
+ if (keyval_lower == GDK_KEY_ISO_Left_Tab)
+ keyval_lower = GDK_KEY_Tab;
+
+ /* Put shift back if it changed the case of the key, not otherwise. */
+ if (keyval_lower != event->keyval)
+ real_mask |= GDK_SHIFT_MASK;
+
+ if (keyval_lower == GDK_KEY_Sys_Req &&
+ (real_mask & GDK_MOD1_MASK) != 0)
+ {
+ /* HACK: we don't want to use SysRq as a keybinding (but we do
+ * want Alt+Print), so we avoid translation from Alt+Print to SysRq */
+ keyval_lower = GDK_KEY_Print;
+ }
+
+ /* A single Escape press cancels the editing */
+ if (!event->is_modifier && real_mask == 0 && keyval_lower == GDK_KEY_Escape)
+ {
+ self->edited = FALSE;
+
+ release_grab (self);
+ cancel_editing (self);
+
+ return GDK_EVENT_STOP;
+ }
+
+ /* Backspace disables the current shortcut */
+ if (!event->is_modifier && real_mask == 0 && keyval_lower == GDK_KEY_BackSpace)
+ {
+ self->edited = TRUE;
+ self->custom_is_modifier = FALSE;
+ memset (self->custom_combo, 0, sizeof (CcKeyCombo));
+
+ gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->custom_shortcut_accel_label), "");
+ gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->shortcut_accel_label), "");
+
+ release_grab (self);
+
+ self->edited = FALSE;
+
+ setup_custom_shortcut (self);
+
+ return GDK_EVENT_STOP;
+ }
+
+ self->custom_is_modifier = event->is_modifier;
+ self->custom_combo->keycode = event->hardware_keycode;
+ self->custom_combo->keyval = keyval_lower;
+ self->custom_combo->mask = real_mask;
+
+ /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */
+ self->custom_combo->mask &= ~GDK_LOCK_MASK;
+
+ setup_custom_shortcut (self);
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+cc_keyboard_shortcut_editor_close (GtkDialog *dialog)
+{
+ CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (dialog);
+
+ if (self->mode == CC_SHORTCUT_EDITOR_EDIT)
+ update_shortcut (self);
+
+ GTK_DIALOG_CLASS (cc_keyboard_shortcut_editor_parent_class)->close (dialog);
+}
+
+static void
+cc_keyboard_shortcut_editor_response (GtkDialog *dialog,
+ gint response_id)
+{
+ CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (dialog);
+
+ if (response_id == GTK_RESPONSE_DELETE_EVENT &&
+ self->mode == CC_SHORTCUT_EDITOR_EDIT)
+ {
+ update_shortcut (self);
+ }
+}
+
+static gboolean
+grab_idle (gpointer data)
+{
+ CcKeyboardShortcutEditor *self = data;
+
+ if (self->item && cc_keyboard_item_get_item_type (self->item) != CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
+ grab_seat (self);
+
+ self->grab_idle_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+cc_keyboard_shortcut_editor_show (GtkWidget *widget)
+{
+ CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (widget);
+
+ /* Map before grabbing, so that the window is visible */
+ GTK_WIDGET_CLASS (cc_keyboard_shortcut_editor_parent_class)->show (widget);
+
+ self->grab_idle_id = g_timeout_add (100, grab_idle, self);
+}
+
+static void
+cc_keyboard_shortcut_editor_unrealize (GtkWidget *widget)
+{
+ CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (widget);
+
+ if (self->grab_idle_id) {
+ g_source_remove (self->grab_idle_id);
+ self->grab_idle_id = 0;
+ }
+
+ release_grab (self);
+
+ GTK_WIDGET_CLASS (cc_keyboard_shortcut_editor_parent_class)->unrealize (widget);
+}
+
+static void
+cc_keyboard_shortcut_editor_class_init (CcKeyboardShortcutEditorClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_keyboard_shortcut_editor_finalize;
+ object_class->get_property = cc_keyboard_shortcut_editor_get_property;
+ object_class->set_property = cc_keyboard_shortcut_editor_set_property;
+
+ widget_class->show = cc_keyboard_shortcut_editor_show;
+ widget_class->unrealize = cc_keyboard_shortcut_editor_unrealize;
+ widget_class->key_press_event = cc_keyboard_shortcut_editor_key_press_event;
+
+ dialog_class->close = cc_keyboard_shortcut_editor_close;
+ dialog_class->response = cc_keyboard_shortcut_editor_response;
+
+ /**
+ * CcKeyboardShortcutEditor:keyboard-item:
+ *
+ * The current keyboard shortcut being edited.
+ */
+ properties[PROP_KEYBOARD_ITEM] = g_param_spec_object ("keyboard-item",
+ "Keyboard item",
+ "The keyboard item being edited",
+ CC_TYPE_KEYBOARD_ITEM,
+ G_PARAM_READWRITE);
+
+ /**
+ * CcKeyboardShortcutEditor:panel:
+ *
+ * The current keyboard panel.
+ */
+ properties[PROP_MANAGER] = g_param_spec_object ("manager",
+ "Keyboard manager",
+ "The keyboard manager",
+ CC_TYPE_KEYBOARD_MANAGER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-keyboard-shortcut-editor.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, add_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, change_custom_shortcut_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, command_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, custom_grid);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, custom_shortcut_accel_label);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, custom_shortcut_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, edit_box);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, headerbar);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, name_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, new_shortcut_conflict_label);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, remove_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, replace_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, reset_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, reset_custom_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, set_button);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, shortcut_accel_label);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, shortcut_conflict_label);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, standard_box);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, stack);
+ gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, top_info_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, add_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, cancel_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, change_custom_shortcut_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, command_entry_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, name_entry_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, remove_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, replace_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, reset_custom_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, reset_item_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, set_button_clicked_cb);
+}
+
+static void
+cc_keyboard_shortcut_editor_init (CcKeyboardShortcutEditor *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->mode = CC_SHORTCUT_EDITOR_EDIT;
+ self->custom_is_modifier = TRUE;
+ self->custom_combo = g_new0 (CcKeyCombo, 1);
+
+ gtk_widget_set_direction (GTK_WIDGET (self->custom_shortcut_accel_label), GTK_TEXT_DIR_LTR);
+ gtk_widget_set_direction (GTK_WIDGET (self->shortcut_accel_label), GTK_TEXT_DIR_LTR);
+}
+
+/**
+ * cc_keyboard_shortcut_editor_new:
+ *
+ * Creates a new #CcKeyboardShortcutEditor.
+ *
+ * Returns: (transfer full): a newly created #CcKeyboardShortcutEditor.
+ */
+GtkWidget*
+cc_keyboard_shortcut_editor_new (CcKeyboardManager *manager)
+{
+ return g_object_new (CC_TYPE_KEYBOARD_SHORTCUT_EDITOR,
+ "manager", manager,
+ "use-header-bar", 1,
+ NULL);
+}
+
+/**
+ * cc_keyboard_shortcut_editor_get_item:
+ * @self: a #CcKeyboardShortcutEditor
+ *
+ * Retrieves the current keyboard shortcut being edited.
+ *
+ * Returns: (transfer none)(nullable): a #CcKeyboardItem
+ */
+CcKeyboardItem*
+cc_keyboard_shortcut_editor_get_item (CcKeyboardShortcutEditor *self)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self), NULL);
+
+ return self->item;
+}
+
+/**
+ * cc_keyboard_shortcut_editor_set_item:
+ * @self: a #CcKeyboardShortcutEditor
+ * @item: a #CcKeyboardItem
+ *
+ * Sets the current keyboard shortcut to be edited.
+ */
+void
+cc_keyboard_shortcut_editor_set_item (CcKeyboardShortcutEditor *self,
+ CcKeyboardItem *item)
+{
+ g_return_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self));
+
+ setup_keyboard_item (self, item);
+
+ if (!g_set_object (&self->item, item))
+ return;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_KEYBOARD_ITEM]);
+}
+
+CcShortcutEditorMode
+cc_keyboard_shortcut_editor_get_mode (CcKeyboardShortcutEditor *self)
+{
+ g_return_val_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self), 0);
+
+ return self->mode;
+}
+
+void
+cc_keyboard_shortcut_editor_set_mode (CcKeyboardShortcutEditor *self,
+ CcShortcutEditorMode mode)
+{
+ gboolean is_create_mode;
+
+ g_return_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self));
+
+ self->mode = mode;
+ is_create_mode = mode == CC_SHORTCUT_EDITOR_CREATE;
+
+ gtk_widget_set_visible (GTK_WIDGET (self->new_shortcut_conflict_label), is_create_mode);
+ gtk_stack_set_visible_child (self->custom_shortcut_stack,
+ is_create_mode ? GTK_WIDGET (self->change_custom_shortcut_button) : GTK_WIDGET (self->custom_shortcut_accel_label));
+
+ if (mode == CC_SHORTCUT_EDITOR_CREATE)
+ {
+ /* Cleanup whatever was set before */
+ clear_custom_entries (self);
+
+ set_header_mode (self, HEADER_MODE_ADD);
+ set_shortcut_editor_page (self, PAGE_CUSTOM);
+ gtk_header_bar_set_title (self->headerbar, _("Add Custom Shortcut"));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->command_entry), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->name_entry), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE);
+
+ gtk_widget_hide (GTK_WIDGET (self->reset_custom_button));
+ }
+}
diff --git a/panels/keyboard/cc-keyboard-shortcut-editor.h b/panels/keyboard/cc-keyboard-shortcut-editor.h
new file mode 100644
index 0000000..963309f
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-shortcut-editor.h
@@ -0,0 +1,52 @@
+/* cc-keyboard-shortcut-editor.h
+ *
+ * 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/>.
+ *
+ * Authors: Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "cc-keyboard-item.h"
+#include "cc-keyboard-manager.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_KEYBOARD_SHORTCUT_EDITOR (cc_keyboard_shortcut_editor_get_type ())
+G_DECLARE_FINAL_TYPE (CcKeyboardShortcutEditor, cc_keyboard_shortcut_editor, CC, KEYBOARD_SHORTCUT_EDITOR, GtkDialog)
+
+typedef enum
+{
+ CC_SHORTCUT_EDITOR_CREATE,
+ CC_SHORTCUT_EDITOR_EDIT
+} CcShortcutEditorMode;
+
+GtkWidget* cc_keyboard_shortcut_editor_new (CcKeyboardManager *manager);
+
+CcKeyboardItem* cc_keyboard_shortcut_editor_get_item (CcKeyboardShortcutEditor *self);
+
+void cc_keyboard_shortcut_editor_set_item (CcKeyboardShortcutEditor *self,
+ CcKeyboardItem *item);
+
+CcShortcutEditorMode cc_keyboard_shortcut_editor_get_mode (CcKeyboardShortcutEditor *self);
+
+void cc_keyboard_shortcut_editor_set_mode (CcKeyboardShortcutEditor *self,
+ CcShortcutEditorMode mode);
+
+G_END_DECLS
+
diff --git a/panels/keyboard/cc-keyboard-shortcut-editor.ui b/panels/keyboard/cc-keyboard-shortcut-editor.ui
new file mode 100644
index 0000000..e904709
--- /dev/null
+++ b/panels/keyboard/cc-keyboard-shortcut-editor.ui
@@ -0,0 +1,354 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <template class="CcKeyboardShortcutEditor" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="width_request">400</property>
+ <property name="height_request">300</property>
+ <property name="window_position">center</property>
+ <property name="type_hint">dialog</property>
+ <signal name="close" handler="cancel_button_clicked_cb" object="CcKeyboardShortcutEditor" swapped="no" />
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete" object="CcKeyboardShortcutEditor" swapped="yes"/>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="border_width">12</property>
+ <child>
+ <object class="GtkLabel" id="top_info_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="width_chars">15</property>
+ <property name="max_width_chars">20</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkBox" id="edit_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <property name="expand">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resource">/org/gnome/control-center/keyboard/enter-keyboard-shortcut.svg</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="label" translatable="yes">Press Esc to cancel or Backspace to disable the keyboard shortcut.</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="standard_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">18</property>
+ <child type="center">
+ <object class="GtkShortcutLabel" id="shortcut_accel_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="disabled-text" translatable="yes">Disabled</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="reset_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">none</property>
+ <property name="halign">end</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon-name">edit-clear-symbolic</property>
+ </object>
+ </child>
+ <signal name="clicked" handler="reset_item_clicked_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="shortcut_conflict_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="width_chars">15</property>
+ <property name="max_width_chars">20</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid" id="custom_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Name</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Command</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Shortcut</property>
+ <property name="xalign">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="new_shortcut_conflict_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="width_chars">15</property>
+ <property name="max_width_chars">20</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <signal name="notify::text" handler="name_entry_changed_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="command_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <signal name="notify::text" handler="command_entry_changed_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="reset_custom_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">none</property>
+ <property name="halign">end</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon-name">edit-clear-symbolic</property>
+ </object>
+ </child>
+ <signal name="clicked" handler="reset_custom_clicked_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="custom_shortcut_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="change_custom_shortcut_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Set Shortcut…</property>
+ <signal name="clicked" handler="change_custom_shortcut_button_clicked_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutLabel" id="custom_shortcut_accel_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="disabled-text" translatable="yes">None</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="headerbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_close_button">True</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="cancel_button_clicked_cb" object="CcKeyboardShortcutEditor" swapped="no" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="remove_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Remove</property>
+ <property name="valign">end</property>
+ <signal name="clicked" handler="remove_button_clicked_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ <style>
+ <class name="destructive-action" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="add_button">
+ <property name="label" translatable="yes">Add</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="add_button_clicked_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="replace_button">
+ <property name="label" translatable="yes">Replace</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="replace_button_clicked_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="set_button">
+ <property name="label" translatable="yes">Set</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="set_button_clicked_cb" object="CcKeyboardShortcutEditor" swapped="yes" />
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ <action-widget response="accept">replace_button</action-widget>
+ <action-widget response="apply">set_button</action-widget>
+ <action-widget response="ok" default="true">add_button</action-widget>
+ </action-widgets>
+ </template>
+ <object class="GtkSizeGroup">
+ <widgets>
+ <widget name="cancel_button"/>
+ <widget name="add_button"/>
+ <widget name="replace_button"/>
+ <widget name="reset_button"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/keyboard/enter-keyboard-shortcut.svg b/panels/keyboard/enter-keyboard-shortcut.svg
new file mode 100644
index 0000000..b7ce2e4
--- /dev/null
+++ b/panels/keyboard/enter-keyboard-shortcut.svg
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="256"
+ height="72"
+ viewBox="0 0 256 72.000001"
+ id="svg3611"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="enter-keyboard-shortcut.svg">
+ <defs
+ id="defs3613" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.8"
+ inkscape:cx="137.98997"
+ inkscape:cy="34.663602"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:window-width="1366"
+ inkscape:window-height="704"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata3616">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Camada 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-980.36216)">
+ <g
+ id="g3715"
+ transform="translate(-503.23415,689.94658)">
+ <path
+ d="m 509.66363,325.47627 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19813 1.43306,0.67185 1.50029,1.50028 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50028 z"
+ id="path27275"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 543.62146,325.47627 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19813 1.43308,0.67185 1.50029,1.50028 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.5003,-1.50028 z"
+ id="path27277"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 577.57927,325.47627 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19813 1.43308,0.67185 1.50029,1.50028 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.6952,-1.70681 1.5003,-1.50028 z"
+ id="path27279"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 611.12326,325.47627 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19813 1.43306,0.67185 1.50029,1.50028 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50028 z"
+ id="path5218"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 645.08109,325.47627 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19813 1.43308,0.67185 1.50029,1.50028 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33168,-8.70575 1.99751,-13.05863 0.12565,-0.82161 0.69519,-1.70681 1.50031,-1.50028 z"
+ id="path5220"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 679.0389,325.47627 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19813 1.43308,0.67185 1.50029,1.50028 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33168,-8.70575 1.99751,-13.05863 0.12565,-0.82161 0.69521,-1.70681 1.50031,-1.50028 z"
+ id="path5222"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 712.58289,325.47627 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19813 1.43306,0.67185 1.50029,1.50028 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35287 1.33167,-8.70575 1.99751,-13.05863 0.12565,-0.82161 0.69519,-1.70681 1.50029,-1.50028 z"
+ id="path4829"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.60000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 516.19044,335.26648 c 5.53002,1.41851 18.51389,1.41851 24.2936,0 0.8072,-0.19812 1.43306,0.67186 1.50028,1.50029 l 1.99752,13.05863 -1.50029,0 -28.28862,0 -1.50029,0 c 0.66584,-4.35288 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50029 z"
+ id="path3662"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 550.14827,335.26648 c 5.53,1.41851 18.51387,1.41851 24.29358,0 0.8072,-0.19812 1.43307,0.67186 1.50028,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66584,-4.35288 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.5003,-1.50029 z"
+ id="path3664"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 584.10608,335.26648 c 5.53,1.41851 18.51387,1.41851 24.29357,0 0.80721,-0.19812 1.43308,0.67186 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66584,-4.35288 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.6952,-1.70681 1.5003,-1.50029 z"
+ id="path3666"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 617.65007,335.26648 c 5.53002,1.41851 18.51389,1.41851 24.29359,0 0.80721,-0.19812 1.43306,0.67186 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66584,-4.35288 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50029 z"
+ id="path3668"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 651.6079,335.26648 c 5.53,1.41851 18.51387,1.41851 24.29357,0 0.80721,-0.19812 1.43308,0.67186 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.5003,-1.50029 z"
+ id="path3670"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 685.56571,335.26648 c 5.53,1.41851 18.51387,1.41851 24.29357,0 0.80721,-0.19812 1.43308,0.67186 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33168,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.6952,-1.70681 1.5003,-1.50029 z"
+ id="path3672"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 719.1097,335.26648 c 5.53002,1.41851 18.51389,1.41851 24.29359,0 0.80721,-0.19812 1.43306,0.67186 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33167,-8.70575 1.99752,-13.05863 0.12564,-0.82161 0.69518,-1.70681 1.50028,-1.50029 z"
+ id="path3674"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.60000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 525.98066,345.0567 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19812 1.43306,0.67185 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33167,-8.70576 1.99752,-13.05863 0.12564,-0.82162 0.69518,-1.70681 1.50028,-1.50029 z"
+ id="path3676"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 559.93849,345.0567 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19812 1.43308,0.67185 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33168,-8.70576 1.99751,-13.05863 0.12565,-0.82162 0.69519,-1.70681 1.50031,-1.50029 z"
+ id="path3678"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 593.8963,345.0567 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19812 1.43308,0.67185 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33168,-8.70576 1.99751,-13.05863 0.12565,-0.82162 0.69521,-1.70681 1.50031,-1.50029 z"
+ id="path3680"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 627.44029,345.0567 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.80721,-0.19812 1.43306,0.67185 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28862,0 -1.50029,0 c 0.66583,-4.35288 1.33167,-8.70576 1.99751,-13.05863 0.12565,-0.82162 0.69519,-1.70681 1.50029,-1.50029 z"
+ id="path3682"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 661.39812,345.0567 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.80721,-0.19812 1.43308,0.67185 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28863,0 -1.50028,0 c 0.66583,-4.35288 1.33168,-8.70576 1.99751,-13.05863 0.12564,-0.82162 0.69519,-1.70681 1.50031,-1.50029 z"
+ id="path3684"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 695.35593,345.0567 c 5.53,1.4185 18.51387,1.4185 24.29357,0 0.8072,-0.19812 1.43308,0.67185 1.50029,1.50029 l 1.99753,13.05863 -1.5003,0 -28.28863,0 -1.50028,0 c 0.66583,-4.35288 1.33168,-8.70576 1.99751,-13.05863 0.12564,-0.82162 0.69521,-1.70681 1.50031,-1.50029 z"
+ id="path3686"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.59999979;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 728.89992,345.0567 c 5.53002,1.4185 18.51389,1.4185 24.29359,0 0.8072,-0.19812 1.43306,0.67185 1.50029,1.50029 l 1.99751,13.05863 -1.50028,0 -28.28863,0 -1.50028,0 c 0.66583,-4.35288 1.33166,-8.70576 1.99751,-13.05863 0.12564,-0.82162 0.69519,-1.70681 1.50029,-1.50029 z"
+ id="path3688"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.60000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:nodetypes="sssccccss"
+ inkscape:connector-curvature="0" />
+ <g
+ id="g3713"
+ transform="matrix(1.359752,0,0,1.359752,418.09336,-671.08525)">
+ <path
+ d="m 95.250257,715.10933 0,1.09089 c -1.31e-4,0.0113 -5.02e-4,0.0227 0,0.0341 0.01222,0.27812 0.140266,0.55621 0.340902,0.74999 l 5.693061,5.76124 5.65897,-5.76124 c 0.20529,-0.20532 0.30681,-0.49473 0.30681,-0.78413 l 0,-1.09089 -1.09088,0 c -0.28941,0 -0.57881,0.10156 -0.78408,0.30681 l -4.09082,4.15901 -4.124913,-4.15901 c -0.212319,-0.22989 -0.511898,-0.33071 -0.818164,-0.30681 l -1.090886,0 z"
+ id="path3715"
+ style="color:#bebebe;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;enable-background:new"
+ inkscape:connector-curvature="0" />
+ <rect
+ height="11.999745"
+ id="rect3717"
+ rx="0"
+ ry="0"
+ style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;enable-background:accumulate"
+ transform="scale(-1,1)"
+ width="2.1817718"
+ x="-102.34102"
+ y="708.92743" />
+ </g>
+ <g
+ id="g3740"
+ transform="matrix(1.359752,0,0,1.359752,492.12198,-661.29504)">
+ <path
+ d="m 95.250257,715.10933 0,1.09089 c -1.31e-4,0.0113 -5.02e-4,0.0227 0,0.0341 0.01222,0.27812 0.140266,0.55621 0.340902,0.74999 l 5.693061,5.76124 5.65897,-5.76124 c 0.20529,-0.20532 0.30681,-0.49473 0.30681,-0.78413 l 0,-1.09089 -1.09088,0 c -0.28941,0 -0.57881,0.10156 -0.78408,0.30681 l -4.09082,4.15901 -4.124913,-4.15901 c -0.212319,-0.22989 -0.511898,-0.33071 -0.818164,-0.30681 l -1.090886,0 z"
+ id="path3742"
+ style="color:#bebebe;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;enable-background:new"
+ inkscape:connector-curvature="0" />
+ <rect
+ height="11.999745"
+ id="rect3744"
+ rx="0"
+ ry="0"
+ style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;enable-background:accumulate"
+ transform="scale(-1,1)"
+ width="2.1817718"
+ x="-102.34102"
+ y="708.92743" />
+ </g>
+ <g
+ id="g3746"
+ transform="matrix(1.359752,0,0,1.359752,593.58161,-661.29504)">
+ <path
+ d="m 95.250257,715.10933 0,1.09089 c -1.31e-4,0.0113 -5.02e-4,0.0227 0,0.0341 0.01222,0.27812 0.140266,0.55621 0.340902,0.74999 l 5.693061,5.76124 5.65897,-5.76124 c 0.20529,-0.20532 0.30681,-0.49473 0.30681,-0.78413 l 0,-1.09089 -1.09088,0 c -0.28941,0 -0.57881,0.10156 -0.78408,0.30681 l -4.09082,4.15901 -4.124913,-4.15901 c -0.212319,-0.22989 -0.511898,-0.33071 -0.818164,-0.30681 l -1.090886,0 z"
+ id="path3748"
+ style="color:#bebebe;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:'Andale Mono';-inkscape-font-specification:'Andale Mono';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;enable-background:new"
+ inkscape:connector-curvature="0" />
+ <rect
+ height="11.999745"
+ id="rect3750"
+ rx="0"
+ ry="0"
+ style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:#000100;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;enable-background:accumulate"
+ transform="scale(-1,1)"
+ width="2.1817718"
+ x="-102.34102"
+ y="708.92743" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/panels/keyboard/gnome-keybindings.pc.in b/panels/keyboard/gnome-keybindings.pc.in
new file mode 100644
index 0000000..e099b4c
--- /dev/null
+++ b/panels/keyboard/gnome-keybindings.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+datarootdir=@datarootdir@
+datadir=@datadir@
+pkgdatadir=${datadir}/@PACKAGE@
+keysdir=${pkgdatadir}/keybindings
+
+Name: gnome-keybindings
+Description: Keybindings configuration for GNOME applications
+Version: @VERSION@
+
diff --git a/panels/keyboard/gnome-keyboard-panel.desktop.in.in b/panels/keyboard/gnome-keyboard-panel.desktop.in.in
new file mode 100644
index 0000000..9200d69
--- /dev/null
+++ b/panels/keyboard/gnome-keyboard-panel.desktop.in.in
@@ -0,0 +1,18 @@
+[Desktop Entry]
+Name=Keyboard Shortcuts
+Comment=View and change keyboard shortcuts and set your typing preferences
+Exec=gnome-control-center keyboard
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=input-keyboard
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-DevicesSettings;
+OnlyShowIn=GNOME;Unity;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-control-center
+X-GNOME-Bugzilla-Component=keyboard
+X-GNOME-Bugzilla-Version=@VERSION@
+# Translators: Search terms to find the Keyboard panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Shortcut;Workspace;Window;Resize;Zoom;Contrast;Input;Source;Lock;Volume;
diff --git a/panels/keyboard/keyboard-shortcuts.c b/panels/keyboard/keyboard-shortcuts.c
new file mode 100644
index 0000000..ba748a6
--- /dev/null
+++ b/panels/keyboard/keyboard-shortcuts.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2010 Intel, Inc
+ * Copyright (C) 2014 Red Hat, 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/>.
+ *
+ * Authors: Thomas Wood <thomas.wood@intel.com>
+ * Rodrigo Moya <rodrigo@gnome.org>
+ * Christophe Fergeau <cfergeau@redhat.com>
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include "keyboard-shortcuts.h"
+#include "cc-keyboard-option.h"
+
+#define CUSTOM_KEYS_BASENAME "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings"
+
+static char *
+replace_pictures_folder (const char *description)
+{
+ g_autoptr(GRegex) pictures_regex = NULL;
+ const char *path;
+ g_autofree gchar *dirname = NULL;
+ g_autofree gchar *ret = NULL;
+
+ if (description == NULL)
+ return NULL;
+
+ if (strstr (description, "$PICTURES") == NULL)
+ return g_strdup (description);
+
+ pictures_regex = g_regex_new ("\\$PICTURES", 0, 0, NULL);
+ path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+ dirname = g_filename_display_basename (path);
+ ret = g_regex_replace (pictures_regex, description, -1,
+ 0, dirname, 0, NULL);
+
+ if (ret == NULL)
+ return g_strdup (description);
+
+ return g_steal_pointer (&ret);
+}
+
+static void
+parse_start_tag (GMarkupParseContext *ctx,
+ const gchar *element_name,
+ const gchar **attr_names,
+ const gchar **attr_values,
+ gpointer user_data,
+ GError **error)
+{
+ KeyList *keylist = (KeyList *) user_data;
+ KeyListEntry key;
+ const char *name, *schema, *description, *package, *context, *orig_description, *reverse_entry;
+ gboolean is_reversed, hidden;
+
+ name = NULL;
+ schema = NULL;
+ package = NULL;
+ context = NULL;
+
+ /* The top-level element, names the section in the tree */
+ if (g_str_equal (element_name, "KeyListEntries"))
+ {
+ const char *wm_name = NULL;
+ const char *group = NULL;
+
+ while (*attr_names && *attr_values)
+ {
+ if (g_str_equal (*attr_names, "name"))
+ {
+ if (**attr_values)
+ name = *attr_values;
+ } else if (g_str_equal (*attr_names, "group")) {
+ if (**attr_values)
+ group = *attr_values;
+ } else if (g_str_equal (*attr_names, "wm_name")) {
+ if (**attr_values)
+ wm_name = *attr_values;
+ } else if (g_str_equal (*attr_names, "schema")) {
+ if (**attr_values)
+ schema = *attr_values;
+ } else if (g_str_equal (*attr_names, "package")) {
+ if (**attr_values)
+ package = *attr_values;
+ }
+ ++attr_names;
+ ++attr_values;
+ }
+
+ if (name)
+ {
+ if (keylist->name)
+ g_warning ("Duplicate section name");
+ g_free (keylist->name);
+ keylist->name = g_strdup (name);
+ }
+ if (wm_name)
+ {
+ if (keylist->wm_name)
+ g_warning ("Duplicate window manager name");
+ g_free (keylist->wm_name);
+ keylist->wm_name = g_strdup (wm_name);
+ }
+ if (package)
+ {
+ if (keylist->package)
+ g_warning ("Duplicate gettext package name");
+ g_free (keylist->package);
+ keylist->package = g_strdup (package);
+ bind_textdomain_codeset (keylist->package, "UTF-8");
+ }
+ if (group)
+ {
+ if (keylist->group)
+ g_warning ("Duplicate group");
+ g_free (keylist->group);
+ keylist->group = g_strdup (group);
+ }
+ if (schema)
+ {
+ if (keylist->schema)
+ g_warning ("Duplicate schema");
+ g_free (keylist->schema);
+ keylist->schema = g_strdup (schema);
+ }
+ return;
+ }
+
+ if (!g_str_equal (element_name, "KeyListEntry")
+ || attr_names == NULL
+ || attr_values == NULL)
+ return;
+
+ schema = NULL;
+ description = NULL;
+ context = NULL;
+ orig_description = NULL;
+ reverse_entry = NULL;
+ is_reversed = FALSE;
+ hidden = FALSE;
+
+ while (*attr_names && *attr_values)
+ {
+ if (g_str_equal (*attr_names, "name"))
+ {
+ /* skip if empty */
+ if (**attr_values)
+ name = *attr_values;
+ } else if (g_str_equal (*attr_names, "schema")) {
+ if (**attr_values) {
+ schema = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "description")) {
+ if (**attr_values)
+ orig_description = *attr_values;
+ } else if (g_str_equal (*attr_names, "msgctxt")) {
+ if (**attr_values)
+ context = *attr_values;
+ } else if (g_str_equal (*attr_names, "reverse-entry")) {
+ if (**attr_values)
+ reverse_entry = *attr_values;
+ } else if (g_str_equal (*attr_names, "is-reversed")) {
+ if (g_str_equal (*attr_values, "true"))
+ is_reversed = TRUE;
+ } else if (g_str_equal (*attr_names, "hidden")) {
+ if (g_str_equal (*attr_values, "true"))
+ hidden = TRUE;
+ }
+
+ ++attr_names;
+ ++attr_values;
+ }
+
+ if (name == NULL)
+ return;
+
+ if (schema == NULL &&
+ keylist->schema == NULL) {
+ g_debug ("Ignored GConf keyboard shortcut '%s'", name);
+ return;
+ }
+
+ if (context != NULL)
+ description = g_dpgettext2 (keylist->package, context, orig_description);
+ else
+ description = dgettext (keylist->package, orig_description);
+
+ key.name = g_strdup (name);
+ key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS;
+ key.description = replace_pictures_folder (description);
+ key.schema = schema ? g_strdup (schema) : g_strdup (keylist->schema);
+ key.reverse_entry = g_strdup (reverse_entry);
+ key.is_reversed = is_reversed;
+ key.hidden = hidden;
+ g_array_append_val (keylist->entries, key);
+}
+
+static const guint forbidden_keyvals[] = {
+ /* Navigation keys */
+ GDK_KEY_Home,
+ GDK_KEY_Left,
+ GDK_KEY_Up,
+ GDK_KEY_Right,
+ GDK_KEY_Down,
+ GDK_KEY_Page_Up,
+ GDK_KEY_Page_Down,
+ GDK_KEY_End,
+ GDK_KEY_Tab,
+
+ /* Return */
+ GDK_KEY_KP_Enter,
+ GDK_KEY_Return,
+
+ GDK_KEY_Mode_switch
+};
+
+static gboolean
+keyval_is_forbidden (guint keyval)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) {
+ if (keyval == forbidden_keyvals[i])
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+is_valid_binding (CcKeyCombo *combo)
+{
+ if ((combo->mask == 0 || combo->mask == GDK_SHIFT_MASK) && combo->keycode != 0)
+ {
+ guint keyval = combo->keyval;
+
+ if ((keyval >= GDK_KEY_a && keyval <= GDK_KEY_z)
+ || (keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z)
+ || (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9)
+ || (keyval >= GDK_KEY_kana_fullstop && keyval <= GDK_KEY_semivoicedsound)
+ || (keyval >= GDK_KEY_Arabic_comma && keyval <= GDK_KEY_Arabic_sukun)
+ || (keyval >= GDK_KEY_Serbian_dje && keyval <= GDK_KEY_Cyrillic_HARDSIGN)
+ || (keyval >= GDK_KEY_Greek_ALPHAaccent && keyval <= GDK_KEY_Greek_omega)
+ || (keyval >= GDK_KEY_hebrew_doublelowline && keyval <= GDK_KEY_hebrew_taf)
+ || (keyval >= GDK_KEY_Thai_kokai && keyval <= GDK_KEY_Thai_lekkao)
+ || (keyval >= GDK_KEY_Hangul_Kiyeog && keyval <= GDK_KEY_Hangul_J_YeorinHieuh)
+ || (keyval == GDK_KEY_space && combo->mask == 0)
+ || keyval_is_forbidden (keyval)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+gboolean
+is_empty_binding (CcKeyCombo *combo)
+{
+ if (combo->keyval == 0 &&
+ combo->mask == 0 &&
+ combo->keycode == 0)
+ return TRUE;
+ return FALSE;
+}
+
+gboolean
+is_valid_accel (CcKeyCombo *combo)
+{
+ /* Unlike gtk_accelerator_valid(), we want to allow Tab when combined
+ * with some modifiers (Alt+Tab and friends)
+ */
+ return gtk_accelerator_valid (combo->keyval, combo->mask) ||
+ (combo->keyval == GDK_KEY_Tab && combo->mask != 0);
+}
+
+gchar*
+find_free_settings_path (GSettings *settings)
+{
+ g_auto(GStrv) used_names = NULL;
+ g_autofree gchar *dir = NULL;
+ int i, num, n_names;
+
+ used_names = g_settings_get_strv (settings, "custom-keybindings");
+ n_names = g_strv_length (used_names);
+
+ for (num = 0; dir == NULL; num++)
+ {
+ g_autofree gchar *tmp = NULL;
+ gboolean found = FALSE;
+
+ tmp = g_strdup_printf ("%s/custom%d/", CUSTOM_KEYS_BASENAME, num);
+ for (i = 0; i < n_names && !found; i++)
+ found = strcmp (used_names[i], tmp) == 0;
+
+ if (!found)
+ dir = g_steal_pointer (&tmp);
+ }
+
+ return g_steal_pointer (&dir);
+}
+
+KeyList*
+parse_keylist_from_file (const gchar *path)
+{
+ KeyList *keylist;
+ g_autoptr(GError) err = NULL;
+ g_autofree gchar *buf = NULL;
+ gsize buf_len;
+ guint i;
+
+ g_autoptr(GMarkupParseContext) ctx = NULL;
+ GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL };
+
+ /* Parse file */
+ if (!g_file_get_contents (path, &buf, &buf_len, &err))
+ return NULL;
+
+ keylist = g_new0 (KeyList, 1);
+ keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
+ ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL);
+
+ if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err))
+ {
+ g_warning ("Failed to parse '%s': '%s'", path, err->message);
+ g_free (keylist->name);
+ g_free (keylist->package);
+ g_free (keylist->wm_name);
+
+ for (i = 0; i < keylist->entries->len; i++)
+ g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name);
+
+ g_array_free (keylist->entries, TRUE);
+ g_free (keylist);
+ return NULL;
+ }
+
+ return keylist;
+}
+
+/*
+ * Stolen from GtkCellRendererAccel:
+ * https://git.gnome.org/browse/gtk+/tree/gtk/gtkcellrendereraccel.c#n261
+ */
+gchar*
+convert_keysym_state_to_string (CcKeyCombo *combo)
+{
+ gchar *name;
+
+ if (combo->keyval == 0 && combo->keycode == 0)
+ {
+ /* This label is displayed in a treeview cell displaying
+ * a disabled accelerator key combination.
+ */
+ name = g_strdup (_("Disabled"));
+ }
+ else
+ {
+ name = gtk_accelerator_get_label_with_keycode (NULL, combo->keyval, combo->keycode, combo->mask);
+
+ if (name == NULL)
+ name = gtk_accelerator_name_with_keycode (NULL, combo->keyval, combo->keycode, combo->mask);
+ }
+
+ return name;
+}
diff --git a/panels/keyboard/keyboard-shortcuts.h b/panels/keyboard/keyboard-shortcuts.h
new file mode 100644
index 0000000..e303486
--- /dev/null
+++ b/panels/keyboard/keyboard-shortcuts.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Intel, 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/>.
+ *
+ * Authors: Thomas Wood <thomas.wood@intel.com>
+ * Rodrigo Moya <rodrigo@gnome.org>
+ */
+
+#include <gtk/gtk.h>
+#include <shell/cc-panel.h>
+
+#include "cc-keyboard-item.h"
+
+typedef struct {
+ /* The untranslated name, combine with ->package to translate */
+ char *name;
+ /* The group of keybindings (system or application) */
+ char *group;
+ /* The gettext package to use to translate the section title */
+ char *package;
+ /* Name of the window manager the keys would apply to */
+ char *wm_name;
+ /* The GSettings schema for the whole file, if any */
+ char *schema;
+ /* an array of KeyListEntry */
+ GArray *entries;
+} KeyList;
+
+typedef struct
+{
+ CcKeyboardItemType type;
+ char *schema; /* GSettings schema name, if any */
+ char *description; /* description for GSettings types */
+ char *name; /* GSettings schema path, or GSettings key name depending on type */
+ char *reverse_entry;
+ gboolean is_reversed;
+ gboolean hidden;
+} KeyListEntry;
+
+typedef struct {
+ CcKeyboardItem *orig_item;
+ CcKeyboardItem *conflict_item;
+ guint new_keyval;
+ GdkModifierType new_mask;
+ guint new_keycode;
+} CcUniquenessData;
+
+enum
+{
+ SECTION_DESCRIPTION_COLUMN,
+ SECTION_ID_COLUMN,
+ SECTION_GROUP_COLUMN,
+ SECTION_N_COLUMNS
+};
+
+gchar* find_free_settings_path (GSettings *settings);
+
+gboolean is_valid_binding (CcKeyCombo *combo);
+
+gboolean is_empty_binding (CcKeyCombo *combo);
+
+gboolean is_valid_accel (CcKeyCombo *combo);
+
+KeyList* parse_keylist_from_file (const gchar *path);
+
+gchar* convert_keysym_state_to_string (CcKeyCombo *combo);
diff --git a/panels/keyboard/keyboard.gresource.xml b/panels/keyboard/keyboard.gresource.xml
new file mode 100644
index 0000000..0283dae
--- /dev/null
+++ b/panels/keyboard/keyboard.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/keyboard">
+ <file preprocess="xml-stripblanks">enter-keyboard-shortcut.svg</file>
+ <file preprocess="xml-stripblanks">cc-alt-chars-key-dialog.ui</file>
+ <file preprocess="xml-stripblanks">cc-keyboard-panel.ui</file>
+ <file preprocess="xml-stripblanks">cc-keyboard-shortcut-editor.ui</file>
+ </gresource>
+</gresources>
diff --git a/panels/keyboard/meson.build b/panels/keyboard/meson.build
new file mode 100644
index 0000000..2f61ed0
--- /dev/null
+++ b/panels/keyboard/meson.build
@@ -0,0 +1,94 @@
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+ input: desktop + '.in.in',
+ output: desktop + '.in',
+ configuration: desktop_conf
+)
+
+i18n.merge_file(
+ desktop,
+ type: 'desktop',
+ input: desktop_in,
+ output: desktop,
+ po_dir: po_dir,
+ install: true,
+ install_dir: control_center_desktopdir
+)
+
+pc_conf = configuration_data()
+pc_conf.set('prefix', control_center_prefix)
+pc_conf.set('datarootdir', control_center_datadir)
+pc_conf.set('datadir', control_center_datadir)
+pc_conf.set('PACKAGE', meson.project_name())
+pc_conf.set('VERSION', meson.project_version())
+
+pc = 'gnome-keybindings.pc'
+
+configure_file(
+ input: pc + '.in',
+ output: pc,
+ install: true,
+ install_dir: join_paths(control_center_datadir, 'pkgconfig'),
+ configuration: pc_conf
+)
+
+xml_files = [
+ '00-multimedia.xml',
+ '01-input-sources.xml',
+ '01-launchers.xml',
+ '01-screenshot.xml',
+ '01-system.xml',
+ '50-accessibility.xml'
+]
+
+foreach file: xml_files
+ i18n.merge_file(
+ file,
+ input: file + '.in',
+ output: file,
+ po_dir: po_dir,
+ data_dirs: its_dir,
+ install: true,
+ install_dir: join_paths(control_center_pkgdatadir, 'keybindings')
+ )
+endforeach
+
+sources = files(
+ 'cc-alt-chars-key-dialog.c',
+ 'cc-keyboard-panel.c',
+ 'cc-keyboard-item.c',
+ 'cc-keyboard-manager.c',
+ 'cc-keyboard-option.c',
+ 'cc-keyboard-shortcut-editor.c',
+ 'wm-common.c',
+ 'keyboard-shortcuts.c'
+)
+
+resource_data = files(
+ 'enter-keyboard-shortcut.svg',
+ 'cc-keyboard-panel.ui',
+ 'cc-keyboard-shortcut-editor.ui',
+)
+
+sources += gnome.compile_resources(
+ 'cc-' + cappletname + '-resources',
+ cappletname + '.gresource.xml',
+ c_name: 'cc_' + cappletname,
+ dependencies: resource_data,
+ export: true
+)
+
+deps = common_deps + [
+ gnome_desktop_dep,
+ x11_dep
+]
+
+panels_libs += static_library(
+ cappletname,
+ sources: sources,
+ include_directories: [top_inc, common_inc],
+ dependencies: deps,
+ c_args: cflags
+)
diff --git a/panels/keyboard/wm-common.c b/panels/keyboard/wm-common.c
new file mode 100644
index 0000000..0cddb10
--- /dev/null
+++ b/panels/keyboard/wm-common.c
@@ -0,0 +1,261 @@
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdk.h>
+#include <string.h>
+#include <glib.h>
+#include <glib-object.h>
+#include "wm-common.h"
+
+typedef struct _WMCallbackData
+{
+ GFunc func;
+ gpointer data;
+} WMCallbackData;
+
+/* Our WM Window */
+static Window wm_window = None;
+
+/*
+ * These push/pop implementations are based on the GDK versions, except that they
+ * use only non-deprecated API.
+ */
+
+static GPtrArray*
+push_error_traps (void)
+{
+ GdkDisplayManager *manager;
+ g_autoptr(GPtrArray) trapped_displays = NULL;
+ g_autoptr(GSList) displays = NULL;
+ GSList *l;
+
+ manager = gdk_display_manager_get ();
+ displays = gdk_display_manager_list_displays (manager);
+ trapped_displays = g_ptr_array_new ();
+
+ for (l = displays; l != NULL; l = l->next)
+ {
+ GdkDisplay *display = l->data;
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (display))
+ {
+ gdk_x11_display_error_trap_push (display);
+ g_ptr_array_add (trapped_displays, display);
+ }
+#endif
+ }
+
+ return g_steal_pointer (&trapped_displays);
+}
+
+static gint
+pop_error_traps (GPtrArray *displays)
+{
+ guint i;
+ gint result;
+
+ result = 0;
+
+ for (i = 0; displays && i < displays->len; i++)
+ {
+ GdkDisplay *display;
+ gint code = 0;
+
+ display = g_ptr_array_index (displays, i);
+
+#ifdef GDK_WINDOWING_X11
+ code = gdk_x11_display_error_trap_pop (display);
+#endif
+
+ if (code != 0)
+ result = code;
+ }
+
+ return result;
+}
+
+static char *
+wm_common_get_window_manager_property (Atom atom)
+{
+ g_autoptr(GPtrArray) trapped_displays = NULL;
+ Atom utf8_string, type;
+ int result;
+ char *retval;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ gchar *val;
+
+ if (wm_window == None)
+ return NULL;
+
+ utf8_string = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "UTF8_STRING", False);
+
+ trapped_displays = push_error_traps ();
+
+ val = NULL;
+ result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ wm_window,
+ atom,
+ 0, G_MAXLONG,
+ False, utf8_string,
+ &type, &format, &nitems,
+ &bytes_after, (guchar **) &val);
+
+ if (pop_error_traps (trapped_displays) ||
+ result != Success ||
+ type != utf8_string ||
+ format != 8 ||
+ nitems == 0 ||
+ !g_utf8_validate (val, nitems, NULL))
+ {
+ retval = NULL;
+ }
+ else
+ {
+ retval = g_strndup (val, nitems);
+ }
+
+ g_clear_pointer (&val, XFree);
+
+ return retval;
+}
+static gchar*
+wm_common_get_current_window_manager (void)
+{
+ Atom atom = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_WM_NAME", False);
+ char *result;
+
+ result = wm_common_get_window_manager_property (atom);
+ if (result)
+ return result;
+ else
+ return g_strdup (WM_COMMON_UNKNOWN);
+}
+
+GStrv
+wm_common_get_current_keybindings (void)
+{
+ g_autofree gchar* keybindings = NULL;
+ g_auto(GStrv) results = NULL;
+ Atom keybindings_atom;
+
+ keybindings_atom = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_GNOME_WM_KEYBINDINGS", False);
+ keybindings = wm_common_get_window_manager_property (keybindings_atom);
+
+ if (keybindings)
+ {
+ GStrv p;
+
+ results = g_strsplit (keybindings, ",", -1);
+
+ for (p = results; p && *p; p++)
+ g_strstrip (*p);
+ }
+ else
+ {
+ g_autofree gchar *wm_name = NULL;
+ Atom wm_atom;
+ gchar *to_copy[2] = { NULL, NULL };
+
+ wm_atom = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_WM_NAME", False);
+ wm_name = wm_common_get_window_manager_property (wm_atom);
+
+ to_copy[0] = wm_name ? wm_name : WM_COMMON_UNKNOWN;
+
+ results = g_strdupv (to_copy);
+ }
+
+ return g_steal_pointer (&results);
+}
+
+static void
+update_wm_window (void)
+{
+ g_autoptr(GPtrArray) trapped_displays = NULL;
+ Window *xwindow;
+ Atom type;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+
+ XGetWindowProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), GDK_ROOT_WINDOW (),
+ XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_SUPPORTING_WM_CHECK", False),
+ 0, G_MAXLONG, False, XA_WINDOW, &type, &format,
+ &nitems, &bytes_after, (guchar **) &xwindow);
+
+ if (type != XA_WINDOW)
+ {
+ wm_window = None;
+ return;
+ }
+
+ trapped_displays = push_error_traps ();
+
+ XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), *xwindow, StructureNotifyMask | PropertyChangeMask);
+ XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), False);
+
+ if (pop_error_traps (trapped_displays))
+ {
+ XFree (xwindow);
+ wm_window = None;
+ return;
+ }
+
+ wm_window = *xwindow;
+ XFree (xwindow);
+}
+
+static GdkFilterReturn
+wm_window_event_filter (GdkXEvent *xev,
+ GdkEvent *event,
+ gpointer data)
+{
+ WMCallbackData *ncb_data = (WMCallbackData*) data;
+ XEvent *xevent = (XEvent *)xev;
+
+ if ((xevent->type == DestroyNotify &&
+ wm_window != None && xevent->xany.window == wm_window) ||
+ (xevent->type == PropertyNotify &&
+ xevent->xany.window == GDK_ROOT_WINDOW () &&
+ xevent->xproperty.atom == (XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_SUPPORTING_WM_CHECK", False))) ||
+ (xevent->type == PropertyNotify &&
+ wm_window != None && xevent->xany.window == wm_window &&
+ xevent->xproperty.atom == (XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_WM_NAME", False))))
+ {
+ update_wm_window ();
+ (* ncb_data->func) ((gpointer) wm_common_get_current_window_manager (), ncb_data->data);
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+gpointer
+wm_common_register_window_manager_change (GFunc func,
+ gpointer data)
+{
+ WMCallbackData *ncb_data;
+
+ ncb_data = g_new0 (WMCallbackData, 1);
+
+ ncb_data->func = func;
+ ncb_data->data = data;
+
+ gdk_window_add_filter (NULL, wm_window_event_filter, ncb_data);
+
+ update_wm_window ();
+
+ XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), GDK_ROOT_WINDOW (), PropertyChangeMask);
+ XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), False);
+
+ return ncb_data;
+}
+
+void
+wm_common_unregister_window_manager_change (gpointer id)
+{
+ g_return_if_fail (id != NULL);
+
+ gdk_window_remove_filter (NULL, wm_window_event_filter, id);
+ g_free (id);
+}
diff --git a/panels/keyboard/wm-common.h b/panels/keyboard/wm-common.h
new file mode 100644
index 0000000..461c057
--- /dev/null
+++ b/panels/keyboard/wm-common.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#define WM_COMMON_METACITY "Metacity"
+#define WM_COMMON_SAWFISH "Sawfish"
+#define WM_COMMON_UNKNOWN "Unknown"
+
+/* Returns a strv of keybinding names for the window manager;
+ * using _GNOME_WM_KEYBINDINGS if available, _NET_WM_NAME otherwise. */
+GStrv wm_common_get_current_keybindings (void);
+
+gpointer wm_common_register_window_manager_change (GFunc func,
+ gpointer data);
+
+void wm_common_unregister_window_manager_change (gpointer id);