Commit 971c7ac6 authored by Alberts Muktupāvels's avatar Alberts Muktupāvels
Browse files

common: add support for modifiers-only keybindings

More precisely - support for ISO_Next_Group that will be used to
switch input sources.
parent 66195244
......@@ -24,11 +24,21 @@
#include "gf-keybindings.h"
#define DESKTOP_INPUT_SOURCES_SCHEMA "org.gnome.desktop.input-sources"
#define KEY_XKB_OPTIONS "xkb-options"
struct _GfKeybindings
{
GObject parent;
GHashTable *keybindings;
GHashTable *iso_next_group_keybindings;
gboolean iso_next_group;
GSettings *settings;
gchar *iso_next_group_option;
Display *xdisplay;
Window xwindow;
......@@ -59,12 +69,24 @@ typedef struct
enum
{
SIGNAL_ACCELERATOR_ACTIVATED,
SIGNAL_MODIFIERS_ACCELERATOR_ACTIVATED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
enum
{
PROP_0,
PROP_ISO_NEXT_GROUP,
LAST_PROP
};
static GParamSpec *properties[LAST_PROP] = { NULL };
G_DEFINE_TYPE (GfKeybindings, gf_keybindings, G_TYPE_OBJECT)
static gboolean
......@@ -141,6 +163,9 @@ change_keygrab (GfKeybindings *keybindings,
keycode = keybinding->keycode;
mask = keybinding->mask;
if (keycode == 0)
return;
while (ignored_mask <= keybindings->ignored_mask)
{
if (ignored_mask & ~(keybindings->ignored_mask))
......@@ -224,12 +249,183 @@ get_keybinding (GfKeybindings *keybindings,
return keybinding;
}
static gint
get_keycodes_for_keysym (GfKeybindings *keybindings,
gint keysym,
gint **keycodes)
{
GArray *retval;
gint min_keycodes;
gint max_keycodes;
gint keysyms_per_keycode;
KeySym *keymap;
gint n_keycodes;
gint keycode;
retval = g_array_new (FALSE, FALSE, sizeof (gint));
XDisplayKeycodes (keybindings->xdisplay, &min_keycodes, &max_keycodes);
keymap = XGetKeyboardMapping (keybindings->xdisplay, min_keycodes,
max_keycodes - min_keycodes + 1,
&keysyms_per_keycode);
keycode = min_keycodes;
while (keycode <= max_keycodes)
{
const KeySym *syms;
gint i;
syms = keymap + (keycode - min_keycodes) * keysyms_per_keycode;
i = 0;
while (i < keysyms_per_keycode)
{
if (syms[i] == (uint) keysym)
g_array_append_val (retval, keycode);
++i;
}
++keycode;
}
XFree (keymap);
n_keycodes = retval->len;
*keycodes = (gint *) g_array_free (retval, n_keycodes == 0 ? TRUE : FALSE);
return n_keycodes;
}
static void
reload_iso_next_group_keybindings (GfKeybindings *keybindings)
{
gint *keycodes;
gint n_keycodes;
gint i;
g_hash_table_remove_all (keybindings->iso_next_group_keybindings);
if (keybindings->iso_next_group_option == NULL)
return;
n_keycodes = get_keycodes_for_keysym (keybindings, XK_ISO_Next_Group,
&keycodes);
if (g_strcmp0 (keybindings->iso_next_group_option, "toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "lalt_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "lwin_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "rwin_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "lshift_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "rshift_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "lctrl_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "rctrl_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "sclk_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "menu_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "caps_toggle") == 0)
{
for (i = 0; i < n_keycodes; i++)
{
Keybinding *keybinding;
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], 0, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i), keybinding);
}
}
else if (g_strcmp0 (keybindings->iso_next_group_option, "shift_caps_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "shifts_toggle") == 0)
{
for (i = 0; i < n_keycodes; i++)
{
Keybinding *keybinding;
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ShiftMask, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i), keybinding);
}
}
else if (g_strcmp0 (keybindings->iso_next_group_option, "alt_caps_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "alt_space_toggle") == 0)
{
for (i = 0; i < n_keycodes; i++)
{
Keybinding *keybinding;
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], Mod1Mask, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i), keybinding);
}
}
else if (g_strcmp0 (keybindings->iso_next_group_option, "ctrl_shift_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "lctrl_lshift_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "rctrl_rshift_toggle") == 0)
{
for (i = 0; i < n_keycodes; i++)
{
Keybinding *keybinding;
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ShiftMask, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i), keybinding);
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ControlMask, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i + n_keycodes), keybinding);
}
}
else if (g_strcmp0 (keybindings->iso_next_group_option, "ctrl_alt_toggle") == 0)
{
for (i = 0; i < n_keycodes; i++)
{
Keybinding *keybinding;
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], Mod1Mask, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i), keybinding);
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ControlMask, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i + n_keycodes), keybinding);
}
}
else if (g_strcmp0 (keybindings->iso_next_group_option, "alt_shift_toggle") == 0 ||
g_strcmp0 (keybindings->iso_next_group_option, "lalt_lshift_toggle") == 0)
{
for (i = 0; i < n_keycodes; i++)
{
Keybinding *keybinding;
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], Mod1Mask, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i), keybinding);
keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ShiftMask, 0);
g_hash_table_insert (keybindings->iso_next_group_keybindings,
GINT_TO_POINTER (i + n_keycodes), keybinding);
}
}
g_free (keycodes);
}
static void
ungrab_keybindings (GfKeybindings *keybindings)
{
GHashTableIter iter;
gpointer value;
g_hash_table_iter_init (&iter, keybindings->iso_next_group_keybindings);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
Keybinding *keybinding;
keybinding = (Keybinding *) value;
change_keygrab (keybindings, FALSE, keybinding);
}
g_hash_table_iter_init (&iter, keybindings->keybindings);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
......@@ -278,6 +474,8 @@ reload_keybindings (GfKeybindings *keybindings)
GHashTableIter iter;
gpointer value;
reload_iso_next_group_keybindings (keybindings);
g_hash_table_iter_init (&iter, keybindings->keybindings);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
......@@ -319,6 +517,16 @@ regrab_keybindings (GfKeybindings *keybindings)
GHashTableIter iter;
gpointer value;
g_hash_table_iter_init (&iter, keybindings->iso_next_group_keybindings);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
Keybinding *keybinding;
keybinding = (Keybinding *) value;
change_keygrab (keybindings, TRUE, keybinding);
}
g_hash_table_iter_init (&iter, keybindings->keybindings);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
......@@ -330,6 +538,90 @@ regrab_keybindings (GfKeybindings *keybindings)
}
}
static gboolean
process_iso_next_group (GfKeybindings *keybindings,
XEvent *event)
{
gboolean processed;
guint state;
GList *values;
GList *l;
if (event->type == KeyRelease)
return FALSE;
processed = FALSE;
state = event->xkey.state & 0xff & ~(keybindings->ignored_mask);
values = g_hash_table_get_values (keybindings->iso_next_group_keybindings);
for (l = values; l; l = l->next)
{
Keybinding *keybinding;
keybinding = (Keybinding *) l->data;
if (keybinding->keycode == event->xkey.keycode &&
keybinding->mask == state)
{
gboolean freeze;
g_signal_emit (keybindings,
signals[SIGNAL_MODIFIERS_ACCELERATOR_ACTIVATED],
0, &freeze);
if (!freeze)
XUngrabKeyboard (keybindings->xdisplay, event->xkey.time);
processed = TRUE;
break;
}
}
g_list_free (values);
return processed;
}
static gboolean
process_event (GfKeybindings *keybindings,
XEvent *event)
{
gboolean processed;
guint state;
GList *values;
GList *l;
if (event->type == KeyRelease)
return FALSE;
processed = FALSE;
state = event->xkey.state & 0xff & ~(keybindings->ignored_mask);
values = g_hash_table_get_values (keybindings->keybindings);
for (l = values; l; l = l->next)
{
Keybinding *keybinding;
keybinding = (Keybinding *) l->data;
if (keybinding->keycode == event->xkey.keycode &&
keybinding->mask == state)
{
XUngrabKeyboard (keybindings->xdisplay, event->xkey.time);
g_signal_emit (keybindings, signals[SIGNAL_ACCELERATOR_ACTIVATED],
0, keybinding->action);
processed = TRUE;
break;
}
}
g_list_free (values);
return processed;
}
static GdkFilterReturn
filter_func (GdkXEvent *xevent,
GdkEvent *event,
......@@ -337,8 +629,6 @@ filter_func (GdkXEvent *xevent,
{
GfKeybindings *keybindings;
XEvent *ev;
GList *values;
GList *l;
keybindings = GF_KEYBINDINGS (user_data);
ev = (XEvent *) xevent;
......@@ -363,43 +653,81 @@ filter_func (GdkXEvent *xevent,
}
}
if (ev->type != KeyPress && ev->type != KeyRelease)
return GDK_FILTER_CONTINUE;
if (process_iso_next_group (keybindings, ev))
return GDK_FILTER_REMOVE;
XAllowEvents (keybindings->xdisplay, AsyncKeyboard, ev->xkey.time);
if (ev->type != KeyPress)
return GDK_FILTER_CONTINUE;
if (process_event (keybindings, ev))
return GDK_FILTER_REMOVE;
values = g_hash_table_get_values (keybindings->keybindings);
return GDK_FILTER_CONTINUE;
}
for (l = values; l; l = l->next)
{
Keybinding *keybinding;
guint state;
static guint
get_next_action (void)
{
static guint action;
keybinding = (Keybinding *) l->data;
state = ev->xkey.state & 0xff & ~(keybindings->ignored_mask);
return ++action;
}
if (keybinding->keycode == ev->xkey.keycode &&
keybinding->mask == state)
{
XUngrabKeyboard (keybindings->xdisplay, ev->xkey.time);
g_signal_emit (keybindings, signals[SIGNAL_ACCELERATOR_ACTIVATED],
0, keybinding->action);
static void
xkb_options_changed_cb (GSettings *settings,
gchar *key,
gpointer user_data)
{
GfKeybindings *keybindings;
gchar **xkb_options;
gchar **p;
gchar *option;
keybindings = GF_KEYBINDINGS (user_data);
xkb_options = g_settings_get_strv (settings, KEY_XKB_OPTIONS);
option = NULL;
for (p = xkb_options; p && *p; ++p)
{
if (g_str_has_prefix (*p, "grp:"))
{
option = (*p + 4);
break;
}
}
g_list_free (values);
if (g_strcmp0 (option, keybindings->iso_next_group_option) == 0)
{
g_strfreev (xkb_options);
return;
}
return GDK_FILTER_CONTINUE;
g_free (keybindings->iso_next_group_option);
keybindings->iso_next_group_option = g_strdup (option);
g_strfreev (xkb_options);
reload_iso_next_group_keybindings (keybindings);
}
static guint
get_next_action (void)
static void
gf_keybindings_constructed (GObject *object)
{
static guint action;
GfKeybindings *keybindings;
return ++action;
keybindings = GF_KEYBINDINGS (object);
G_OBJECT_CLASS (gf_keybindings_parent_class)->constructed (object);
if (keybindings->iso_next_group)
{
keybindings->settings = g_settings_new (DESKTOP_INPUT_SOURCES_SCHEMA);
g_signal_connect (keybindings->settings, "changed::" KEY_XKB_OPTIONS,
G_CALLBACK (xkb_options_changed_cb), keybindings);
xkb_options_changed_cb (keybindings->settings, NULL, keybindings);
}
}
static void
......@@ -415,6 +743,14 @@ gf_keybindings_dispose (GObject *object)
keybindings->keybindings = NULL;
}
if (keybindings->iso_next_group_keybindings != NULL)
{
g_hash_table_destroy (keybindings->iso_next_group_keybindings);
keybindings->iso_next_group_keybindings = NULL;
}
g_clear_object (&keybindings->settings);
G_OBJECT_CLASS (gf_keybindings_parent_class)->dispose (object);
}
......@@ -427,9 +763,33 @@ gf_keybindings_finalize (GObject *object)
gdk_window_remove_filter (NULL, filter_func, keybindings);
g_free (keybindings->iso_next_group_option);
G_OBJECT_CLASS (gf_keybindings_parent_class)->finalize (object);
}
static void
gf_keybindings_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GfKeybindings *keybindings;
keybindings = GF_KEYBINDINGS (object);
switch (prop_id)
{
case PROP_ISO_NEXT_GROUP:
keybindings->iso_next_group = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gf_keybindings_class_init (GfKeybindingsClass *keybindings_class)
{
......@@ -437,21 +797,43 @@ gf_keybindings_class_init (GfKeybindingsClass *keybindings_class)
object_class = G_OBJECT_CLASS (keybindings_class);
object_class->constructed = gf_keybindings_constructed;
object_class->dispose = gf_keybindings_dispose;
object_class->finalize = gf_keybindings_finalize;
object_class->set_property = gf_keybindings_set_property;
/**
* GfKeybindings::accelerator-activated:
* @keybindings: the object on which the signal is emitted
* @action: keybinding action from gf_keybindings_grab
*
* The ::accelerator-activated signal is emitted each time when keybinding
* is activated by user.
* The ::accelerator-activated signal will be emitted when a keybinding is
* activated.
*/
signals[SIGNAL_ACCELERATOR_ACTIVATED] =
g_signal_new ("accelerator-activated",
G_TYPE_FROM_CLASS (keybindings_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
/**
* GfKeybindings::modifiers-accelerator-activated:
* @keybindings: the object on which the signal is emitted
*
* The ::modifiers-accelerator-activated signal will be emitted when a
* special modifiers-only keybinding is activated.
*/
signals[SIGNAL_MODIFIERS_ACCELERATOR_ACTIVATED] =
g_signal_new ("modifiers-accelerator-activated",
G_TYPE_FROM_CLASS (keybindings_class), G_SIGNAL_RUN_LAST,
0, g_signal_accumulator_first_wins, NULL, NULL,
G_TYPE_BOOLEAN, 0);
properties[PROP_ISO_NEXT_GROUP] =
g_param_spec_boolean ("iso-next-group", "iso-next-group", "iso-next-group",
FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, LAST_PROP, properties);
}
static void
......@@ -464,6 +846,8 @@ gf_keybindings_init (GfKeybindings *keybindings)
keybindings->keybindings = g_hash_table_new_full (NULL, NULL, NULL,
keybinding_free);
keybindings->iso_next_group_keybindings = g_hash_table_new_full (NULL, NULL, NULL,
keybinding_free);
display = gdk_display_get_default ();
xkb_major = XkbMajorVersion;
......@@ -502,9 +886,11 @@ gf_keybindings_init (GfKeybindings *keybindings)
* Returns: (transfer full): a newly created #GfKeybindings.
*/
GfKeybindings *
gf_keybindings_new (void)
gf_keybindings_new (gboolean iso_next_group)
{
return g_object_new (GF_TYPE_KEYBINDINGS, NULL);
return g_object_new (GF_TYPE_KEYBINDINGS,
"iso-next-group", iso_next_group,
NULL);
}
/**
......
......@@ -25,7 +25,7 @@ G_BEGIN_DECLS
#define GF_TYPE_KEYBINDINGS gf_keybindings_get_type ()
G_DECLARE_FINAL_TYPE (GfKeybindings, gf_keybindings, GF, KEYBINDINGS, GObject)
GfKeybindings *gf_keybindings_new (void);
GfKeybindings *gf_keybindings_new (gboolean iso_next_group);
guint gf_keybindings_grab (GfKeybindings *keybindings,
const gchar *accelerator);
......
......@@ -265,7 +265,7 @@ static void
keybindings_init (GfInputSourceManager *manager)
{
manager->wm_keybindings = g_settings_new (DESKTOP_WM_KEYBINDINGS_SCHEMA);
manager->keybindings = gf_keybindings_new ();
manager->keybindings = gf_keybindings_new (TRUE);
g_signal_connect (manager->wm_keybindings,
"changed::" KEY_SWITCH_INPUT_SOURCE,
......
......@@ -465,7 +465,7 @@ flashback_shell_init (FlashbackShell *shell)
shell->grabbed_accelerators = g_hash_table_new_full (NULL, NULL, NULL, g_free);
shell->grabbers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);