diff --git a/src/sugar3/Makefile.am b/src/sugar3/Makefile.am index 7ee9aa1f..82583cdd 100644 --- a/src/sugar3/Makefile.am +++ b/src/sugar3/Makefile.am @@ -47,6 +47,8 @@ libsugarext_la_SOURCES = \ gsm-xsmp.h \ sugar-grid.c \ sugar-grid.h \ + sugar-gesture-grabber.c \ + sugar-gesture-grabber.h \ sugar-key-grabber.c \ sugar-key-grabber.h \ sugar-wm.c \ diff --git a/src/sugar3/sugar-gesture-grabber.c b/src/sugar3/sugar-gesture-grabber.c new file mode 100644 index 00000000..63673a49 --- /dev/null +++ b/src/sugar3/sugar-gesture-grabber.c @@ -0,0 +1,377 @@ +/* + * 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 + */ + +#include +#include +#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; + + 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); + + 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; + } + + 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) { + XIAllowTouchEvents (gdk_x11_display_get_xdisplay (display), + ev->deviceid, ev->detail, + gdk_x11_window_get_xid (priv->root_window), + XIRejectTouch); + } 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); +} + +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)); + + priv = grabber->_priv; + + data.controller = g_object_ref (controller); + data.rect = *rect; + g_array_append_val (priv->controllers, data); +} diff --git a/src/sugar3/sugar-gesture-grabber.h b/src/sugar3/sugar-gesture-grabber.h new file mode 100644 index 00000000..2d956a33 --- /dev/null +++ b/src/sugar3/sugar-gesture-grabber.h @@ -0,0 +1,57 @@ +/* + * 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 + */ + +#ifndef __SUGAR_GESTURE_GRABBER_H__ +#define __SUGAR_GESTURE_GRABBER_H__ + +#include +#include "event-controller/sugar-event-controllers.h" + +G_BEGIN_DECLS + +typedef struct _SugarGestureGrabber SugarGestureGrabber; +typedef struct _SugarGestureGrabberClass SugarGestureGrabberClass; + +#define SUGAR_TYPE_GESTURE_GRABBER (sugar_gesture_grabber_get_type()) +#define SUGAR_GESTURE_GRABBER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_GESTURE_GRABBER, SugarGestureGrabber)) +#define SUGAR_GESTURE_GRABBER_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_GESTURE_GRABBER, SugarGestureGrabberClass)) +#define SUGAR_IS_GESTURE_GRABBER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_GESTURE_GRABBER)) +#define SUGAR_IS_GESTURE_GRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_GESTURE_GRABBER)) +#define SUGAR_GESTURE_GRABBER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_GESTURE_GRABBER, SugarGestureGrabberClass)) + +struct _SugarGestureGrabber { + GObject parent_instance; + gpointer _priv; +}; + +struct _SugarGestureGrabberClass { + GObjectClass parent_class; +}; + +GType sugar_gesture_grabber_get_type (void); +SugarGestureGrabber * sugar_gesture_grabber_new (void); +void sugar_gesture_grabber_add (SugarGestureGrabber *grabber, + SugarEventController *controller, + const GdkRectangle *rect); + +G_END_DECLS + +#endif /* __SUGAR_GESTURE_GRABBER_H__ */