mutter/clutter/x11/clutter-keymap-x11.c
Emmanuele Bassi 9090070a98 x11: Refresh key mapping when notified by X11
Use both the MappingNotify event and the XKB XkbMapNotify event, if
we're compiled with XKB support.

This change is also useful for making ClutterKeymapX11 an event
translator and let it deal with XKB events internally like we do for
stage and input events.

Based on a patch by: Damien Lespiau <damien.lespiau@intel.com>

Signed-off by: Emmanuele Bassi <ebassi@linux.intel.com>

http://bugzilla.clutter-project.org/show_bug.cgi?id=2525
2011-02-08 12:13:13 +00:00

478 lines
13 KiB
C

/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2010 Intel Corp.
*
* 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/>.
*
* Author: Emmanuele Bassi <ebassi@linux.intel.com>
*/
#include "config.h"
#include "clutter-keymap-x11.h"
#include "clutter-backend-x11.h"
#include "clutter-debug.h"
#include "clutter-event-translator.h"
#include "clutter-private.h"
#include <X11/Xatom.h>
#ifdef HAVE_XKB
#include <X11/XKBlib.h>
#endif
typedef struct _ClutterKeymapX11Class ClutterKeymapX11Class;
struct _ClutterKeymapX11
{
GObject parent_instance;
ClutterBackend *backend;
gint min_keycode;
gint max_keycode;
ClutterModifierType modmap[8];
ClutterModifierType num_lock_mask;
#ifdef HAVE_XKB
XkbDescPtr xkb_desc;
int xkb_event_base;
guint xkb_map_serial;
#endif
guint caps_lock_state : 1;
guint num_lock_state : 1;
};
struct _ClutterKeymapX11Class
{
GObjectClass parent_class;
};
enum
{
PROP_0,
PROP_BACKEND,
PROP_LAST
};
static GParamSpec *obj_props[PROP_LAST] = { NULL, };
static void clutter_event_translator_iface_init (ClutterEventTranslatorIface *iface);
#define clutter_keymap_x11_get_type _clutter_keymap_x11_get_type
G_DEFINE_TYPE_WITH_CODE (ClutterKeymapX11, clutter_keymap_x11, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_EVENT_TRANSLATOR,
clutter_event_translator_iface_init));
#ifdef HAVE_XKB
/* code adapted from gdk/x11/gdkkeys-x11.c - update_modmap */
static void
update_modmap (Display *display,
ClutterKeymapX11 *keymap_x11)
{
static struct {
const gchar *name;
Atom atom;
ClutterModifierType mask;
} vmods[] = {
{ "Meta", 0, CLUTTER_META_MASK },
{ "Super", 0, CLUTTER_SUPER_MASK },
{ "Hyper", 0, CLUTTER_HYPER_MASK },
{ NULL, 0, 0 }
};
int i, j, k;
if (vmods[0].atom == 0)
for (i = 0; vmods[i].name; i++)
vmods[i].atom = XInternAtom (display, vmods[i].name, FALSE);
for (i = 0; i < 8; i++)
keymap_x11->modmap[i] = 1 << i;
for (i = 0; i < XkbNumVirtualMods; i++)
{
for (j = 0; vmods[j].atom; j++)
{
if (keymap_x11->xkb_desc->names->vmods[i] == vmods[j].atom)
{
for (k = 0; k < 8; k++)
{
if (keymap_x11->xkb_desc->server->vmods[i] & (1 << k))
keymap_x11->modmap[k] |= vmods[j].mask;
}
}
}
}
}
static XkbDescPtr
get_xkb (ClutterKeymapX11 *keymap_x11)
{
ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (keymap_x11->backend);
if (keymap_x11->max_keycode == 0)
XDisplayKeycodes (backend_x11->xdpy,
&keymap_x11->min_keycode,
&keymap_x11->max_keycode);
if (keymap_x11->xkb_desc == NULL)
{
int flags = XkbKeySymsMask
| XkbKeyTypesMask
| XkbModifierMapMask
| XkbVirtualModsMask;
keymap_x11->xkb_desc = XkbGetMap (backend_x11->xdpy, flags, XkbUseCoreKbd);
if (G_UNLIKELY (keymap_x11->xkb_desc == NULL))
{
g_error ("Failed to get the keymap from XKB");
return NULL;
}
flags = XkbGroupNamesMask | XkbVirtualModNamesMask;
XkbGetNames (backend_x11->xdpy, flags, keymap_x11->xkb_desc);
update_modmap (backend_x11->xdpy, keymap_x11);
}
else if (keymap_x11->xkb_map_serial != backend_x11->keymap_serial)
{
int flags = XkbKeySymsMask
| XkbKeyTypesMask
| XkbModifierMapMask
| XkbVirtualModsMask;
CLUTTER_NOTE (BACKEND, "Updating XKB keymap");
XkbGetUpdatedMap (backend_x11->xdpy, flags, keymap_x11->xkb_desc);
flags = XkbGroupNamesMask | XkbVirtualModNamesMask;
XkbGetNames (backend_x11->xdpy, flags, keymap_x11->xkb_desc);
update_modmap (backend_x11->xdpy, keymap_x11);
keymap_x11->xkb_map_serial = backend_x11->keymap_serial;
}
if (keymap_x11->num_lock_mask == 0)
keymap_x11->num_lock_mask = XkbKeysymToModifiers (backend_x11->xdpy,
XK_Num_Lock);
return keymap_x11->xkb_desc;
}
#endif /* HAVE_XKB */
#ifdef HAVE_XKB
static void
update_locked_mods (ClutterKeymapX11 *keymap_x11,
gint locked_mods)
{
gboolean old_caps_lock_state, old_num_lock_state;
old_caps_lock_state = keymap_x11->caps_lock_state;
old_num_lock_state = keymap_x11->num_lock_state;
keymap_x11->caps_lock_state = (locked_mods & CLUTTER_LOCK_MASK) != 0;
keymap_x11->num_lock_state = (locked_mods & keymap_x11->num_lock_mask) != 0;
CLUTTER_NOTE (BACKEND, "Locks state changed - Num: %s, Caps: %s",
keymap_x11->num_lock_state ? "set" : "unset",
keymap_x11->caps_lock_state ? "set" : "unset");
#if 0
/* Add signal to ClutterBackend? */
if ((keymap_x11->caps_lock_state != old_caps_lock_state) ||
(keymap_x11->num_lock_state != old_num_lock_state))
g_signal_emit_by_name (keymap_x11->backend, "key-lock-changed");
#endif
}
#endif /* HAVE_XKB */
static void
clutter_keymap_x11_constructed (GObject *gobject)
{
ClutterKeymapX11 *keymap_x11 = CLUTTER_KEYMAP_X11 (gobject);
ClutterBackendX11 *backend_x11;
g_assert (keymap_x11->backend != NULL);
backend_x11 = CLUTTER_BACKEND_X11 (keymap_x11->backend);
#ifdef HAVE_XKB
{
gint xkb_major = XkbMajorVersion;
gint xkb_minor = XkbMinorVersion;
if (XkbLibraryVersion (&xkb_major, &xkb_minor))
{
xkb_major = XkbMajorVersion;
xkb_minor = XkbMinorVersion;
if (XkbQueryExtension (backend_x11->xdpy,
NULL,
&keymap_x11->xkb_event_base,
NULL,
&xkb_major, &xkb_minor))
{
Bool detectable_autorepeat_supported;
ClutterEventTranslator *t;
backend_x11->use_xkb = TRUE;
XkbSelectEvents (backend_x11->xdpy,
XkbUseCoreKbd,
XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask,
XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask);
XkbSelectEventDetails (backend_x11->xdpy,
XkbUseCoreKbd, XkbStateNotify,
XkbAllStateComponentsMask,
XkbGroupLockMask | XkbModifierLockMask);
/* add ourselves as an event translator for XKB events */
t = CLUTTER_EVENT_TRANSLATOR (keymap_x11);
_clutter_backend_x11_add_event_translator (backend_x11, t);
/* enable XKB autorepeat */
XkbSetDetectableAutoRepeat (backend_x11->xdpy,
True,
&detectable_autorepeat_supported);
backend_x11->have_xkb_autorepeat = detectable_autorepeat_supported;
CLUTTER_NOTE (BACKEND, "Detectable autorepeat: %s",
backend_x11->have_xkb_autorepeat ? "supported"
: "not supported");
}
}
}
#endif /* HAVE_XKB */
}
static void
clutter_keymap_x11_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ClutterKeymapX11 *keymap = CLUTTER_KEYMAP_X11 (gobject);
switch (prop_id)
{
case PROP_BACKEND:
keymap->backend = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
clutter_keymap_x11_finalize (GObject *gobject)
{
ClutterKeymapX11 *keymap;
ClutterBackendX11 *backend;
ClutterEventTranslator *translator;
keymap = CLUTTER_KEYMAP_X11 (gobject);
backend = CLUTTER_BACKEND_X11 (keymap->backend);
translator = CLUTTER_EVENT_TRANSLATOR (keymap);
#ifdef HAVE_XKB
_clutter_backend_x11_remove_event_translator (backend, translator);
if (keymap->xkb_desc != NULL)
XkbFreeKeyboard (keymap->xkb_desc, XkbAllComponentsMask, True);
#endif
G_OBJECT_CLASS (clutter_keymap_x11_parent_class)->finalize (gobject);
}
static void
clutter_keymap_x11_class_init (ClutterKeymapX11Class *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
obj_props[PROP_BACKEND] =
g_param_spec_object ("backend",
P_("Backend"),
P_("The Clutter backend"),
CLUTTER_TYPE_BACKEND,
CLUTTER_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
gobject_class->constructed = clutter_keymap_x11_constructed;
gobject_class->set_property = clutter_keymap_x11_set_property;
gobject_class->finalize = clutter_keymap_x11_finalize;
g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
}
static void
clutter_keymap_x11_init (ClutterKeymapX11 *keymap)
{
}
static ClutterTranslateReturn
clutter_keymap_x11_translate_event (ClutterEventTranslator *translator,
gpointer native,
ClutterEvent *event)
{
ClutterKeymapX11 *keymap_x11 = CLUTTER_KEYMAP_X11 (translator);
ClutterBackendX11 *backend_x11;
ClutterTranslateReturn retval;
XEvent *xevent;
backend_x11 = CLUTTER_BACKEND_X11 (keymap_x11->backend);
if (!backend_x11->use_xkb)
return CLUTTER_TRANSLATE_CONTINUE;
xevent = native;
retval = CLUTTER_TRANSLATE_CONTINUE;
#ifdef HAVE_XKB
if (xevent->type == keymap_x11->xkb_event_base)
{
XkbEvent *xkb_event = (XkbEvent *) xevent;
switch (xkb_event->any.xkb_type)
{
case XkbStateNotify:
CLUTTER_NOTE (EVENT, "Updating locked modifiers");
update_locked_mods (keymap_x11, xkb_event->state.locked_mods);
retval = CLUTTER_TRANSLATE_REMOVE;
break;
case XkbMapNotify:
CLUTTER_NOTE (EVENT, "Updating keyboard mapping");
XkbRefreshKeyboardMapping (&xkb_event->map);
backend_x11->keymap_serial += 1;
retval = CLUTTER_TRANSLATE_REMOVE;
break;
default:
break;
}
}
#endif /* HAVE_XKB */
return retval;
}
static void
clutter_event_translator_iface_init (ClutterEventTranslatorIface *iface)
{
iface->translate_event = clutter_keymap_x11_translate_event;
}
gint
_clutter_keymap_x11_get_key_group (ClutterKeymapX11 *keymap,
ClutterModifierType state)
{
#ifdef HAVE_XKB
return XkbGroupForCoreState (state);
#else
return 0;
#endif /* HAVE_XKB */
}
gboolean
_clutter_keymap_x11_get_num_lock_state (ClutterKeymapX11 *keymap)
{
g_return_val_if_fail (CLUTTER_IS_KEYMAP_X11 (keymap), FALSE);
return keymap->num_lock_state;
}
gboolean
_clutter_keymap_x11_get_caps_lock_state (ClutterKeymapX11 *keymap)
{
g_return_val_if_fail (CLUTTER_IS_KEYMAP_X11 (keymap), FALSE);
return keymap->caps_lock_state;
}
gint
_clutter_keymap_x11_translate_key_state (ClutterKeymapX11 *keymap,
guint hardware_keycode,
ClutterModifierType modifier_state,
ClutterModifierType *mods_p)
{
ClutterBackendX11 *backend_x11;
ClutterModifierType unconsumed_modifiers = 0;
gint retval;
g_return_val_if_fail (CLUTTER_IS_KEYMAP_X11 (keymap), 0);
backend_x11 = CLUTTER_BACKEND_X11 (keymap->backend);
#ifdef HAVE_XKB
if (backend_x11->use_xkb)
{
XkbDescRec *xkb = get_xkb (keymap);
KeySym tmp_keysym;
if (XkbTranslateKeyCode (xkb, hardware_keycode, modifier_state,
&unconsumed_modifiers,
&tmp_keysym))
{
retval = tmp_keysym;
}
else
retval = 0;
}
else
#endif /* HAVE_XKB */
retval = XKeycodeToKeysym (backend_x11->xdpy, hardware_keycode, 0);
if (mods_p)
*mods_p = unconsumed_modifiers;
return retval;
}
gboolean
_clutter_keymap_x11_get_is_modifier (ClutterKeymapX11 *keymap,
guint keycode)
{
g_return_val_if_fail (CLUTTER_IS_KEYMAP_X11 (keymap), FALSE);
if (keycode < keymap->min_keycode || keycode > keymap->max_keycode)
return FALSE;
#ifdef HAVE_XKB
if (CLUTTER_BACKEND_X11 (keymap->backend)->use_xkb)
{
XkbDescRec *xkb = get_xkb (keymap);
if (xkb->map->modmap && xkb->map->modmap[keycode] != 0)
return TRUE;
}
#endif /* HAVE_XKB */
return FALSE;
}