gtk2/gdk/wayland/gdkkeys-wayland.c
Matthias Clasen 99c3928cec keymap: Cache key info
We currently calling gdk_display_map_keyval up to
once per key event per shortcut trigger, and that function
does an expensive loop over the entire keymap and
allocates an array. Avoid this by caching the entries
in a single array, and have a lookup table for finding
the entries for a keyval.

To do this, change the GdkKeymap.get_entries_for_keyval
signature, and change the ::keys-changed signal to be
RUN_FIRST, since we want to clear the cache in the class
handler before running signal handlers. These changes are
possible now, since keymaps are no longer public API.
2020-04-30 13:05:52 -04:00

609 lines
18 KiB
C

/* GDK - The GIMP Drawing Kit
* Copyright (C) 2000 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <sys/mman.h>
#include <fribidi.h>
#include "gdk.h"
#include "gdkwayland.h"
#include "gdkprivate-wayland.h"
#include "gdk-private.h"
#include "gdkkeysprivate.h"
#include <xkbcommon/xkbcommon.h>
typedef struct _GdkWaylandKeymap GdkWaylandKeymap;
typedef struct _GdkWaylandKeymapClass GdkWaylandKeymapClass;
struct _GdkWaylandKeymap
{
GdkKeymap parent_instance;
struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state;
PangoDirection *direction;
gboolean bidi;
};
struct _GdkWaylandKeymapClass
{
GdkKeymapClass parent_class;
};
#define GDK_TYPE_WAYLAND_KEYMAP (_gdk_wayland_keymap_get_type ())
#define GDK_WAYLAND_KEYMAP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_WAYLAND_KEYMAP, GdkWaylandKeymap))
#define GDK_IS_WAYLAND_KEYMAP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_KEYMAP))
GType _gdk_wayland_keymap_get_type (void);
G_DEFINE_TYPE (GdkWaylandKeymap, _gdk_wayland_keymap, GDK_TYPE_KEYMAP)
static void
gdk_wayland_keymap_finalize (GObject *object)
{
GdkWaylandKeymap *keymap = GDK_WAYLAND_KEYMAP (object);
xkb_keymap_unref (keymap->xkb_keymap);
xkb_state_unref (keymap->xkb_state);
g_free (keymap->direction);
G_OBJECT_CLASS (_gdk_wayland_keymap_parent_class)->finalize (object);
}
static PangoDirection
gdk_wayland_keymap_get_direction (GdkKeymap *keymap)
{
GdkWaylandKeymap *keymap_wayland = GDK_WAYLAND_KEYMAP (keymap);
gint i;
for (i = 0; i < xkb_keymap_num_layouts (keymap_wayland->xkb_keymap); i++)
{
if (xkb_state_layout_index_is_active (keymap_wayland->xkb_state, i, XKB_STATE_LAYOUT_EFFECTIVE))
return keymap_wayland->direction[i];
}
return PANGO_DIRECTION_NEUTRAL;
}
static gboolean
gdk_wayland_keymap_have_bidi_layouts (GdkKeymap *keymap)
{
GdkWaylandKeymap *keymap_wayland = GDK_WAYLAND_KEYMAP (keymap);
return keymap_wayland->bidi;
}
static gboolean
gdk_wayland_keymap_get_caps_lock_state (GdkKeymap *keymap)
{
return xkb_state_led_name_is_active (GDK_WAYLAND_KEYMAP (keymap)->xkb_state,
XKB_LED_NAME_CAPS);
}
static gboolean
gdk_wayland_keymap_get_num_lock_state (GdkKeymap *keymap)
{
return xkb_state_led_name_is_active (GDK_WAYLAND_KEYMAP (keymap)->xkb_state,
XKB_LED_NAME_NUM);
}
static gboolean
gdk_wayland_keymap_get_scroll_lock_state (GdkKeymap *keymap)
{
return xkb_state_led_name_is_active (GDK_WAYLAND_KEYMAP (keymap)->xkb_state,
XKB_LED_NAME_SCROLL);
}
static gboolean
gdk_wayland_keymap_get_entries_for_keyval (GdkKeymap *keymap,
guint keyval,
GArray *retval)
{
struct xkb_keymap *xkb_keymap = GDK_WAYLAND_KEYMAP (keymap)->xkb_keymap;
guint keycode;
xkb_keycode_t min_keycode, max_keycode;
guint len = retval->len;
min_keycode = xkb_keymap_min_keycode (xkb_keymap);
max_keycode = xkb_keymap_max_keycode (xkb_keymap);
for (keycode = min_keycode; keycode < max_keycode; keycode++)
{
gint num_layouts, layout;
num_layouts = xkb_keymap_num_layouts_for_key (xkb_keymap, keycode);
for (layout = 0; layout < num_layouts; layout++)
{
gint num_levels, level;
num_levels = xkb_keymap_num_levels_for_key (xkb_keymap, keycode, layout);
for (level = 0; level < num_levels; level++)
{
const xkb_keysym_t *syms;
gint num_syms, sym;
num_syms = xkb_keymap_key_get_syms_by_level (xkb_keymap, keycode, layout, level, &syms);
for (sym = 0; sym < num_syms; sym++)
{
if (syms[sym] == keyval)
{
GdkKeymapKey key;
key.keycode = keycode;
key.group = layout;
key.level = level;
g_array_append_val (retval, key);
}
}
}
}
}
return retval->len > len;
}
static gboolean
gdk_wayland_keymap_get_entries_for_keycode (GdkKeymap *keymap,
guint hardware_keycode,
GdkKeymapKey **keys,
guint **keyvals,
gint *n_entries)
{
struct xkb_keymap *xkb_keymap = GDK_WAYLAND_KEYMAP (keymap)->xkb_keymap;
gint num_layouts, layout;
gint num_entries;
gint i;
num_layouts = xkb_keymap_num_layouts_for_key (xkb_keymap, hardware_keycode);
num_entries = 0;
for (layout = 0; layout < num_layouts; layout++)
num_entries += xkb_keymap_num_levels_for_key (xkb_keymap, hardware_keycode, layout);
if (n_entries)
*n_entries = num_entries;
if (keys)
*keys = g_new0 (GdkKeymapKey, num_entries);
if (keyvals)
*keyvals = g_new0 (guint, num_entries);
i = 0;
for (layout = 0; layout < num_layouts; layout++)
{
gint num_levels, level;
num_levels = xkb_keymap_num_levels_for_key (xkb_keymap, hardware_keycode, layout);
for (level = 0; level < num_levels; level++)
{
const xkb_keysym_t *syms;
int num_syms;
num_syms = xkb_keymap_key_get_syms_by_level (xkb_keymap, hardware_keycode, layout, 0, &syms);
if (keys)
{
(*keys)[i].keycode = hardware_keycode;
(*keys)[i].group = layout;
(*keys)[i].level = level;
}
if (keyvals && num_syms > 0)
(*keyvals)[i] = syms[0];
i++;
}
}
return num_entries > 0;
}
static guint
gdk_wayland_keymap_lookup_key (GdkKeymap *keymap,
const GdkKeymapKey *key)
{
struct xkb_keymap *xkb_keymap = GDK_WAYLAND_KEYMAP (keymap)->xkb_keymap;
const xkb_keysym_t *syms;
int num_syms;
num_syms = xkb_keymap_key_get_syms_by_level (xkb_keymap,
key->keycode,
key->group,
key->level,
&syms);
if (num_syms > 0)
return syms[0];
else
return XKB_KEY_NoSymbol;
}
static guint32
get_xkb_modifiers (struct xkb_keymap *xkb_keymap,
GdkModifierType state)
{
guint32 mods = 0;
if (state & GDK_SHIFT_MASK)
mods |= 1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_SHIFT);
if (state & GDK_LOCK_MASK)
mods |= 1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_CAPS);
if (state & GDK_CONTROL_MASK)
mods |= 1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_CTRL);
if (state & GDK_ALT_MASK)
mods |= 1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_ALT);
if (state & GDK_SUPER_MASK)
mods |= 1 << xkb_keymap_mod_get_index (xkb_keymap, "Super");
if (state & GDK_HYPER_MASK)
mods |= 1 << xkb_keymap_mod_get_index (xkb_keymap, "Hyper");
if (state & GDK_META_MASK)
mods |= 1 << xkb_keymap_mod_get_index (xkb_keymap, "Meta");
return mods;
}
static GdkModifierType
get_gdk_modifiers (struct xkb_keymap *xkb_keymap,
guint32 mods)
{
GdkModifierType state = 0;
if (mods & (1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_SHIFT)))
state |= GDK_SHIFT_MASK;
if (mods & (1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_CAPS)))
state |= GDK_LOCK_MASK;
if (mods & (1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_CTRL)))
state |= GDK_CONTROL_MASK;
if (mods & (1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_ALT)))
state |= GDK_ALT_MASK;
if (mods & (1 << xkb_keymap_mod_get_index (xkb_keymap, "Super")))
state |= GDK_SUPER_MASK;
if (mods & (1 << xkb_keymap_mod_get_index (xkb_keymap, "Hyper")))
state |= GDK_HYPER_MASK;
/* GTK treats MOD1 as a synonym for Alt, and does not expect it to
* be mapped around, so we should avoid adding GDK_META_MASK if MOD1
* is already included to avoid confusing GTK and applications that
* rely on that behavior.
*/
if (mods & (1 << xkb_keymap_mod_get_index (xkb_keymap, "Meta")) &&
(state & GDK_ALT_MASK) == 0)
state |= GDK_META_MASK;
return state;
}
GdkModifierType
gdk_wayland_keymap_get_gdk_modifiers (GdkKeymap *keymap,
guint32 mods)
{
struct xkb_keymap *xkb_keymap = GDK_WAYLAND_KEYMAP (keymap)->xkb_keymap;
return get_gdk_modifiers (xkb_keymap, mods);
}
static gboolean
gdk_wayland_keymap_translate_keyboard_state (GdkKeymap *keymap,
guint hardware_keycode,
GdkModifierType state,
gint group,
guint *keyval,
gint *effective_group,
gint *effective_level,
GdkModifierType *consumed_modifiers)
{
struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state;
guint32 modifiers;
guint32 consumed;
xkb_layout_index_t layout;
xkb_level_index_t level;
xkb_keysym_t sym;
g_return_val_if_fail (keymap == NULL || GDK_IS_KEYMAP (keymap), FALSE);
g_return_val_if_fail (group < 4, FALSE);
xkb_keymap = GDK_WAYLAND_KEYMAP (keymap)->xkb_keymap;
modifiers = get_xkb_modifiers (xkb_keymap, state);
xkb_state = xkb_state_new (xkb_keymap);
xkb_state_update_mask (xkb_state, modifiers, 0, 0, group, 0, 0);
layout = xkb_state_key_get_layout (xkb_state, hardware_keycode);
level = xkb_state_key_get_level (xkb_state, hardware_keycode, layout);
sym = xkb_state_key_get_one_sym (xkb_state, hardware_keycode);
consumed = modifiers & ~xkb_state_mod_mask_remove_consumed (xkb_state, hardware_keycode, modifiers);
xkb_state_unref (xkb_state);
if (keyval)
*keyval = sym;
if (effective_group)
*effective_group = layout;
if (effective_level)
*effective_level = level;
if (consumed_modifiers)
*consumed_modifiers = get_gdk_modifiers (xkb_keymap, consumed);
return (sym != XKB_KEY_NoSymbol);
}
static guint
gdk_wayland_keymap_get_modifier_state (GdkKeymap *keymap)
{
struct xkb_keymap *xkb_keymap = GDK_WAYLAND_KEYMAP (keymap)->xkb_keymap;
struct xkb_state *xkb_state = GDK_WAYLAND_KEYMAP (keymap)->xkb_state;
xkb_mod_mask_t mods;
mods = xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_EFFECTIVE);
return get_gdk_modifiers (xkb_keymap, mods);
}
static void
_gdk_wayland_keymap_class_init (GdkWaylandKeymapClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GdkKeymapClass *keymap_class = GDK_KEYMAP_CLASS (klass);
object_class->finalize = gdk_wayland_keymap_finalize;
keymap_class->get_direction = gdk_wayland_keymap_get_direction;
keymap_class->have_bidi_layouts = gdk_wayland_keymap_have_bidi_layouts;
keymap_class->get_caps_lock_state = gdk_wayland_keymap_get_caps_lock_state;
keymap_class->get_num_lock_state = gdk_wayland_keymap_get_num_lock_state;
keymap_class->get_scroll_lock_state = gdk_wayland_keymap_get_scroll_lock_state;
keymap_class->get_entries_for_keyval = gdk_wayland_keymap_get_entries_for_keyval;
keymap_class->get_entries_for_keycode = gdk_wayland_keymap_get_entries_for_keycode;
keymap_class->lookup_key = gdk_wayland_keymap_lookup_key;
keymap_class->translate_keyboard_state = gdk_wayland_keymap_translate_keyboard_state;
keymap_class->get_modifier_state = gdk_wayland_keymap_get_modifier_state;
}
static void
_gdk_wayland_keymap_init (GdkWaylandKeymap *keymap)
{
}
static void
update_direction (GdkWaylandKeymap *keymap)
{
gint num_layouts;
gint i;
gint *rtl;
xkb_keycode_t min_keycode, max_keycode;
guint key;
gboolean have_rtl, have_ltr;
num_layouts = xkb_keymap_num_layouts (keymap->xkb_keymap);
keymap->direction = g_renew (PangoDirection, keymap->direction, num_layouts);
rtl = g_newa (gint, num_layouts);
for (i = 0; i < num_layouts; i++)
rtl[i] = 0;
min_keycode = xkb_keymap_min_keycode (keymap->xkb_keymap);
max_keycode = xkb_keymap_max_keycode (keymap->xkb_keymap);
for (key = min_keycode; key < max_keycode; key++)
{
gint layouts, layout;
layouts = xkb_keymap_num_layouts_for_key (keymap->xkb_keymap, key);
for (layout = 0; layout < layouts; layout++)
{
const xkb_keysym_t *syms;
gint num_syms;
gint sym;
num_syms = xkb_keymap_key_get_syms_by_level (keymap->xkb_keymap, key, layout, 0, &syms);
for (sym = 0; sym < num_syms; sym++)
{
PangoDirection dir;
dir = gdk_unichar_direction (xkb_keysym_to_utf32 (syms[sym]));
switch (dir)
{
case PANGO_DIRECTION_RTL:
rtl[layout]++;
break;
case PANGO_DIRECTION_LTR:
rtl[layout]--;
break;
case PANGO_DIRECTION_TTB_LTR:
case PANGO_DIRECTION_TTB_RTL:
case PANGO_DIRECTION_WEAK_LTR:
case PANGO_DIRECTION_WEAK_RTL:
case PANGO_DIRECTION_NEUTRAL:
default:
break;
}
}
}
}
have_rtl = have_ltr = FALSE;
for (i = 0; i < num_layouts; i++)
{
if (rtl[i] > 0)
{
keymap->direction[i] = PANGO_DIRECTION_RTL;
have_rtl = TRUE;
}
else
{
keymap->direction[i] = PANGO_DIRECTION_LTR;
have_ltr = TRUE;
}
}
if (have_rtl && have_ltr)
keymap->bidi = TRUE;
}
GdkKeymap *
_gdk_wayland_keymap_new (GdkDisplay *display)
{
GdkWaylandKeymap *keymap;
struct xkb_context *context;
struct xkb_rule_names names;
keymap = g_object_new (_gdk_wayland_keymap_get_type(), NULL);
GDK_KEYMAP (keymap)->display = display;
context = xkb_context_new (0);
names.rules = "evdev";
names.model = "pc105";
names.layout = "us";
names.variant = "";
names.options = "";
keymap->xkb_keymap = xkb_keymap_new_from_names (context, &names, 0);
keymap->xkb_state = xkb_state_new (keymap->xkb_keymap);
xkb_context_unref (context);
update_direction (keymap);
return GDK_KEYMAP (keymap);
}
#ifdef G_ENABLE_DEBUG
static void
print_modifiers (struct xkb_keymap *keymap)
{
int i, j;
uint32_t real;
struct xkb_state *state;
g_print ("modifiers:\n");
for (i = 0; i < xkb_keymap_num_mods (keymap); i++)
g_print ("%s ", xkb_keymap_mod_get_name (keymap, i));
g_print ("\n\n");
g_print ("modifier mapping\n");
state = xkb_state_new (keymap);
for (i = 0; i < 8; i++)
{
gboolean need_arrow = TRUE;
g_print ("%s ", xkb_keymap_mod_get_name (keymap, i));
for (j = 8; j < xkb_keymap_num_mods (keymap); j++)
{
xkb_state_update_mask (state, 1 << j, 0, 0, 0, 0, 0);
real = xkb_state_serialize_mods (state, XKB_STATE_MODS_EFFECTIVE);
if (real & (1 << i))
{
if (need_arrow)
{
g_print ("-> ");
need_arrow = FALSE;
}
g_print ("%s ", xkb_keymap_mod_get_name (keymap, j));
}
}
g_print ("\n");
}
xkb_state_unref (state);
}
#endif
void
_gdk_wayland_keymap_update_from_fd (GdkKeymap *keymap,
uint32_t format,
uint32_t fd,
uint32_t size)
{
GdkWaylandKeymap *keymap_wayland = GDK_WAYLAND_KEYMAP (keymap);
struct xkb_context *context;
struct xkb_keymap *xkb_keymap;
char *map_str;
context = xkb_context_new (0);
map_str = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0);
if (map_str == MAP_FAILED)
{
close(fd);
return;
}
GDK_DISPLAY_NOTE (keymap->display, INPUT, g_message ("keymap:\n%s", map_str));
xkb_keymap = xkb_keymap_new_from_string (context, map_str, format, 0);
munmap (map_str, size);
close (fd);
if (!xkb_keymap)
{
g_warning ("Got invalid keymap from compositor, keeping previous/default one");
xkb_context_unref (context);
return;
}
GDK_DISPLAY_NOTE (keymap->display, INPUT, print_modifiers (xkb_keymap));
xkb_keymap_unref (keymap_wayland->xkb_keymap);
keymap_wayland->xkb_keymap = xkb_keymap;
xkb_state_unref (keymap_wayland->xkb_state);
keymap_wayland->xkb_state = xkb_state_new (keymap_wayland->xkb_keymap);
xkb_context_unref (context);
update_direction (keymap_wayland);
}
struct xkb_keymap *
_gdk_wayland_keymap_get_xkb_keymap (GdkKeymap *keymap)
{
return GDK_WAYLAND_KEYMAP (keymap)->xkb_keymap;
}
struct xkb_state *
_gdk_wayland_keymap_get_xkb_state (GdkKeymap *keymap)
{
return GDK_WAYLAND_KEYMAP (keymap)->xkb_state;
}
gboolean
_gdk_wayland_keymap_key_is_modifier (GdkKeymap *keymap,
guint keycode)
{
struct xkb_keymap *xkb_keymap = GDK_WAYLAND_KEYMAP (keymap)->xkb_keymap;
struct xkb_state *xkb_state;
gboolean is_modifier;
is_modifier = FALSE;
xkb_state = xkb_state_new (xkb_keymap);
if (xkb_state_update_key (xkb_state, keycode, XKB_KEY_DOWN) & XKB_STATE_MODS_EFFECTIVE)
is_modifier = TRUE;
xkb_state_unref (xkb_state);
return is_modifier;
}