/* * 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 . * * Author: Emmanuele Bassi */ #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 #ifdef HAVE_XKB #include #endif typedef struct _ClutterKeymapX11Class ClutterKeymapX11Class; typedef struct _DirectionCacheEntry DirectionCacheEntry; struct _DirectionCacheEntry { guint serial; Atom group_atom; PangoDirection direction; }; struct _ClutterKeymapX11 { GObject parent_instance; ClutterBackend *backend; int min_keycode; int max_keycode; ClutterModifierType modmap[8]; ClutterModifierType num_lock_mask; ClutterModifierType scroll_lock_mask; PangoDirection current_direction; #ifdef HAVE_XKB XkbDescPtr xkb_desc; int xkb_event_base; guint xkb_map_serial; Atom current_group_atom; guint current_cache_serial; DirectionCacheEntry group_direction_cache[4]; #endif guint caps_lock_state : 1; guint num_lock_state : 1; guint has_direction : 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); if (keymap_x11->scroll_lock_mask == 0) keymap_x11->scroll_lock_mask = XkbKeysymToModifiers (backend_x11->xdpy, XK_Scroll_Lock); return keymap_x11->xkb_desc; } #endif /* HAVE_XKB */ #ifdef HAVE_XKB static void update_locked_mods (ClutterKeymapX11 *keymap_x11, gint locked_mods) { #if 0 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; #endif 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 */ #ifdef HAVE_XKB /* the code to retrieve the keymap direction and cache it * is taken from GDK: * gdk/x11/gdkkeys-x11.c */ static PangoDirection get_direction (XkbDescPtr xkb, int group) { int rtl_minus_ltr = 0; /* total number of RTL keysyms minus LTR ones */ int code; for (code = xkb->min_key_code; code <= xkb->max_key_code; code += 1) { int level = 0; KeySym sym = XkbKeySymEntry (xkb, code, level, group); PangoDirection dir = pango_unichar_direction (clutter_keysym_to_unicode (sym)); switch (dir) { case PANGO_DIRECTION_RTL: rtl_minus_ltr++; break; case PANGO_DIRECTION_LTR: rtl_minus_ltr--; break; default: break; } } if (rtl_minus_ltr > 0) return PANGO_DIRECTION_RTL; return PANGO_DIRECTION_LTR; } static PangoDirection get_direction_from_cache (ClutterKeymapX11 *keymap_x11, XkbDescPtr xkb, int group) { Atom group_atom = xkb->names->groups[group]; gboolean cache_hit = FALSE; DirectionCacheEntry *cache = keymap_x11->group_direction_cache; PangoDirection direction = PANGO_DIRECTION_NEUTRAL; int i; if (keymap_x11->has_direction) { /* look up in the cache */ for (i = 0; i < G_N_ELEMENTS (keymap_x11->group_direction_cache); i++) { if (cache[i].group_atom == group_atom) { cache_hit = TRUE; cache[i].serial = keymap_x11->current_cache_serial++; direction = cache[i].direction; group_atom = cache[i].group_atom; break; } } } else { /* initialize the cache */ for (i = 0; i < G_N_ELEMENTS (keymap_x11->group_direction_cache); i++) { cache[i].group_atom = 0; cache[i].direction = PANGO_DIRECTION_NEUTRAL; cache[i].serial = keymap_x11->current_cache_serial; } keymap_x11->current_cache_serial += 1; } /* insert the new entry in the cache */ if (!cache_hit) { int oldest = 0; direction = get_direction (xkb, group); /* replace the oldest entry */ for (i = 0; i < G_N_ELEMENTS (keymap_x11->group_direction_cache); i++) { if (cache[i].serial < cache[oldest].serial) oldest = i; } cache[oldest].group_atom = group_atom; cache[oldest].direction = direction; cache[oldest].serial = keymap_x11->current_cache_serial++; } return direction; } #endif /* HAVE_XKB */ static void update_direction (ClutterKeymapX11 *keymap_x11, int group) { #ifdef HAVE_XKB XkbDescPtr xkb = get_xkb (keymap_x11); Atom group_atom; group_atom = xkb->names->groups[group]; if (!keymap_x11->has_direction || keymap_x11->current_group_atom != group_atom) { keymap_x11->current_direction = get_direction_from_cache (keymap_x11, xkb, group); keymap_x11->current_group_atom = group_atom; keymap_x11->has_direction = TRUE; } #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; 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); /* 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; ClutterEventTranslator *translator; keymap = CLUTTER_KEYMAP_X11 (gobject); translator = CLUTTER_EVENT_TRANSLATOR (keymap); #ifdef HAVE_XKB _clutter_backend_remove_event_translator (keymap->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); 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) { keymap->current_direction = PANGO_DIRECTION_NEUTRAL; } 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 keyboard state"); update_direction (keymap_x11, XkbStateGroup (&xkb_event->state)); update_locked_mods (keymap_x11, xkb_event->state.locked_mods); retval = CLUTTER_TRANSLATE_REMOVE; break; case XkbNewKeyboardNotify: 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; } G_GNUC_BEGIN_IGNORE_DEPRECATIONS /* XXX - yes, I know that XKeycodeToKeysym() has been deprecated; hopefully, * this code will never get run on any decent system that is also able to * run Clutter. I just don't want to copy the implementation inside GDK for * a fallback path. */ static int translate_keysym (ClutterKeymapX11 *keymap, guint hardware_keycode) { ClutterBackendX11 *backend_x11; gint retval; backend_x11 = CLUTTER_BACKEND_X11 (keymap->backend); retval = XKeycodeToKeysym (backend_x11->xdpy, hardware_keycode, 0); return retval; } G_GNUC_END_IGNORE_DEPRECATIONS gint _clutter_keymap_x11_translate_key_state (ClutterKeymapX11 *keymap, guint hardware_keycode, ClutterModifierType *modifier_state_p, ClutterModifierType *mods_p) { ClutterBackendX11 *backend_x11; ClutterModifierType unconsumed_modifiers = 0; ClutterModifierType modifier_state = *modifier_state_p; 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 = translate_keysym (keymap, hardware_keycode); if (mods_p) *mods_p = unconsumed_modifiers; *modifier_state_p = modifier_state & ~(keymap->num_lock_mask | keymap->scroll_lock_mask | LockMask); return retval; } gboolean _clutter_keymap_x11_get_is_modifier (ClutterKeymapX11 *keymap, gint 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; } PangoDirection _clutter_keymap_x11_get_direction (ClutterKeymapX11 *keymap) { g_return_val_if_fail (CLUTTER_IS_KEYMAP_X11 (keymap), PANGO_DIRECTION_NEUTRAL); #ifdef HAVE_XKB if (CLUTTER_BACKEND_X11 (keymap->backend)->use_xkb) { if (!keymap->has_direction) { Display *xdisplay = CLUTTER_BACKEND_X11 (keymap->backend)->xdpy; XkbStateRec state_rec; XkbGetState (xdisplay, XkbUseCoreKbd, &state_rec); update_direction (keymap, XkbStateGroup (&state_rec)); } return keymap->current_direction; } else #endif return PANGO_DIRECTION_NEUTRAL; }