438 lines
12 KiB
C
438 lines
12 KiB
C
/*
|
|
* Copyright (C) 2012 One laptop per child
|
|
*
|
|
* 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, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
* Author: Carlos Garnacho <carlos@lanedo.com>
|
|
*/
|
|
|
|
#include <gdk/gdkx.h>
|
|
#include <X11/extensions/XInput2.h>
|
|
#include "sugar-gesture-grabber.h"
|
|
|
|
typedef struct _SugarGestureGrabberPriv SugarGestureGrabberPriv;
|
|
typedef struct _ControllerData ControllerData;
|
|
typedef struct _TouchData TouchData;
|
|
|
|
struct _TouchData
|
|
{
|
|
GdkDevice *device;
|
|
GdkEventSequence *sequence;
|
|
gboolean consumed;
|
|
};
|
|
|
|
struct _ControllerData
|
|
{
|
|
SugarEventController *controller;
|
|
GdkRectangle rect;
|
|
};
|
|
|
|
struct _SugarGestureGrabberPriv
|
|
{
|
|
GdkWindow *root_window;
|
|
GArray *controllers;
|
|
GArray *touches;
|
|
guint cancel_timeout_id;
|
|
};
|
|
|
|
G_DEFINE_TYPE (SugarGestureGrabber, sugar_gesture_grabber, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
_sugar_gesture_grabber_notify_touch (SugarGestureGrabber *grabber,
|
|
GdkDevice *device,
|
|
GdkEventSequence *sequence,
|
|
gboolean handled)
|
|
{
|
|
SugarGestureGrabberPriv *priv = grabber->_priv;
|
|
GdkDisplay *display;
|
|
guint i;
|
|
|
|
display = gdk_window_get_display (priv->root_window);
|
|
|
|
for (i = 0; i < priv->touches->len; i++) {
|
|
TouchData *data;
|
|
|
|
data = &g_array_index (priv->touches, TouchData, i);
|
|
|
|
if (device && data->device != device)
|
|
continue;
|
|
|
|
if (sequence && data->sequence != sequence)
|
|
continue;
|
|
|
|
if (data->consumed)
|
|
continue;
|
|
|
|
gdk_error_trap_push ();
|
|
XIAllowTouchEvents (gdk_x11_display_get_xdisplay (display),
|
|
gdk_x11_device_get_id (data->device),
|
|
GPOINTER_TO_INT (data->sequence),
|
|
gdk_x11_window_get_xid (priv->root_window),
|
|
(handled) ? XIAcceptTouch : XIRejectTouch);
|
|
|
|
gdk_error_trap_pop_ignored ();
|
|
data->consumed = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_sugar_gesture_grabber_add_touch (SugarGestureGrabber *grabber,
|
|
GdkDevice *device,
|
|
GdkEventSequence *sequence)
|
|
{
|
|
SugarGestureGrabberPriv *priv = grabber->_priv;
|
|
TouchData data;
|
|
|
|
data.device = device;
|
|
data.sequence = sequence;
|
|
data.consumed = FALSE;
|
|
g_array_append_val (priv->touches, data);
|
|
}
|
|
|
|
static void
|
|
_sugar_gesture_grabber_remove_touch (SugarGestureGrabber *grabber,
|
|
GdkDevice *device,
|
|
GdkEventSequence *sequence)
|
|
{
|
|
SugarGestureGrabberPriv *priv = grabber->_priv;
|
|
guint i;
|
|
|
|
for (i = 0; i < priv->touches->len; i++) {
|
|
TouchData *data;
|
|
|
|
data = &g_array_index (priv->touches, TouchData, i);
|
|
|
|
if (data->device == device &&
|
|
data->sequence == sequence) {
|
|
g_array_remove_index_fast (priv->touches, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
_sugar_gesture_grabber_cancel_timeout (SugarGestureGrabber *grabber)
|
|
{
|
|
SugarGestureGrabberPriv *priv = grabber->_priv;
|
|
|
|
_sugar_gesture_grabber_notify_touch (grabber, NULL, NULL, FALSE);
|
|
priv->cancel_timeout_id = 0;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
sugar_gesture_grabber_finalize (GObject *object)
|
|
{
|
|
SugarGestureGrabberPriv *priv = SUGAR_GESTURE_GRABBER (object)->_priv;
|
|
guint i;
|
|
|
|
if (priv->cancel_timeout_id) {
|
|
g_source_remove (priv->cancel_timeout_id);
|
|
priv->cancel_timeout_id = 0;
|
|
}
|
|
|
|
_sugar_gesture_grabber_notify_touch (SUGAR_GESTURE_GRABBER (object),
|
|
NULL, NULL, FALSE);
|
|
|
|
for (i = 0; i < priv->controllers->len; i++) {
|
|
ControllerData *data;
|
|
|
|
data = &g_array_index (priv->controllers, ControllerData, i);
|
|
g_object_unref (data->controller);
|
|
}
|
|
|
|
g_array_free (priv->controllers, TRUE);
|
|
g_array_free (priv->touches, TRUE);
|
|
|
|
G_OBJECT_CLASS (sugar_gesture_grabber_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
sugar_gesture_grabber_class_init (SugarGestureGrabberClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = sugar_gesture_grabber_finalize;
|
|
|
|
g_type_class_add_private (klass, sizeof (SugarGestureGrabberPriv));
|
|
}
|
|
|
|
static void
|
|
_grab_touch_events (GdkWindow *window)
|
|
{
|
|
XIGrabModifiers mods = { 1 };
|
|
unsigned char mask[4] = { 0 };
|
|
GdkDisplay *display;
|
|
XIEventMask evmask;
|
|
|
|
XISetMask (mask, XI_TouchBegin);
|
|
XISetMask (mask, XI_TouchUpdate);
|
|
XISetMask (mask, XI_TouchEnd);
|
|
|
|
evmask.deviceid = XIAllMasterDevices;
|
|
evmask.mask_len = sizeof (mask);
|
|
evmask.mask = mask;
|
|
|
|
mods.modifiers = XIAnyModifier;
|
|
display = gdk_window_get_display (window);
|
|
|
|
XIGrabTouchBegin (gdk_x11_display_get_xdisplay (display),
|
|
XIAllMasterDevices,
|
|
gdk_x11_window_get_xid (window),
|
|
XINoOwnerEvents, &evmask, 1, &mods);
|
|
}
|
|
|
|
static GdkWindow *
|
|
_get_default_root_window (void)
|
|
{
|
|
GdkDisplay *display;
|
|
GdkScreen *screen;
|
|
|
|
display = gdk_display_get_default ();
|
|
screen = gdk_display_get_default_screen (display);
|
|
|
|
return gdk_screen_get_root_window (screen);
|
|
}
|
|
|
|
static gboolean
|
|
_sugar_gesture_grabber_run_controllers (SugarGestureGrabber *grabber,
|
|
GdkEvent *event)
|
|
{
|
|
SugarGestureGrabberPriv *priv = grabber->_priv;
|
|
gboolean handled = FALSE;
|
|
guint i;
|
|
|
|
for (i = 0; i < priv->controllers->len; i++) {
|
|
ControllerData *data;
|
|
|
|
data = &g_array_index (priv->controllers, ControllerData, i);
|
|
|
|
if (event->type == GDK_TOUCH_BEGIN &&
|
|
(event->touch.x_root < data->rect.x ||
|
|
event->touch.x_root > data->rect.x + data->rect.width ||
|
|
event->touch.y_root < data->rect.y ||
|
|
event->touch.y_root > data->rect.y + data->rect.height))
|
|
continue;
|
|
|
|
handled = sugar_event_controller_handle_event (data->controller,
|
|
event);
|
|
|
|
if (handled) {
|
|
guint state;
|
|
|
|
state = sugar_event_controller_get_state (data->controller);
|
|
|
|
if (state == SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED) {
|
|
_sugar_gesture_grabber_notify_touch (grabber,
|
|
event->touch.device,
|
|
event->touch.sequence,
|
|
TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
static GdkFilterReturn
|
|
filter_function (GdkXEvent *xevent,
|
|
GdkEvent *gdkevent,
|
|
gpointer user_data)
|
|
{
|
|
XGenericEventCookie *xge = xevent;
|
|
GdkDeviceManager *device_manager;
|
|
SugarGestureGrabber *grabber;
|
|
SugarGestureGrabberPriv *priv;
|
|
gboolean handled = FALSE;
|
|
GdkDevice *device;
|
|
XIDeviceEvent *ev;
|
|
GdkDisplay *display;
|
|
GdkEvent *event;
|
|
|
|
if (xge->type != GenericEvent)
|
|
return GDK_FILTER_CONTINUE;
|
|
|
|
grabber = user_data;
|
|
priv = grabber->_priv;
|
|
|
|
display = gdk_window_get_display (priv->root_window);
|
|
device_manager = gdk_display_get_device_manager (display);
|
|
ev = (XIDeviceEvent *) xge->data;
|
|
|
|
switch (ev->evtype) {
|
|
case XI_TouchBegin:
|
|
event = gdk_event_new (GDK_TOUCH_BEGIN);
|
|
break;
|
|
case XI_TouchEnd:
|
|
event = gdk_event_new (GDK_TOUCH_END);
|
|
break;
|
|
case XI_TouchUpdate:
|
|
event = gdk_event_new (GDK_TOUCH_UPDATE);
|
|
break;
|
|
default:
|
|
return GDK_FILTER_CONTINUE;
|
|
}
|
|
|
|
if (ev->event != gdk_x11_window_get_xid (priv->root_window))
|
|
return GDK_FILTER_CONTINUE;
|
|
|
|
event->touch.window = g_object_ref (priv->root_window);
|
|
event->touch.time = ev->time;
|
|
event->touch.x = ev->event_x;
|
|
event->touch.y = ev->event_y;
|
|
event->touch.x_root = ev->root_x;
|
|
event->touch.y_root = ev->root_y;
|
|
event->touch.sequence = GINT_TO_POINTER (ev->detail);
|
|
event->touch.emulating_pointer = (ev->flags & XITouchEmulatingPointer);
|
|
|
|
device = gdk_x11_device_manager_lookup (device_manager, ev->deviceid);
|
|
gdk_event_set_device (event, device);
|
|
|
|
device = gdk_x11_device_manager_lookup (device_manager, ev->sourceid);
|
|
gdk_event_set_source_device (event, device);
|
|
|
|
handled = _sugar_gesture_grabber_run_controllers (grabber, event);
|
|
|
|
if (!handled) {
|
|
gdk_error_trap_push ();
|
|
XIAllowTouchEvents (gdk_x11_display_get_xdisplay (display),
|
|
ev->deviceid, ev->detail,
|
|
gdk_x11_window_get_xid (priv->root_window),
|
|
XIRejectTouch);
|
|
gdk_error_trap_pop_ignored ();
|
|
} else if (event->type == GDK_TOUCH_BEGIN) {
|
|
_sugar_gesture_grabber_add_touch (grabber,
|
|
event->touch.device,
|
|
event->touch.sequence);
|
|
} else if (event->type == GDK_TOUCH_END) {
|
|
_sugar_gesture_grabber_notify_touch (grabber,
|
|
event->touch.device,
|
|
event->touch.sequence,
|
|
FALSE);
|
|
_sugar_gesture_grabber_remove_touch (grabber,
|
|
event->touch.device,
|
|
event->touch.sequence);
|
|
}
|
|
|
|
if (handled) {
|
|
if (priv->cancel_timeout_id)
|
|
g_source_remove (priv->cancel_timeout_id);
|
|
|
|
priv->cancel_timeout_id =
|
|
gdk_threads_add_timeout (150,
|
|
(GSourceFunc) _sugar_gesture_grabber_cancel_timeout,
|
|
grabber);
|
|
}
|
|
|
|
gdk_event_free (event);
|
|
|
|
return GDK_FILTER_REMOVE;
|
|
}
|
|
|
|
static void
|
|
sugar_gesture_grabber_init (SugarGestureGrabber *grabber)
|
|
{
|
|
SugarGestureGrabberPriv *priv;
|
|
|
|
grabber->_priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (grabber,
|
|
SUGAR_TYPE_GESTURE_GRABBER,
|
|
SugarGestureGrabberPriv);
|
|
priv->root_window = _get_default_root_window ();
|
|
_grab_touch_events (priv->root_window);
|
|
gdk_window_add_filter (NULL, filter_function, grabber);
|
|
|
|
priv->touches = g_array_new (FALSE, FALSE, sizeof (TouchData));
|
|
priv->controllers = g_array_new (FALSE, FALSE, sizeof (ControllerData));
|
|
}
|
|
|
|
SugarGestureGrabber *
|
|
sugar_gesture_grabber_new (void)
|
|
{
|
|
return g_object_new (SUGAR_TYPE_GESTURE_GRABBER, NULL);
|
|
}
|
|
|
|
static ControllerData *
|
|
_sugar_gesture_grabber_find_controller (SugarGestureGrabber *grabber,
|
|
SugarEventController *controller,
|
|
gint *pos)
|
|
{
|
|
SugarGestureGrabberPriv *priv;
|
|
guint i;
|
|
|
|
priv = grabber->_priv;
|
|
|
|
for (i = 0; i < priv->controllers->len; i++) {
|
|
ControllerData *data;
|
|
|
|
data = &g_array_index (priv->controllers, ControllerData, i);
|
|
|
|
if (data->controller == controller) {
|
|
if (pos)
|
|
*pos = i;
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
sugar_gesture_grabber_add (SugarGestureGrabber *grabber,
|
|
SugarEventController *controller,
|
|
const GdkRectangle *rect)
|
|
{
|
|
SugarGestureGrabberPriv *priv;
|
|
ControllerData data;
|
|
|
|
g_return_if_fail (SUGAR_IS_GESTURE_GRABBER (grabber));
|
|
g_return_if_fail (SUGAR_IS_EVENT_CONTROLLER (controller));
|
|
|
|
if (_sugar_gesture_grabber_find_controller (grabber, controller, NULL)) {
|
|
g_warning ("Controller is already on the gesture grabber"
|
|
" list. Controllers can only be added once.");
|
|
return;
|
|
}
|
|
|
|
priv = grabber->_priv;
|
|
|
|
data.controller = g_object_ref (controller);
|
|
data.rect = *rect;
|
|
g_array_append_val (priv->controllers, data);
|
|
}
|
|
|
|
void
|
|
sugar_gesture_grabber_remove (SugarGestureGrabber *grabber,
|
|
SugarEventController *controller)
|
|
{
|
|
SugarGestureGrabberPriv *priv;
|
|
ControllerData *data;
|
|
gint pos;
|
|
|
|
g_return_if_fail (SUGAR_IS_GESTURE_GRABBER (grabber));
|
|
g_return_if_fail (SUGAR_IS_EVENT_CONTROLLER (controller));
|
|
|
|
priv = grabber->_priv;
|
|
data = _sugar_gesture_grabber_find_controller (grabber, controller, &pos);
|
|
|
|
if (data) {
|
|
g_array_remove_index_fast (priv->controllers, pos);
|
|
sugar_event_controller_reset (data->controller);
|
|
g_object_unref (data->controller);
|
|
}
|
|
}
|