/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright © 2009, 2010, 2011 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-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
/* 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);
#define clutter_device_manager_x11_get_type _clutter_device_manager_x11_get_type
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;
ClutterInputDeviceX11 *device_x11;
guint n_keys;
device_x11 = CLUTTER_INPUT_DEVICE_X11 (device);
n_keys = xk_info->max_keycode - xk_info->min_keycode + 1;
_clutter_input_device_set_n_keys (device, n_keys);
device_x11->min_keycode = xk_info->min_keycode;
device_x11->max_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,
"enabled", FALSE,
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;
clutter_event_set_device (event, 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.axes = NULL;
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.axes = NULL;
break;
}
clutter_event_set_device (event, manager_x11->core_pointer);
_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;
clutter_event_set_device (event, 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.axes = NULL;
clutter_event_set_device (event, manager_x11->core_pointer);
res = CLUTTER_TRANSLATE_QUEUE;
break;
case EnterNotify:
CLUTTER_NOTE (EVENT, "Entering the stage (time:%u)",
(unsigned int) xevent->xcrossing.time);
event->crossing.type = CLUTTER_ENTER;
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.related = NULL;
clutter_event_set_device (event, manager_x11->core_pointer);
_clutter_stage_add_device (stage, 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_NOTE (EVENT, "Leaving the stage (time:%u)",
(unsigned int) xevent->xcrossing.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.related = NULL;
clutter_event_set_device (event, manager_x11->core_pointer);
_clutter_stage_remove_device (stage, 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,
"enabled", TRUE,
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,
"enabled", TRUE,
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);
}