keybindings: handle multiple keycodes for keysym

A single keysym can resolve to multiple keycodes. Instead of only using
the first one and ignoring the others, we store all codes in
MetaResolvedKeyCombo and then handle all of them in keybinding
resolution. If we already have bound a keycode for a keybinding with a
specific keysym then this can get overwritten by a new keybinding with a
different keysym that resolves to the same keycode. Now that we resolve
and bind all keycodes for a keysym this might happen more often; in that
case warn but still overwrite, but only for the first keycode for each
keysym. If a secondary (i.e. all non-first keycodes) is already indexed
we just ignore that; this should resemble the old behavior  where we
only took the first keycode for any keysym as close as possible.

https://bugzilla.gnome.org/show_bug.cgi?id=781223
This commit is contained in:
Christian Kellner 2017-04-25 13:17:02 +02:00
parent dd82f4afcd
commit 68dacb531b
2 changed files with 233 additions and 176 deletions

View File

@ -44,7 +44,8 @@ struct _MetaKeyHandler
};
typedef struct _MetaResolvedKeyCombo {
xkb_keycode_t keycode;
xkb_keycode_t *keycodes;
int len;
xkb_mod_mask_t mask;
} MetaResolvedKeyCombo;
@ -102,7 +103,7 @@ typedef struct
MetaKeyCombo overlay_key_combo;
MetaResolvedKeyCombo overlay_resolved_key_combo;
gboolean overlay_key_only_pressed;
MetaResolvedKeyCombo *iso_next_group_combos;
MetaResolvedKeyCombo iso_next_group_combo[2];
int n_iso_next_group_combos;
xkb_level_index_t keymap_num_levels;

View File

@ -64,16 +64,63 @@ static gboolean add_builtin_keybinding (MetaDisplay *display,
MetaKeyHandlerFunc handler,
int handler_arg);
static void
resolved_key_combo_reset (MetaResolvedKeyCombo *resolved_combo)
{
g_free (resolved_combo->keycodes);
resolved_combo->len = 0;
resolved_combo->keycodes = NULL;
}
static void
resolved_key_combo_copy (MetaResolvedKeyCombo *from,
MetaResolvedKeyCombo *to)
{
to->len = from->len;
to->keycodes = g_memdup (from->keycodes,
from->len * sizeof (xkb_keycode_t));
}
static gboolean
resolved_key_combo_has_keycode (MetaResolvedKeyCombo *resolved_combo,
int keycode)
{
int i;
for (i = 0; i < resolved_combo->len; i++)
if ((int) resolved_combo->keycodes[i] == keycode)
return TRUE;
return FALSE;
}
static gboolean
resolved_key_combo_intersect (MetaResolvedKeyCombo *a,
MetaResolvedKeyCombo *b)
{
int i;
for (i = 0; i < a->len; i++)
if (resolved_key_combo_has_keycode (b, a->keycodes[i]))
return TRUE;
return FALSE;
}
static void
meta_key_binding_free (MetaKeyBinding *binding)
{
resolved_key_combo_reset (&binding->resolved_combo);
g_slice_free (MetaKeyBinding, binding);
}
static MetaKeyBinding *
meta_key_binding_copy (MetaKeyBinding *binding)
{
return g_slice_dup (MetaKeyBinding, binding);
MetaKeyBinding *clone = g_slice_dup (MetaKeyBinding, binding);
resolved_key_combo_copy (&binding->resolved_combo,
&clone->resolved_combo);
return clone;
}
G_DEFINE_BOXED_TYPE(MetaKeyBinding,
@ -163,7 +210,8 @@ meta_key_grab_free (MetaKeyGrab *grab)
}
static guint32
key_combo_key (MetaResolvedKeyCombo *resolved_combo)
key_combo_key (MetaResolvedKeyCombo *resolved_combo,
int i)
{
/* On X, keycodes are only 8 bits while libxkbcommon supports 32 bit
keycodes, but since we're using the same XKB keymaps that X uses,
@ -173,7 +221,7 @@ key_combo_key (MetaResolvedKeyCombo *resolved_combo)
can use a 32 bit integer to safely concatenate both keycode and
mask and thus making it easy to use them as an index in a
GHashTable. */
guint32 key = resolved_combo->keycode & 0xffff;
guint32 key = resolved_combo->keycodes[i] & 0xffff;
return (key << 16) | (resolved_combo->mask & 0xffff);
}
@ -274,21 +322,34 @@ get_keycodes_for_keysym_iter (struct xkb_keymap *keymap,
xkb_level_index_t level = search_data->level;
if (is_keycode_for_keysym (keymap, layout, level, keycode, keysym))
{
guint i;
gboolean missing = TRUE;
/* duplicate keycode detection */
for (i = 0; i < keycodes->len; i++)
if (g_array_index (keycodes, xkb_keysym_t, i) == keycode)
{
missing = FALSE;
break;
}
if (missing)
g_array_append_val (keycodes, keycode);
}
}
/* Original code from gdk_x11_keymap_get_entries_for_keyval() in
* gdkkeys-x11.c */
static int
static void
get_keycodes_for_keysym (MetaKeyBindingManager *keys,
int keysym,
int **keycodes)
MetaResolvedKeyCombo *resolved_combo)
{
GArray *retval;
int n_keycodes;
int keycode;
retval = g_array_new (FALSE, FALSE, sizeof (int));
retval = g_array_new (FALSE, FALSE, sizeof (xkb_keysym_t));
/* Special-case: Fake mutter keysym */
if (keysym == META_KEY_ABOVE_TAB)
@ -313,28 +374,8 @@ get_keycodes_for_keysym (MetaKeyBindingManager *keys,
}
out:
n_keycodes = retval->len;
*keycodes = (int*) g_array_free (retval, n_keycodes == 0 ? TRUE : FALSE);
return n_keycodes;
}
static guint
get_first_keycode_for_keysym (MetaKeyBindingManager *keys,
guint keysym)
{
int *keycodes;
int n_keycodes;
int keycode;
n_keycodes = get_keycodes_for_keysym (keys, keysym, &keycodes);
if (n_keycodes > 0)
keycode = keycodes[0];
else
keycode = 0;
g_free (keycodes);
return keycode;
resolved_combo->len = retval->len;
resolved_combo->keycodes = (xkb_keycode_t *) g_array_free (retval, retval->len == 0 ? TRUE : FALSE);
}
static void
@ -367,20 +408,23 @@ static void
reload_iso_next_group_combos (MetaKeyBindingManager *keys)
{
const char *iso_next_group_option;
MetaResolvedKeyCombo *combos;
int *keycodes;
int n_keycodes;
int n_combos;
int i;
g_clear_pointer (&keys->iso_next_group_combos, g_free);
for (i = 0; i < keys->n_iso_next_group_combos; i++)
resolved_key_combo_reset (&keys->iso_next_group_combo[i]);
keys->n_iso_next_group_combos = 0;
iso_next_group_option = meta_prefs_get_iso_next_group_option ();
if (iso_next_group_option == NULL)
return;
n_keycodes = get_keycodes_for_keysym (keys, XKB_KEY_ISO_Next_Group, &keycodes);
get_keycodes_for_keysym (keys, XKB_KEY_ISO_Next_Group, keys->iso_next_group_combo);
if (keys->iso_next_group_combo[0].len == 0)
return;
keys->n_iso_next_group_combos = 1;
if (g_str_equal (iso_next_group_option, "toggle") ||
g_str_equal (iso_next_group_option, "lalt_toggle") ||
@ -394,94 +438,53 @@ reload_iso_next_group_combos (MetaKeyBindingManager *keys)
g_str_equal (iso_next_group_option, "menu_toggle") ||
g_str_equal (iso_next_group_option, "caps_toggle"))
{
n_combos = n_keycodes;
combos = g_new (MetaResolvedKeyCombo, n_combos);
for (i = 0; i < n_keycodes; ++i)
{
combos[i].keycode = keycodes[i];
combos[i].mask = 0;
}
keys->iso_next_group_combo[0].mask = 0;
}
else if (g_str_equal (iso_next_group_option, "shift_caps_toggle") ||
g_str_equal (iso_next_group_option, "shifts_toggle"))
{
n_combos = n_keycodes;
combos = g_new (MetaResolvedKeyCombo, n_combos);
for (i = 0; i < n_keycodes; ++i)
{
combos[i].keycode = keycodes[i];
combos[i].mask = ShiftMask;
}
keys->iso_next_group_combo[0].mask = ShiftMask;
}
else if (g_str_equal (iso_next_group_option, "alt_caps_toggle") ||
g_str_equal (iso_next_group_option, "alt_space_toggle"))
{
n_combos = n_keycodes;
combos = g_new (MetaResolvedKeyCombo, n_combos);
for (i = 0; i < n_keycodes; ++i)
{
combos[i].keycode = keycodes[i];
combos[i].mask = Mod1Mask;
}
keys->iso_next_group_combo[0].mask = Mod1Mask;
}
else if (g_str_equal (iso_next_group_option, "ctrl_shift_toggle") ||
g_str_equal (iso_next_group_option, "lctrl_lshift_toggle") ||
g_str_equal (iso_next_group_option, "rctrl_rshift_toggle"))
{
n_combos = n_keycodes * 2;
combos = g_new (MetaResolvedKeyCombo, n_combos);
resolved_key_combo_copy (&keys->iso_next_group_combo[0],
&keys->iso_next_group_combo[1]);
for (i = 0; i < n_keycodes; ++i)
{
combos[i].keycode = keycodes[i];
combos[i].mask = ShiftMask;
combos[i + n_keycodes].keycode = keycodes[i];
combos[i + n_keycodes].mask = ControlMask;
}
keys->iso_next_group_combo[0].mask = ShiftMask;
keys->iso_next_group_combo[1].mask = ControlMask;
keys->n_iso_next_group_combos = 2;
}
else if (g_str_equal (iso_next_group_option, "ctrl_alt_toggle"))
{
n_combos = n_keycodes * 2;
combos = g_new (MetaResolvedKeyCombo, n_combos);
resolved_key_combo_copy (&keys->iso_next_group_combo[0],
&keys->iso_next_group_combo[1]);
for (i = 0; i < n_keycodes; ++i)
{
combos[i].keycode = keycodes[i];
combos[i].mask = Mod1Mask;
combos[i + n_keycodes].keycode = keycodes[i];
combos[i + n_keycodes].mask = ControlMask;
}
keys->iso_next_group_combo[0].mask = Mod1Mask;
keys->iso_next_group_combo[1].mask = ControlMask;
keys->n_iso_next_group_combos = 2;
}
else if (g_str_equal (iso_next_group_option, "alt_shift_toggle") ||
g_str_equal (iso_next_group_option, "lalt_lshift_toggle"))
{
n_combos = n_keycodes * 2;
combos = g_new (MetaResolvedKeyCombo, n_combos);
resolved_key_combo_copy (&keys->iso_next_group_combo[0],
&keys->iso_next_group_combo[1]);
for (i = 0; i < n_keycodes; ++i)
{
combos[i].keycode = keycodes[i];
combos[i].mask = Mod1Mask;
combos[i + n_keycodes].keycode = keycodes[i];
combos[i + n_keycodes].mask = ShiftMask;
}
keys->iso_next_group_combo[0].mask = Mod1Mask;
keys->iso_next_group_combo[1].mask = ShiftMask;
keys->n_iso_next_group_combos = 2;
}
else
{
n_combos = 0;
combos = NULL;
resolved_key_combo_reset (keys->iso_next_group_combo);
keys->n_iso_next_group_combos = 0;
}
g_free (keycodes);
keys->n_iso_next_group_combos = n_combos;
keys->iso_next_group_combos = combos;
}
static void
@ -517,11 +520,35 @@ static void
index_binding (MetaKeyBindingManager *keys,
MetaKeyBinding *binding)
{
int i;
for (i = 0; i < binding->resolved_combo.len; i++)
{
MetaKeyBinding *existing;
guint32 index_key;
index_key = key_combo_key (&binding->resolved_combo);
index_key = key_combo_key (&binding->resolved_combo, i);
existing = g_hash_table_lookup (keys->key_bindings_index,
GINT_TO_POINTER (index_key));
if (existing != NULL)
{
/* Overwrite already indexed keycodes only for the first
* keycode, i.e. we give those primary keycodes precedence
* over non-first ones. */
if (i > 0)
continue;
meta_warning ("Overwriting existing binding of keysym %x"
" with keysym %x (keycode %x).\n",
binding->combo.keysym,
existing->combo.keysym,
binding->resolved_combo.keycodes[i]);
}
g_hash_table_replace (keys->key_bindings_index,
GINT_TO_POINTER (index_key), binding);
}
}
static void
@ -529,10 +556,19 @@ resolve_key_combo (MetaKeyBindingManager *keys,
MetaKeyCombo *combo,
MetaResolvedKeyCombo *resolved_combo)
{
resolved_key_combo_reset (resolved_combo);
if (combo->keysym != 0)
resolved_combo->keycode = get_first_keycode_for_keysym (keys, combo->keysym);
else
resolved_combo->keycode = combo->keycode;
{
get_keycodes_for_keysym (keys, combo->keysym, resolved_combo);
}
else if (combo->keycode != 0)
{
resolved_combo->keycodes = g_new0 (xkb_keycode_t, 1);
resolved_combo->keycodes[0] = combo->keycode;
resolved_combo->len = 1;
}
devirtualize_modifiers (keys, combo->modifiers, &resolved_combo->mask);
}
@ -691,9 +727,22 @@ static MetaKeyBinding *
get_keybinding (MetaKeyBindingManager *keys,
MetaResolvedKeyCombo *resolved_combo)
{
MetaKeyBinding *binding = NULL;
int i;
for (i = 0; i < resolved_combo->len; i++)
{
guint32 key;
key = key_combo_key (resolved_combo);
return g_hash_table_lookup (keys->key_bindings_index, GINT_TO_POINTER (key));
key = key_combo_key (resolved_combo, i);
binding = g_hash_table_lookup (keys->key_bindings_index,
GINT_TO_POINTER (key));
if (binding != NULL)
break;
}
return binding;
}
static guint
@ -823,7 +872,8 @@ get_keybinding_action (MetaKeyBindingManager *keys,
* of mutter keybindings while holding a grab, the overlay-key-only-pressed
* tracking is left to the plugin here.
*/
if (resolved_combo->keycode == (unsigned int)keys->overlay_resolved_key_combo.keycode)
if (resolved_key_combo_intersect (resolved_combo,
&keys->overlay_resolved_key_combo))
return META_KEYBINDING_ACTION_OVERLAY_KEY;
binding = get_keybinding (keys, resolved_combo);
@ -841,14 +891,11 @@ get_keybinding_action (MetaKeyBindingManager *keys,
}
}
static void
resolved_combo_from_event_params (MetaResolvedKeyCombo *resolved_combo,
MetaKeyBindingManager *keys,
unsigned int keycode,
static xkb_mod_mask_t
mask_from_event_params (MetaKeyBindingManager *keys,
unsigned long mask)
{
resolved_combo->keycode = keycode;
resolved_combo->mask = mask & 0xff & ~keys->ignored_modifier_mask;
return mask & 0xff & ~keys->ignored_modifier_mask;
}
/**
@ -871,8 +918,10 @@ meta_display_get_keybinding_action (MetaDisplay *display,
unsigned long mask)
{
MetaKeyBindingManager *keys = &display->key_binding_manager;
MetaResolvedKeyCombo resolved_combo;
resolved_combo_from_event_params (&resolved_combo, keys, keycode, mask);
xkb_keycode_t code = (xkb_keycode_t) keycode;
MetaResolvedKeyCombo resolved_combo = { &code, 1 };
resolved_combo.mask = mask_from_event_params (keys, mask);
return get_keybinding_action (keys, &resolved_combo);
}
@ -1165,30 +1214,36 @@ meta_change_keygrab (MetaKeyBindingManager *keys,
MetaBackendX11 *backend = META_BACKEND_X11 (meta_get_backend ());
Display *xdisplay = meta_backend_x11_get_xdisplay (backend);
GArray *mods;
int i;
/* Grab keycode/modmask, together with
* all combinations of ignored modifiers.
* X provides no better way to do this.
*/
mods = calc_grab_modifiers (keys, resolved_combo->mask);
for (i = 0; i < resolved_combo->len; i++)
{
xkb_keycode_t keycode = resolved_combo->keycodes[i];
meta_topic (META_DEBUG_KEYBINDINGS,
"%s keybinding keycode %d mask 0x%x on 0x%lx\n",
grab ? "Grabbing" : "Ungrabbing",
resolved_combo->keycode, resolved_combo->mask, xwindow);
mods = calc_grab_modifiers (keys, resolved_combo->mask);
keycode, resolved_combo->mask, xwindow);
if (grab)
XIGrabKeycode (xdisplay,
META_VIRTUAL_CORE_KEYBOARD_ID,
resolved_combo->keycode, xwindow,
keycode, xwindow,
XIGrabModeSync, XIGrabModeAsync,
False, &mask, mods->len, (XIGrabModifiers *)mods->data);
else
XIUngrabKeycode (xdisplay,
META_VIRTUAL_CORE_KEYBOARD_ID,
resolved_combo->keycode, xwindow,
keycode, xwindow,
mods->len, (XIGrabModifiers *)mods->data);
}
g_array_free (mods, TRUE);
}
@ -1213,7 +1268,7 @@ change_keygrab_foreach (gpointer key,
if (data->only_per_window != binding_is_per_window)
return;
if (binding->resolved_combo.keycode == 0)
if (binding->resolved_combo.len == 0)
return;
meta_change_keygrab (data->keys, data->xwindow, data->grab, &binding->resolved_combo);
@ -1241,21 +1296,13 @@ meta_screen_change_keygrabs (MetaScreen *screen,
{
MetaDisplay *display = screen->display;
MetaKeyBindingManager *keys = &display->key_binding_manager;
int i;
if (keys->overlay_resolved_key_combo.keycode != 0)
if (keys->overlay_resolved_key_combo.len != 0)
meta_change_keygrab (keys, screen->xroot, grab, &keys->overlay_resolved_key_combo);
if (keys->iso_next_group_combos)
{
int i = 0;
while (i < keys->n_iso_next_group_combos)
{
if (keys->iso_next_group_combos[i].keycode != 0)
meta_change_keygrab (keys, screen->xroot, grab, &keys->iso_next_group_combos[i]);
++i;
}
}
for (i = 0; i < keys->n_iso_next_group_combos; i++)
meta_change_keygrab (keys, screen->xroot, grab, &keys->iso_next_group_combo[i]);
change_binding_keygrabs (keys, screen->xroot, FALSE, grab);
}
@ -1367,7 +1414,7 @@ meta_display_grab_accelerator (MetaDisplay *display,
MetaKeyBinding *binding;
MetaKeyGrab *grab;
MetaKeyCombo combo = { 0 };
MetaResolvedKeyCombo resolved_combo = { 0 };
MetaResolvedKeyCombo resolved_combo = { NULL, 0 };
if (!meta_parse_accelerator (accelerator, &combo))
{
@ -1380,11 +1427,14 @@ meta_display_grab_accelerator (MetaDisplay *display,
resolve_key_combo (keys, &combo, &resolved_combo);
if (resolved_combo.keycode == 0)
if (resolved_combo.len == 0)
return META_KEYBINDING_ACTION_NONE;
if (get_keybinding (keys, &resolved_combo))
{
resolved_key_combo_reset (&resolved_combo);
return META_KEYBINDING_ACTION_NONE;
}
meta_change_keygrab (keys, display->screen->xroot, TRUE, &resolved_combo);
@ -1415,7 +1465,7 @@ meta_display_ungrab_accelerator (MetaDisplay *display,
MetaKeyBinding *binding;
MetaKeyGrab *grab;
char *key;
MetaResolvedKeyCombo resolved_combo;
MetaResolvedKeyCombo resolved_combo = { NULL, 0 };
g_return_val_if_fail (action != META_KEYBINDING_ACTION_NONE, FALSE);
@ -1428,18 +1478,22 @@ meta_display_ungrab_accelerator (MetaDisplay *display,
binding = get_keybinding (keys, &resolved_combo);
if (binding)
{
guint32 index_key;
int i;
meta_change_keygrab (keys, display->screen->xroot, FALSE, &binding->resolved_combo);
index_key = key_combo_key (&binding->resolved_combo);
for (i = 0; i < binding->resolved_combo.len; i++)
{
guint32 index_key = key_combo_key (&binding->resolved_combo, i);
g_hash_table_remove (keys->key_bindings_index, GINT_TO_POINTER (index_key));
}
g_hash_table_remove (keys->key_bindings, binding);
}
g_hash_table_remove (external_grabs, key);
g_free (key);
resolved_key_combo_reset (&resolved_combo);
return TRUE;
}
@ -1645,16 +1699,15 @@ process_event (MetaDisplay *display,
ClutterKeyEvent *event)
{
MetaKeyBindingManager *keys = &display->key_binding_manager;
MetaResolvedKeyCombo resolved_combo;
xkb_keycode_t keycode = (xkb_keycode_t) event->hardware_keycode;
MetaResolvedKeyCombo resolved_combo = { &keycode, 1 };
MetaKeyBinding *binding;
/* we used to have release-based bindings but no longer. */
if (event->type == CLUTTER_KEY_RELEASE)
return FALSE;
resolved_combo_from_event_params (&resolved_combo, keys,
event->hardware_keycode,
event->modifier_state);
resolved_combo.mask = mask_from_event_params (keys, event->modifier_state);
binding = get_keybinding (keys, &resolved_combo);
@ -1708,7 +1761,8 @@ process_overlay_key (MetaDisplay *display,
if (keys->overlay_key_only_pressed)
{
if (event->hardware_keycode != (int)keys->overlay_resolved_key_combo.keycode)
if (! resolved_key_combo_has_keycode (&keys->overlay_resolved_key_combo,
event->hardware_keycode))
{
keys->overlay_key_only_pressed = FALSE;
@ -1790,7 +1844,8 @@ process_overlay_key (MetaDisplay *display,
return TRUE;
}
else if (event->type == CLUTTER_KEY_PRESS &&
event->hardware_keycode == (int)keys->overlay_resolved_key_combo.keycode)
resolved_key_combo_has_keycode (&keys->overlay_resolved_key_combo,
event->hardware_keycode))
{
keys->overlay_key_only_pressed = TRUE;
/* We keep the keyboard frozen - this allows us to use ReplayKeyboard
@ -1813,22 +1868,22 @@ process_iso_next_group (MetaDisplay *display,
{
MetaKeyBindingManager *keys = &display->key_binding_manager;
gboolean activate;
MetaResolvedKeyCombo resolved_combo;
int i;
xkb_keycode_t keycode = (xkb_keycode_t) event->hardware_keycode;
xkb_mod_mask_t mask;
int i, j;
if (event->type == CLUTTER_KEY_RELEASE)
return FALSE;
activate = FALSE;
resolved_combo_from_event_params (&resolved_combo, keys,
event->hardware_keycode,
event->modifier_state);
mask = mask_from_event_params (keys, event->modifier_state);
for (i = 0; i < keys->n_iso_next_group_combos; ++i)
{
if (resolved_combo.keycode == keys->iso_next_group_combos[i].keycode &&
resolved_combo.mask == keys->iso_next_group_combos[i].mask)
for (j = 0; j < keys->iso_next_group_combo[i].len; ++j)
{
if (keycode == keys->iso_next_group_combo[i].keycodes[j] &&
mask == keys->iso_next_group_combo[i].mask)
{
/* If the signal handler returns TRUE the keyboard will
remain frozen. It's the signal handler's responsibility
@ -1839,6 +1894,7 @@ process_iso_next_group (MetaDisplay *display,
break;
}
}
}
return activate;
}