Commit 978ab40f authored by Rui Matos's avatar Rui Matos

keyboard: Add common XKB options to the Typing shortcuts section

Both the compose key and the 3rd level chooser are common and useful
enough to expose in the control center.

Since these shortcuts are a small pre-defined set of only modifier
keys we present them in combo cell renderers.

https://bugzilla.gnome.org/show_bug.cgi?id=682069
parent 4e0bc86e
......@@ -103,8 +103,8 @@ POLKIT_REQUIRED_VERSION=0.103
GSD_REQUIRED_VERSION=3.5.2
NETWORK_MANAGER_REQUIRED_VERSION=0.8.992
LIBNOTIFY_REQUIRED_VERSION=0.7.3
GNOME_DESKTOP_REQUIRED_VERSION=3.5.3
SCHEMAS_REQUIRED_VERSION=3.5.3
GNOME_DESKTOP_REQUIRED_VERSION=3.5.6
SCHEMAS_REQUIRED_VERSION=3.5.5
LIBWACOM_REQUIRED_VERSION=0.5
CLUTTER_REQUIRED_VERSION=1.11.3
GOA_REQUIRED_VERSION=3.5.90
......@@ -130,7 +130,9 @@ PKG_CHECK_MODULES(DATETIME_PANEL, $COMMON_MODULES
PKG_CHECK_MODULES(DISPLAY_PANEL, $COMMON_MODULES gnome-desktop-3.0 >= 3.1.0)
PKG_CHECK_MODULES(INFO_PANEL, $COMMON_MODULES libgtop-2.0
polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION)
PKG_CHECK_MODULES(KEYBOARD_PANEL, $COMMON_MODULES x11)
PKG_CHECK_MODULES(KEYBOARD_PANEL, $COMMON_MODULES
gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION
x11)
PKG_CHECK_MODULES(MEDIA_PANEL, $COMMON_MODULES)
PKG_CHECK_MODULES(MOUSE_PANEL, $COMMON_MODULES xi >= 1.2
gnome-settings-daemon >= $GSD_REQUIRED_VERSION x11)
......
......@@ -10,6 +10,8 @@ libkeyboard_la_SOURCES = \
cc-keyboard-panel.h \
cc-keyboard-item.c \
cc-keyboard-item.h \
cc-keyboard-option.c \
cc-keyboard-option.h \
wm-common.c \
wm-common.h \
keyboard-general.c \
......
/*
* 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 <glib/gi18n.h>
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-xkb-info.h>
#include "cc-keyboard-option.h"
#define CC_TYPE_KEYBOARD_OPTION (cc_keyboard_option_get_type ())
#define CC_KEYBOARD_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_KEYBOARD_OPTION, CcKeyboardOption))
#define CC_KEYBOARD_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TYPE_KEYBOARD_OPTION, CcKeyboardOptionClass))
#define CC_IS_KEYBOARD_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_KEYBOARD_OPTION))
#define CC_IS_KEYBOARD_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TYPE_KEYBOARD_OPTION))
#define CC_KEYBOARD_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_KEYBOARD_OPTION, CcKeyboardOptionClass))
#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"
enum
{
PROP_0,
PROP_GROUP,
PROP_DESCRIPTION
};
enum
{
CHANGED_SIGNAL,
LAST_SIGNAL
};
struct _CcKeyboardOption
{
GObject parent_object;
gchar *group;
gchar *description;
gchar *current_value;
GtkListStore *store;
const gchar * const *whitelist;
};
typedef struct _CcKeyboardOptionClass CcKeyboardOptionClass;
struct _CcKeyboardOptionClass
{
GObjectClass parent_class;
};
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 *xkb_option_lvl3_whitelist[] = {
"lv3:switch",
"lv3:menu_switch",
"lv3:rwin_switch",
"lv3:lalt_switch",
"lv3:ralt_switch",
"lv3:caps_switch",
NULL
};
static const gchar *xkb_option_comp_whitelist[] = {
"compose:ralt",
"compose:rwin",
"compose:menu",
"compose:lctrl",
"compose:rctrl",
"compose:caps",
NULL
};
static GList *objects_list = NULL;
GType cc_keyboard_option_get_type (void);
G_DEFINE_TYPE (CcKeyboardOption, cc_keyboard_option, G_TYPE_OBJECT);
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->whitelist, *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->whitelist = xkb_option_lvl3_whitelist;
else if (g_str_equal (self->group, XKB_OPTION_GROUP_COMP))
self->whitelist = xkb_option_comp_whitelist;
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->whitelist, 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,
"description", _("Alternative Characters Key"),
NULL));
objects_list = g_list_prepend (objects_list,
g_object_new (CC_TYPE_KEYBOARD_OPTION,
"group", XKB_OPTION_GROUP_COMP,
"description", _("Compose Key"),
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)
{
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);
g_free (new_value);
}
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);
}
/*
* 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.
*/
#ifndef __CC_KEYBOARD_OPTION_H__
#define __CC_KEYBOARD_OPTION_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
enum
{
XKB_OPTION_DESCRIPTION_COLUMN,
XKB_OPTION_ID_COLUMN,
XKB_OPTION_N_COLUMNS
};
typedef struct _CcKeyboardOption CcKeyboardOption;
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
#endif /* __CC_KEYBOARD_OPTION_H__ */
......@@ -22,8 +22,10 @@
#include <config.h>
#include <glib/gi18n.h>
#include "keyboard-shortcuts.h"
#include "cc-keyboard-item.h"
#include "cc-keyboard-option.h"
#include "wm-common.h"
#define BINDINGS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys"
......@@ -53,10 +55,17 @@ typedef struct
char *name; /* GSettings schema path, or GSettings key name depending on type */
} KeyListEntry;
typedef enum
{
SHORTCUT_TYPE_KEY_ENTRY,
SHORTCUT_TYPE_XKB_OPTION,
} ShortcutType;
enum
{
DETAIL_DESCRIPTION_COLUMN,
DETAIL_KEYENTRY_COLUMN,
DETAIL_TYPE_COLUMN,
DETAIL_N_COLUMNS
};
......@@ -668,34 +677,53 @@ accel_set_func (GtkTreeViewColumn *tree_column,
GtkTreeIter *iter,
gpointer data)
{
CcKeyboardItem *item;
gpointer entry;
ShortcutType type;
gtk_tree_model_get (model, iter,
DETAIL_KEYENTRY_COLUMN, &item,
DETAIL_KEYENTRY_COLUMN, &entry,
DETAIL_TYPE_COLUMN, &type,
-1);
if (item == NULL)
g_object_set (cell,
"visible", FALSE,
NULL);
else if (! item->editable)
g_object_set (cell,
"visible", TRUE,
"editable", FALSE,
"accel-key", item->keyval,
"accel-mods", item->mask,
"keycode", item->keycode,
"style", PANGO_STYLE_ITALIC,
NULL);
else
g_object_set (cell,
"visible", TRUE,
"editable", TRUE,
"accel-key", item->keyval,
"accel-mods", item->mask,
"keycode", item->keycode,
"style", PANGO_STYLE_NORMAL,
NULL);
gtk_cell_renderer_set_visible (cell, FALSE);
if (type == SHORTCUT_TYPE_XKB_OPTION &&
GTK_IS_CELL_RENDERER_COMBO (cell))
{
CcKeyboardOption *option = entry;
gtk_cell_renderer_set_visible (cell, TRUE);
g_object_set (cell,
"model", cc_keyboard_option_get_store (option),
"text", cc_keyboard_option_get_current_value_description (option),
NULL);
}
else if (type == SHORTCUT_TYPE_KEY_ENTRY &&
GTK_IS_CELL_RENDERER_TEXT (cell) &&
!GTK_IS_CELL_RENDERER_COMBO (cell) &&
entry != NULL)
{
CcKeyboardItem *item = entry;
gtk_cell_renderer_set_visible (cell, TRUE);
if (item->editable)
g_object_set (cell,
"editable", TRUE,
"accel-key", item->keyval,
"accel-mods", item->mask,
"keycode", item->keycode,
"style", PANGO_STYLE_NORMAL,
NULL);
else
g_object_set (cell,
"editable", FALSE,
"accel-key", item->keyval,
"accel-mods", item->mask,
"keycode", item->keycode,
"style", PANGO_STYLE_ITALIC,
NULL);
}
}
static void
......@@ -705,21 +733,34 @@ description_set_func (GtkTreeViewColumn *tree_column,
GtkTreeIter *iter,
gpointer data)
{
gchar *description;
CcKeyboardItem *item;
ShortcutType type;
gtk_tree_model_get (model, iter,
DETAIL_DESCRIPTION_COLUMN, &description,
DETAIL_KEYENTRY_COLUMN, &item,
DETAIL_TYPE_COLUMN, &type,
-1);
if (item != NULL)
g_object_set (cell,
"editable", FALSE,
"text", item->description != NULL ?
item->description : _("<Unknown Action>"),
NULL);
if (type == SHORTCUT_TYPE_XKB_OPTION)
{
g_object_set (cell, "text", description, NULL);
}
else
g_object_set (cell,
"editable", FALSE, NULL);
{
if (item != NULL)
g_object_set (cell,
"editable", FALSE,
"text", item->description != NULL ?
item->description : _("<Unknown Action>"),
NULL);
else
g_object_set (cell,
"editable", FALSE, NULL);
}
g_free (description);
}
static void
......@@ -730,18 +771,42 @@ shortcut_selection_changed (GtkTreeSelection *selection, gpointer data)
GtkTreeIter iter;
CcKeyboardItem *item;
gboolean can_remove;
ShortcutType type;
can_remove = FALSE;
if (gtk_tree_selection_get_selected (selection, &model, &iter))
{
gtk_tree_model_get (model, &iter, DETAIL_KEYENTRY_COLUMN, &item, -1);
if (item && item->command != NULL && item->editable)
gtk_tree_model_get (model, &iter,
DETAIL_KEYENTRY_COLUMN, &item,
DETAIL_TYPE_COLUMN, &type,
-1);
if (type == SHORTCUT_TYPE_KEY_ENTRY &&
item && item->command != NULL && item->editable)
can_remove = TRUE;
}
gtk_widget_set_sensitive (button, can_remove);
}
static void
fill_xkb_options_shortcuts (GtkTreeModel *model)
{
GList *l;
GtkTreeIter iter;
for (l = cc_keyboard_option_get_all (); l; l = l->next)
{
CcKeyboardOption *option = l->data;
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
DETAIL_DESCRIPTION_COLUMN, cc_keyboard_option_get_description (option),
DETAIL_KEYENTRY_COLUMN, option,
DETAIL_TYPE_COLUMN, SHORTCUT_TYPE_XKB_OPTION,
-1);
}
}
static void
section_selection_changed (GtkTreeSelection *selection, gpointer data)
{
......@@ -769,7 +834,6 @@ section_selection_changed (GtkTreeSelection *selection, gpointer data)
g_free (description);
return;
}
g_free (description);
gtk_widget_set_sensitive (WID (builder, "remove-toolbutton"), FALSE);
......@@ -787,8 +851,14 @@ section_selection_changed (GtkTreeSelection *selection, gpointer data)
gtk_list_store_set (GTK_LIST_STORE (shortcut_model), &new_row,
DETAIL_DESCRIPTION_COLUMN, item->description,
DETAIL_KEYENTRY_COLUMN, item,
DETAIL_TYPE_COLUMN, SHORTCUT_TYPE_KEY_ENTRY,
-1);
}