mutter/clutter/x11/clutter-device-manager-core-x11.c
Emmanuele Bassi 1b1e77b469 event/x11: Rework the way we translate X11 events
This is a lump commit that is fairly difficult to break down without
either breaking bisecting or breaking the test cases.

The new design for handling X11 event translation works this way:

  - ClutterBackend::translate_event() has been added as the central
    point used by a ClutterBackend implementation to translate a
    native event into a ClutterEvent;

  - ClutterEventTranslator is a private interface that should be
    implemented by backend-specific objects, like stage
    implementations and ClutterDeviceManager sub-classes, and
    allows dealing with class-specific event translation;

  - ClutterStageX11 implements EventTranslator, and deals with the
    stage-relative X11 events coming from the X11 event source;

  - ClutterStageGLX overrides EventTranslator, in order to
    deal with the INTEL_GLX_swap_event extension, and it chains up
    to the X11 default implementation;

  - ClutterDeviceManagerX11 has been split into two separate classes,
    one that deals with core and (optionally) XI1 events, and the
    other that deals with XI2 events; the selection is done at run-time,
    since the core+XI1 and XI2 mechanisms are mutually exclusive.

All the other backends we officially support still use their own
custom event source and translation function, but the end goal is to
migrate them to the translate_event() virtual function, and have the
event source be a shared part of Clutter core.
2011-01-21 10:25:43 +00:00

746 lines
24 KiB
C

/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2009 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-device-manager-core-x11.h"
#include "clutter-backend-x11.h"
#include "clutter-input-device-core-x11.h"
#include "clutter-stage-x11.h"
#include "clutter-backend.h"
#include "clutter-debug.h"
#include "clutter-device-manager-private.h"
#include "clutter-event-translator.h"
#include "clutter-stage-private.h"
#include "clutter-private.h"
#ifdef HAVE_XINPUT
#include <X11/extensions/XInput.h>
/* old versions of XI.h don't define these */
#ifndef IsXExtensionKeyboard
#define IsXExtensionKeyboard 3
#define IsXExtensionPointer 4
#endif
#endif /* HAVE_XINPUT */
enum
{
PROP_0,
PROP_EVENT_BASE,
PROP_LAST
};
static GParamSpec *obj_props[PROP_LAST] = { NULL, };
static void clutter_event_translator_iface_init (ClutterEventTranslatorIface *iface);
G_DEFINE_TYPE_WITH_CODE (ClutterDeviceManagerX11,
clutter_device_manager_x11,
CLUTTER_TYPE_DEVICE_MANAGER,
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_EVENT_TRANSLATOR,
clutter_event_translator_iface_init));
#ifdef HAVE_XINPUT
static void
translate_class_info (ClutterInputDevice *device,
XDeviceInfo *info)
{
XAnyClassPtr any_class;
gint i;
any_class = info->inputclassinfo;
for (i = 0; i < info->num_classes; i++)
{
switch (any_class->class)
{
case ButtonClass:
break;
case KeyClass:
{
XKeyInfo *xk_info = (XKeyInfo *) any_class;
guint n_keys;
n_keys = xk_info->max_keycode - xk_info->min_keycode + 1;
_clutter_input_device_set_keys (device, n_keys,
xk_info->min_keycode,
xk_info->max_keycode);
}
break;
case ValuatorClass:
{
XValuatorInfo *xv_info = (XValuatorInfo *) any_class;
gint j;
for (j = 0; j < xv_info->num_axes; j++)
{
ClutterInputAxis axis;
switch (j)
{
case 0:
axis = CLUTTER_INPUT_AXIS_X;
break;
case 1:
axis = CLUTTER_INPUT_AXIS_Y;
break;
case 2:
axis = CLUTTER_INPUT_AXIS_PRESSURE;
break;
case 3:
axis = CLUTTER_INPUT_AXIS_XTILT;
break;
case 4:
axis = CLUTTER_INPUT_AXIS_YTILT;
break;
case 5:
axis = CLUTTER_INPUT_AXIS_WHEEL;
break;
default:
axis = CLUTTER_INPUT_AXIS_IGNORE;
break;
}
_clutter_input_device_add_axis (device, axis,
xv_info->axes[j].min_value,
xv_info->axes[j].max_value,
xv_info->axes[j].resolution);
}
}
break;
}
any_class = (XAnyClassPtr) (((char *) any_class) + any_class->length);
}
}
static ClutterInputDevice *
create_device (ClutterDeviceManagerX11 *manager_x11,
ClutterBackendX11 *backend_x11,
XDeviceInfo *info)
{
ClutterInputDeviceType source;
ClutterInputDevice *retval;
if (info->use != IsXExtensionPointer &&
info->use != IsXExtensionKeyboard)
return NULL;
if (info->use == IsXExtensionKeyboard)
source = CLUTTER_KEYBOARD_DEVICE;
else
{
gchar *name;
name = g_ascii_strdown (info->name, -1);
if (strstr (name, "eraser") != NULL)
source = CLUTTER_ERASER_DEVICE;
else if (strstr (name, "cursor") != NULL)
source = CLUTTER_CURSOR_DEVICE;
else if (strstr (name, "wacom") != NULL || strstr (name, "pen") != NULL)
source = CLUTTER_PEN_DEVICE;
else
source = CLUTTER_POINTER_DEVICE;
g_free (name);
}
retval = g_object_new (CLUTTER_TYPE_INPUT_DEVICE_X11,
"name", info->name,
"id", info->id,
"has-cursor", FALSE,
"device-manager", manager_x11,
"device-type", source,
"device-mode", CLUTTER_INPUT_MODE_FLOATING,
"backend", backend_x11,
NULL);
translate_class_info (retval, info);
CLUTTER_NOTE (BACKEND,
"XI Device '%s' (id: %d) created",
info->name,
(int) info->id);
return retval;
}
#endif /* HAVE_XINPUT */
static inline void
translate_key_event (ClutterBackendX11 *backend_x11,
ClutterDeviceManagerX11 *manager_x11,
ClutterEvent *event,
XEvent *xevent)
{
ClutterEventX11 *event_x11;
char buffer[256 + 1];
int n;
event->key.type = xevent->xany.type == KeyPress ? CLUTTER_KEY_PRESS
: CLUTTER_KEY_RELEASE;
event->key.time = xevent->xkey.time;
event->key.device = manager_x11->core_keyboard;
/* KeyEvents have platform specific data associated to them */
event_x11 = _clutter_event_x11_new ();
_clutter_event_set_platform_data (event, event_x11);
event->key.modifier_state = (ClutterModifierType) xevent->xkey.state;
event->key.hardware_keycode = xevent->xkey.keycode;
/* keyval is the key ignoring all modifiers ('1' vs. '!') */
event->key.keyval =
_clutter_keymap_x11_translate_key_state (backend_x11->keymap,
event->key.hardware_keycode,
event->key.modifier_state,
NULL);
event_x11->key_group =
_clutter_keymap_x11_get_key_group (backend_x11->keymap,
event->key.modifier_state);
event_x11->key_is_modifier =
_clutter_keymap_x11_get_is_modifier (backend_x11->keymap,
event->key.hardware_keycode);
event_x11->num_lock_set =
_clutter_keymap_x11_get_num_lock_state (backend_x11->keymap);
event_x11->caps_lock_set =
_clutter_keymap_x11_get_caps_lock_state (backend_x11->keymap);
/* unicode_value is the printable representation */
n = XLookupString (&xevent->xkey, buffer, sizeof (buffer) - 1, NULL, NULL);
if (n != NoSymbol)
{
event->key.unicode_value = g_utf8_get_char_validated (buffer, n);
if ((event->key.unicode_value != -1) &&
(event->key.unicode_value != -2))
goto out;
}
else
event->key.unicode_value = (gunichar)'\0';
out:
CLUTTER_NOTE (EVENT,
"%s: win:0x%x, key: %12s (%d)",
event->any.type == CLUTTER_KEY_PRESS
? "key press "
: "key release",
(unsigned int) xevent->xkey.window,
event->key.keyval ? buffer : "(none)",
event->key.keyval);
return;
}
#ifdef HAVE_XINPUT
static ClutterInputDevice *
get_device_from_event (ClutterDeviceManagerX11 *manager_x11,
XEvent *xevent)
{
guint32 device_id;
device_id = ((XDeviceButtonEvent *) xevent)->deviceid;
return g_hash_table_lookup (manager_x11->devices_by_id,
GINT_TO_POINTER (device_id));
}
#endif /* HAVE_XINPUT */
static ClutterTranslateReturn
clutter_device_manager_x11_translate_event (ClutterEventTranslator *translator,
gpointer native,
ClutterEvent *event)
{
ClutterDeviceManagerX11 *manager_x11;
ClutterBackendX11 *backend_x11;
ClutterInputDevice *device;
ClutterStageX11 *stage_x11;
ClutterTranslateReturn res;
ClutterStage *stage;
XEvent *xevent;
manager_x11 = CLUTTER_DEVICE_MANAGER_X11 (translator);
backend_x11 = CLUTTER_BACKEND_X11 (clutter_get_default_backend ());
device = NULL;
xevent = native;
stage = clutter_x11_get_stage_from_window (xevent->xany.window);
if (stage == NULL)
return CLUTTER_TRANSLATE_CONTINUE;
if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
return CLUTTER_TRANSLATE_CONTINUE;
stage_x11 = CLUTTER_STAGE_X11 (_clutter_stage_get_window (stage));
event->any.stage = stage;
res = CLUTTER_TRANSLATE_CONTINUE;
#ifdef HAVE_XINPUT
device = get_device_from_event (manager_x11, xevent);
if (device != NULL)
{
ClutterInputDeviceX11 *device_x11;
gboolean retval;
device_x11 = CLUTTER_INPUT_DEVICE_X11 (device);
retval = _clutter_input_device_x11_translate_xi_event (device_x11,
stage_x11,
xevent,
event);
if (retval)
return CLUTTER_TRANSLATE_QUEUE;
}
#endif /* HAVE_XINPUT */
switch (xevent->type)
{
case KeyPress:
translate_key_event (backend_x11, manager_x11, event, xevent);
_clutter_stage_x11_set_user_time (stage_x11, xevent->xkey.time);
res = CLUTTER_TRANSLATE_QUEUE;
break;
case KeyRelease:
/* old-style X11 terminals require that even modern X11 send
* KeyPress/KeyRelease pairs when auto-repeating. for this
* reason modern(-ish) API like XKB has a way to detect
* auto-repeat and do a single KeyRelease at the end of a
* KeyPress sequence.
*
* this check emulates XKB's detectable auto-repeat; we peek
* the next event and check if it's a KeyPress for the same key
* and timestamp - and then ignore it if it matches the
* KeyRelease
*
* if we have XKB, and autorepeat is enabled, then this becomes
* a no-op
*/
if (!backend_x11->have_xkb_autorepeat && XPending (xevent->xkey.display))
{
XEvent next_event;
XPeekEvent (xevent->xkey.display, &next_event);
if (next_event.type == KeyPress &&
next_event.xkey.keycode == xevent->xkey.keycode &&
next_event.xkey.time == xevent->xkey.time)
{
res = CLUTTER_TRANSLATE_REMOVE;
break;
}
}
translate_key_event (backend_x11, manager_x11, event, xevent);
res = CLUTTER_TRANSLATE_QUEUE;
break;
case ButtonPress:
CLUTTER_NOTE (EVENT,
"button press: win: 0x%x, coords: %d, %d, button: %d",
(unsigned int) stage_x11->xwin,
xevent->xbutton.x,
xevent->xbutton.y,
xevent->xbutton.button);
switch (xevent->xbutton.button)
{
case 4: /* up */
case 5: /* down */
case 6: /* left */
case 7: /* right */
event->scroll.type = CLUTTER_SCROLL;
if (xevent->xbutton.button == 4)
event->scroll.direction = CLUTTER_SCROLL_UP;
else if (xevent->xbutton.button == 5)
event->scroll.direction = CLUTTER_SCROLL_DOWN;
else if (xevent->xbutton.button == 6)
event->scroll.direction = CLUTTER_SCROLL_LEFT;
else
event->scroll.direction = CLUTTER_SCROLL_RIGHT;
event->scroll.time = xevent->xbutton.time;
event->scroll.x = xevent->xbutton.x;
event->scroll.y = xevent->xbutton.y;
event->scroll.modifier_state = xevent->xbutton.state;
event->scroll.device = manager_x11->core_pointer;
break;
default:
event->button.type = event->type = CLUTTER_BUTTON_PRESS;
event->button.time = xevent->xbutton.time;
event->button.x = xevent->xbutton.x;
event->button.y = xevent->xbutton.y;
event->button.modifier_state = xevent->xbutton.state;
event->button.button = xevent->xbutton.button;
event->button.device = manager_x11->core_pointer;
event->button.axes = NULL;
break;
}
_clutter_stage_x11_set_user_time (stage_x11, xevent->xbutton.time);
res = CLUTTER_TRANSLATE_QUEUE;
break;
case ButtonRelease:
CLUTTER_NOTE (EVENT,
"button press: win: 0x%x, coords: %d, %d, button: %d",
(unsigned int) stage_x11->xwin,
xevent->xbutton.x,
xevent->xbutton.y,
xevent->xbutton.button);
/* scroll events don't have a corresponding release */
if (xevent->xbutton.button == 4 ||
xevent->xbutton.button == 5 ||
xevent->xbutton.button == 6 ||
xevent->xbutton.button == 7)
{
res = CLUTTER_TRANSLATE_REMOVE;
break;
}
event->button.type = event->type = CLUTTER_BUTTON_RELEASE;
event->button.time = xevent->xbutton.time;
event->button.x = xevent->xbutton.x;
event->button.y = xevent->xbutton.y;
event->button.modifier_state = xevent->xbutton.state;
event->button.button = xevent->xbutton.button;
event->button.axes = NULL;
event->button.device = manager_x11->core_pointer;
res = CLUTTER_TRANSLATE_QUEUE;
break;
case MotionNotify:
CLUTTER_NOTE (EVENT,
"motion: win: 0x%x, coords: %d, %d",
(unsigned int) stage_x11->xwin,
xevent->xmotion.x,
xevent->xmotion.y);
event->motion.type = event->type = CLUTTER_MOTION;
event->motion.time = xevent->xmotion.time;
event->motion.x = xevent->xmotion.x;
event->motion.y = xevent->xmotion.y;
event->motion.modifier_state = xevent->xmotion.state;
event->motion.device = manager_x11->core_pointer;
event->motion.axes = NULL;
res = CLUTTER_TRANSLATE_QUEUE;
break;
case EnterNotify:
/* we know that we are entering the stage here */
_clutter_input_device_set_stage (manager_x11->core_pointer, stage);
CLUTTER_NOTE (EVENT, "Entering the stage");
/* Convert enter notifies to motion events because X
doesn't emit the corresponding motion notify */
event->motion.type = event->type = CLUTTER_MOTION;
event->motion.time = xevent->xcrossing.time;
event->motion.x = xevent->xcrossing.x;
event->motion.y = xevent->xcrossing.y;
event->motion.modifier_state = xevent->xcrossing.state;
event->motion.source = CLUTTER_ACTOR (stage);
event->motion.device = manager_x11->core_pointer;
res = CLUTTER_TRANSLATE_QUEUE;
break;
case LeaveNotify:
if (manager_x11->core_pointer->stage == NULL)
{
CLUTTER_NOTE (EVENT, "Discarding LeaveNotify for "
"ButtonRelease event off-stage");
res = CLUTTER_TRANSLATE_REMOVE;
break;
}
/* we know that we are leaving the stage here */
_clutter_input_device_set_stage (manager_x11->core_pointer, NULL);
CLUTTER_NOTE (EVENT, "Leaving the stage (time:%u)",
event->crossing.time);
event->crossing.type = CLUTTER_LEAVE;
event->crossing.time = xevent->xcrossing.time;
event->crossing.x = xevent->xcrossing.x;
event->crossing.y = xevent->xcrossing.y;
event->crossing.source = CLUTTER_ACTOR (stage);
event->crossing.device = manager_x11->core_pointer;
res = CLUTTER_TRANSLATE_QUEUE;
break;
default:
break;
}
return res;
}
static void
clutter_event_translator_iface_init (ClutterEventTranslatorIface *iface)
{
iface->translate_event = clutter_device_manager_x11_translate_event;
}
static void
clutter_device_manager_x11_constructed (GObject *gobject)
{
ClutterDeviceManagerX11 *manager_x11;
ClutterBackendX11 *backend_x11;
ClutterDeviceManager *manager;
#ifdef HAVE_XINPUT
XDeviceInfo *x_devices = NULL;
int i, n_devices;
#endif /* HAVE_XINPUT */
manager = CLUTTER_DEVICE_MANAGER (gobject);
manager_x11 = CLUTTER_DEVICE_MANAGER_X11 (gobject);
g_object_get (gobject, "backend", &backend_x11, NULL);
g_assert (backend_x11 != NULL);
#ifdef HAVE_XINPUT
x_devices = XListInputDevices (backend_x11->xdpy, &n_devices);
if (n_devices == 0)
{
CLUTTER_NOTE (BACKEND, "No XInput devices found");
goto default_device;
}
for (i = 0; i < n_devices; i++)
{
XDeviceInfo *info = x_devices + i;
ClutterInputDevice *device;
CLUTTER_NOTE (BACKEND,
"Considering device %li with type %d, %d of %d",
info->id,
info->use,
i, n_devices);
device = create_device (manager_x11, backend_x11, info);
if (device != NULL)
_clutter_device_manager_add_device (manager, device);
}
XFreeDeviceList (x_devices);
default_device:
#endif /* HAVE_XINPUT */
/* fallback code in case:
*
* - we do not have XInput support compiled in
* - we do not have the XInput extension
*
* we register two default devices, one for the pointer
* and one for the keyboard. this block must also be
* executed for the XInput support because XI does not
* cover core devices
*/
manager_x11->core_pointer =
g_object_new (CLUTTER_TYPE_INPUT_DEVICE,
"name", "Core Pointer",
"has-cursor", TRUE,
"device-type", CLUTTER_POINTER_DEVICE,
"device-manager", manager_x11,
"device-mode", CLUTTER_INPUT_MODE_MASTER,
"backend", backend_x11,
NULL);
CLUTTER_NOTE (BACKEND, "Added core pointer device");
manager_x11->core_keyboard =
g_object_new (CLUTTER_TYPE_INPUT_DEVICE,
"name", "Core Keyboard",
"has-cursor", FALSE,
"device-type", CLUTTER_KEYBOARD_DEVICE,
"device-manager", manager_x11,
"device-mode", CLUTTER_INPUT_MODE_MASTER,
"backend", backend_x11,
NULL);
CLUTTER_NOTE (BACKEND, "Added core keyboard device");
/* associate core devices */
_clutter_input_device_set_associated_device (manager_x11->core_pointer,
manager_x11->core_keyboard);
_clutter_input_device_set_associated_device (manager_x11->core_keyboard,
manager_x11->core_pointer);
if (G_OBJECT_CLASS (clutter_device_manager_x11_parent_class)->constructed)
G_OBJECT_CLASS (clutter_device_manager_x11_parent_class)->constructed (gobject);
}
static void
clutter_device_manager_x11_add_device (ClutterDeviceManager *manager,
ClutterInputDevice *device)
{
ClutterDeviceManagerX11 *manager_x11 = CLUTTER_DEVICE_MANAGER_X11 (manager);
manager_x11->devices = g_slist_prepend (manager_x11->devices, device);
g_hash_table_replace (manager_x11->devices_by_id,
GINT_TO_POINTER (device->id),
device);
/* blow the cache */
g_slist_free (manager_x11->all_devices);
manager_x11->all_devices = NULL;
}
static void
clutter_device_manager_x11_remove_device (ClutterDeviceManager *manager,
ClutterInputDevice *device)
{
ClutterDeviceManagerX11 *manager_x11 = CLUTTER_DEVICE_MANAGER_X11 (manager);
g_hash_table_remove (manager_x11->devices_by_id,
GINT_TO_POINTER (device->id));
manager_x11->devices = g_slist_remove (manager_x11->devices, device);
/* blow the cache */
g_slist_free (manager_x11->all_devices);
manager_x11->all_devices = NULL;
}
static const GSList *
clutter_device_manager_x11_get_devices (ClutterDeviceManager *manager)
{
ClutterDeviceManagerX11 *manager_x11 = CLUTTER_DEVICE_MANAGER_X11 (manager);
/* cache the devices list so that we can keep the core pointer
* and keyboard outside of the ManagerX11:devices list
*/
if (manager_x11->all_devices == NULL)
{
GSList *all_devices = manager_x11->devices;
all_devices = g_slist_prepend (all_devices, manager_x11->core_keyboard);
all_devices = g_slist_prepend (all_devices, manager_x11->core_pointer);
manager_x11->all_devices = all_devices;
}
return CLUTTER_DEVICE_MANAGER_X11 (manager)->all_devices;
}
static ClutterInputDevice *
clutter_device_manager_x11_get_core_device (ClutterDeviceManager *manager,
ClutterInputDeviceType type)
{
ClutterDeviceManagerX11 *manager_x11;
manager_x11 = CLUTTER_DEVICE_MANAGER_X11 (manager);
switch (type)
{
case CLUTTER_POINTER_DEVICE:
return manager_x11->core_pointer;
case CLUTTER_KEYBOARD_DEVICE:
return manager_x11->core_keyboard;
default:
return NULL;
}
return NULL;
}
static ClutterInputDevice *
clutter_device_manager_x11_get_device (ClutterDeviceManager *manager,
gint id)
{
ClutterDeviceManagerX11 *manager_x11 = CLUTTER_DEVICE_MANAGER_X11 (manager);
return g_hash_table_lookup (manager_x11->devices_by_id,
GINT_TO_POINTER (id));
}
static void
clutter_device_manager_x11_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ClutterDeviceManagerX11 *manager_x11 = CLUTTER_DEVICE_MANAGER_X11 (gobject);
switch (prop_id)
{
case PROP_EVENT_BASE:
manager_x11->xi_event_base = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
clutter_device_manager_x11_class_init (ClutterDeviceManagerX11Class *klass)
{
ClutterDeviceManagerClass *manager_class;
GObjectClass *gobject_class;
obj_props[PROP_EVENT_BASE] =
g_param_spec_int ("event-base",
"Event Base",
"The first XI event",
-1, G_MAXINT,
-1,
CLUTTER_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->constructed = clutter_device_manager_x11_constructed;
gobject_class->set_property = clutter_device_manager_x11_set_property;
g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
manager_class = CLUTTER_DEVICE_MANAGER_CLASS (klass);
manager_class->add_device = clutter_device_manager_x11_add_device;
manager_class->remove_device = clutter_device_manager_x11_remove_device;
manager_class->get_devices = clutter_device_manager_x11_get_devices;
manager_class->get_core_device = clutter_device_manager_x11_get_core_device;
manager_class->get_device = clutter_device_manager_x11_get_device;
}
static void
clutter_device_manager_x11_init (ClutterDeviceManagerX11 *self)
{
self->devices_by_id = g_hash_table_new (NULL, NULL);
}