[gtk+/multitouch: 10/27] Add machinery to emit GdkEventMultiTouch events
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/multitouch: 10/27] Add machinery to emit GdkEventMultiTouch events
- Date: Tue, 13 Dec 2011 18:58:43 +0000 (UTC)
commit c3e92ea74241fbaabd882cc315e968408cba7086
Author: Carlos Garnacho <carlosg gnome org>
Date: Fri Mar 11 21:22:25 2011 +0100
Add machinery to emit GdkEventMultiTouch events
These events are created from GDK_TOUCH_MOTION/PRESS/RELEASE
events, if the touch ID generating the event is within a
touch cluster, that event is stored and not pushed to the queue,
so a touch ID can only emit GDK_TOUCH_* or GDK_MULTITOUCH_* events
at the same time.
gdk/gdkinternals.h | 5 +
gdk/gdkwindow.c | 377 ++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 368 insertions(+), 14 deletions(-)
---
diff --git a/gdk/gdkinternals.h b/gdk/gdkinternals.h
index 124160f..678dff9 100644
--- a/gdk/gdkinternals.h
+++ b/gdk/gdkinternals.h
@@ -260,6 +260,11 @@ struct _GdkWindow
gulong device_changed_handler_id;
guint num_offscreen_children;
+
+ /* Store of latest per-touch events, keys are
+ * GdkDevices, values are hashtables of touchID/info
+ */
+ GHashTable *touch_event_tracker;
GList *touch_clusters;
};
diff --git a/gdk/gdkwindow.c b/gdk/gdkwindow.c
index 85e2e9b..860af2f 100644
--- a/gdk/gdkwindow.c
+++ b/gdk/gdkwindow.c
@@ -212,6 +212,11 @@ typedef struct {
int dx, dy; /* The amount that the source was moved to reach dest_region */
} GdkWindowRegionMove;
+typedef struct {
+ GdkEvent *event; /* latest event for touch */
+ GdkTouchCluster *cluster; /* touch cluster the ID currently pertains to */
+} TouchEventInfo;
+
/* Global info */
static void gdk_window_drop_cairo_surface (GdkWindow *private);
@@ -562,6 +567,9 @@ gdk_window_finalize (GObject *object)
if (window->devices_inside)
g_list_free (window->devices_inside);
+ if (window->touch_event_tracker)
+ g_hash_table_destroy (window->touch_event_tracker);
+
g_list_foreach (window->touch_clusters, (GFunc) g_object_unref, NULL);
g_list_free (window->touch_clusters);
@@ -8138,7 +8146,10 @@ static const guint type_masks[] = {
0, /* GDK_DAMAGE = 36 */
GDK_TOUCH_MASK, /* GDK_TOUCH_MOTION = 37 */
GDK_TOUCH_MASK, /* GDK_TOUCH_PRESS = 38 */
- GDK_TOUCH_MASK /* GDK_TOUCH_RELEASE = 39 */
+ GDK_TOUCH_MASK, /* GDK_TOUCH_RELEASE = 39 */
+ GDK_TOUCH_MASK, /* GDK_MULTITOUCH_ADDED = 40 */
+ GDK_TOUCH_MASK, /* GDK_MULTITOUCH_REMOVED = 41 */
+ GDK_TOUCH_MASK /* GDK_MULTITOUCH_UPDATED = 42 */
};
G_STATIC_ASSERT (G_N_ELEMENTS (type_masks) == GDK_EVENT_LAST);
@@ -8325,6 +8336,79 @@ _gdk_make_event (GdkWindow *window,
return event;
}
+GdkEvent *
+gdk_make_multitouch_event (GdkWindow *window,
+ GdkEventType type,
+ GdkTouchCluster *cluster,
+ GdkDevice *device,
+ guint touch_id,
+ GdkEvent *event_in_queue)
+{
+ GdkEvent *mt_event, *event = NULL;
+ gint i, n_touches, n_updated = -1;
+ GdkEventMotion **subevents;
+ TouchEventInfo *info;
+ GHashTable *by_touch;
+ GList *touches;
+
+ if (!window->touch_event_tracker)
+ return NULL;
+
+ by_touch = g_hash_table_lookup (window->touch_event_tracker, device);
+
+ if (by_touch)
+ {
+ info = g_hash_table_lookup (by_touch, GUINT_TO_POINTER (touch_id));
+ if (info)
+ event = info->event;
+ }
+
+ if (!event)
+ {
+ g_warning ("Creating a multitouch event but no input was pre-recorded");
+ return NULL;
+ }
+
+ /* Generate multitouch event */
+ mt_event = _gdk_make_event (window, type, event_in_queue, FALSE);
+ mt_event->multitouch.time = event->motion.time;
+ mt_event->multitouch.state = event->motion.state;
+ gdk_event_set_device (mt_event, gdk_event_get_device (event));
+ gdk_event_set_source_device (mt_event, gdk_event_get_source_device (event));
+
+ mt_event->multitouch.group = cluster;
+
+ /* Fill in individual motion sub-events */
+ touches = gdk_touch_cluster_get_touches (cluster);
+ n_touches = g_list_length (touches);
+ i = 0;
+
+ subevents = g_new0 (GdkEventMotion *, n_touches);
+
+ while (touches)
+ {
+ TouchEventInfo *subevent_info;
+ GdkEvent *subevent;
+
+ subevent_info = g_hash_table_lookup (by_touch, touches->data);
+ subevent = gdk_event_copy (subevent_info->event);
+ subevents[i] = (GdkEventMotion *) subevent;
+
+ if (subevent->motion.touch_id == touch_id)
+ n_updated = i;
+
+ touches = touches->next;
+ i++;
+ }
+
+ mt_event->multitouch.events = subevents;
+ mt_event->multitouch.n_updated_event = n_updated;
+ mt_event->multitouch.n_events = n_touches;
+ mt_event->multitouch.updated_touch_id = touch_id;
+
+ return mt_event;
+}
+
static void
send_crossing_event (GdkDisplay *display,
GdkWindow *toplevel,
@@ -9179,6 +9263,127 @@ get_event_window (GdkDisplay *display,
return NULL;
}
+static TouchEventInfo *
+touch_event_info_new (void)
+{
+ return g_slice_new0 (TouchEventInfo);
+}
+
+static void
+touch_event_info_free (TouchEventInfo *info)
+{
+ if (info->event)
+ gdk_event_free (info->event);
+ g_slice_free (TouchEventInfo, info);
+}
+
+static TouchEventInfo *
+touch_event_info_lookup (GdkWindow *window,
+ GdkDevice *device,
+ guint touch_id,
+ gboolean create)
+{
+ TouchEventInfo *info;
+ GHashTable *by_touch;
+
+ if (G_UNLIKELY (!window->touch_event_tracker))
+ window->touch_event_tracker = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) g_hash_table_destroy);
+
+ by_touch = g_hash_table_lookup (window->touch_event_tracker, device);
+
+ if (!by_touch)
+ {
+ by_touch = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) touch_event_info_free);
+ g_hash_table_insert (window->touch_event_tracker, device, by_touch);
+ }
+
+ info = g_hash_table_lookup (by_touch, GUINT_TO_POINTER (touch_id));
+
+ if (create && !info)
+ {
+ info = touch_event_info_new ();
+ g_hash_table_insert (by_touch, GUINT_TO_POINTER (touch_id), info);
+ }
+
+ return info;
+}
+
+/* Stores touch event for posterior multitouch
+ * events generation, takes ownership of event
+ */
+static void
+store_touch_event (GdkWindow *window,
+ GdkEvent *event,
+ guint touch_id)
+{
+ GdkDevice *device, *source_device;
+ TouchEventInfo *info;
+
+ if (event->type != GDK_TOUCH_PRESS &&
+ event->type != GDK_TOUCH_RELEASE &&
+ event->type != GDK_TOUCH_MOTION)
+ return;
+
+ device = gdk_event_get_device (event);
+ source_device = gdk_event_get_source_device (event);
+
+ if (event->type == GDK_TOUCH_PRESS ||
+ event->type == GDK_TOUCH_RELEASE)
+ {
+ GdkEvent *new_event;
+
+ /* Create GDK_TOUCH_MOTION event from the available data */
+ new_event = gdk_event_new (GDK_TOUCH_MOTION);
+
+ if (event->button.window)
+ new_event->motion.window = g_object_ref (event->button.window);
+
+ new_event->motion.send_event = event->button.send_event;
+ new_event->motion.time = event->button.time;
+ new_event->motion.x = event->button.x;
+ new_event->motion.y = event->button.y;
+ new_event->motion.x_root = event->button.x_root;
+ new_event->motion.y_root = event->button.y_root;
+ new_event->motion.state = event->button.state;
+ new_event->motion.touch_id = event->button.touch_id;
+ new_event->motion.is_hint = FALSE;
+
+ gdk_event_set_device (new_event, device);
+ gdk_event_set_source_device (new_event, source_device);
+
+ new_event->motion.axes = g_memdup (event->button.axes,
+ sizeof (gdouble) * gdk_device_get_n_axes (device));
+
+ gdk_event_free (event);
+ event = new_event;
+ }
+
+ info = touch_event_info_lookup (window, source_device, touch_id, TRUE);
+ info->event = event;
+}
+
+static GdkTouchCluster *
+_gdk_window_lookup_touch_cluster (GdkWindow *window,
+ GdkEvent *event)
+{
+ TouchEventInfo *info;
+ GdkDevice *device;
+ guint touch_id;
+
+ if (!gdk_event_get_touch_id (event, &touch_id))
+ return NULL;
+
+ device = gdk_event_get_source_device (event);
+ info = touch_event_info_lookup (window, device, touch_id, FALSE);
+
+ if (!info)
+ return NULL;
+
+ return info->cluster;
+}
+
static gboolean
proxy_pointer_event (GdkDisplay *display,
GdkEvent *source_event,
@@ -9390,11 +9595,17 @@ proxy_pointer_event (GdkDisplay *display,
if (!display->ignore_core_events)
{
+ GdkTouchCluster *cluster = NULL;
GdkEventType event_type;
guint touch_id;
- gdk_event_get_touch_id (source_event, &touch_id);
- event_type = source_event->type;
+ if (gdk_event_get_touch_id (source_event, &touch_id))
+ cluster = _gdk_window_lookup_touch_cluster (event_win, source_event);
+
+ if (cluster)
+ event_type = GDK_TOUCH_MOTION;
+ else
+ event_type = source_event->type;
event = gdk_event_new (event_type);
event->any.window = g_object_ref (event_win);
@@ -9413,9 +9624,26 @@ proxy_pointer_event (GdkDisplay *display,
event->motion.touch_id = touch_id;
gdk_event_set_source_device (event, source_device);
- /* Just insert the event */
- _gdk_event_queue_insert_after (gdk_window_get_display (event_win),
- source_event, event);
+ if (cluster)
+ {
+ store_touch_event (event_win, event, touch_id);
+
+ /* Event is not added to the queue, instead it's stored
+ * in order to generate a multitouch event for the touch
+ * ID's cluster.
+ */
+ gdk_make_multitouch_event (event_win, GDK_MULTITOUCH_UPDATED,
+ cluster, source_device, touch_id,
+ source_event);
+ }
+ else
+ {
+ store_touch_event (event_win, gdk_event_copy (event), touch_id);
+
+ /* Just insert the event */
+ _gdk_event_queue_insert_after (gdk_window_get_display (event_win),
+ source_event, event);
+ }
}
}
@@ -9432,7 +9660,8 @@ proxy_pointer_event (GdkDisplay *display,
static gboolean
proxy_button_event (GdkEvent *source_event,
- gulong serial)
+ gulong serial,
+ gboolean *handle_ungrab)
{
GdkWindow *toplevel_window, *event_window;
GdkWindow *event_win;
@@ -9447,6 +9676,7 @@ proxy_button_event (GdkEvent *source_event,
GdkDisplay *display;
GdkWindow *w;
GdkDevice *device, *source_device;
+ GdkEventMask evmask;
type = source_event->any.type;
event_window = source_event->any.window;
@@ -9459,7 +9689,9 @@ proxy_button_event (GdkEvent *source_event,
toplevel_window = convert_native_coords_to_toplevel (event_window,
toplevel_x, toplevel_y,
&toplevel_x, &toplevel_y);
+
pointer_info = _gdk_display_get_pointer_info (display, device);
+ *handle_ungrab = TRUE;
if (type == GDK_BUTTON_PRESS &&
!source_event->any.send_event &&
@@ -9503,7 +9735,20 @@ proxy_button_event (GdkEvent *source_event,
device,
pointer_window,
type, state,
- NULL, serial);
+ &evmask, serial);
+
+ /* Block button press/release events coming from touch devices, only if
+ * the event mask allows both normal and touch events, since
+ * the latter will come right after.
+ */
+ if ((evmask & GDK_TOUCH_MASK) &&
+ gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH &&
+ source_event->type != GDK_TOUCH_PRESS &&
+ source_event->type != GDK_TOUCH_RELEASE)
+ {
+ *handle_ungrab = FALSE;
+ return TRUE;
+ }
if (event_win == NULL || display->ignore_core_events)
return TRUE;
@@ -9576,7 +9821,7 @@ proxy_button_event (GdkEvent *source_event,
state, time_, NULL,
serial, FALSE);
}
- return TRUE;
+ break;
case GDK_SCROLL:
event->scroll.direction = source_event->scroll.direction;
@@ -9588,12 +9833,39 @@ proxy_button_event (GdkEvent *source_event,
event->scroll.state = state;
event->scroll.device = source_event->scroll.device;
gdk_event_set_source_device (event, source_device);
- return TRUE;
+ break;
default:
return FALSE;
}
+ if (type == GDK_TOUCH_RELEASE)
+ {
+ GdkTouchCluster *cluster;
+ GHashTable *by_touch;
+ guint touch_id;
+
+ touch_id = source_event->button.touch_id;
+
+ /* Remove the touch ID from any touch cluster it could pertain to */
+ cluster = _gdk_window_lookup_touch_cluster (event_win, source_event);
+
+ if (cluster)
+ gdk_touch_cluster_remove_touch (cluster, touch_id);
+
+ /* Remove in any case the touch ID from the event tracker */
+ by_touch = g_hash_table_lookup (event_win->touch_event_tracker, source_device);
+
+ if (by_touch)
+ g_hash_table_remove (by_touch, GUINT_TO_POINTER (touch_id));
+
+ /* Only remove the grab if it was the last pending touch on the window */
+ *handle_ungrab = (g_hash_table_size (by_touch) == 0);
+ }
+ else if (type == GDK_TOUCH_PRESS)
+ store_touch_event (event_win, gdk_event_copy (event),
+ event->button.touch_id);
+
return TRUE; /* Always unlink original, we want to obey the emulated event mask */
}
@@ -9682,7 +9954,7 @@ _gdk_windowing_got_event (GdkDisplay *display,
GdkDeviceGrabInfo *button_release_grab;
GdkPointerWindowInfo *pointer_info;
GdkDevice *device, *source_device;
- gboolean is_toplevel;
+ gboolean is_toplevel, handle_ungrab = TRUE;
if (gdk_event_get_time (event) != GDK_CURRENT_TIME)
display->last_event_time = gdk_event_get_time (event);
@@ -9838,9 +10110,11 @@ _gdk_windowing_got_event (GdkDisplay *display,
serial);
else if (is_button_type (event->type))
unlink_event = proxy_button_event (event,
- serial);
+ serial,
+ &handle_ungrab);
- if ((event->type == GDK_BUTTON_RELEASE ||
+ if (handle_ungrab &&
+ (event->type == GDK_BUTTON_RELEASE ||
event->type == GDK_TOUCH_RELEASE) &&
!event->any.send_event)
{
@@ -11148,6 +11422,70 @@ gdk_property_delete (GdkWindow *window,
GDK_WINDOW_IMPL_GET_CLASS (window->impl)->delete_property (window, property);
}
+static void
+touch_cluster_touch_added (GdkTouchCluster *cluster,
+ guint touch_id,
+ gpointer user_data)
+{
+ GdkWindow *window;
+ TouchEventInfo *info;
+ GHashTable *by_touch;
+ GdkDevice *device;
+
+ device = gdk_touch_cluster_get_device (cluster);
+
+ if (!device)
+ return;
+
+ window = user_data;
+ by_touch = g_hash_table_lookup (window->touch_event_tracker, device);
+ g_assert (by_touch != NULL);
+
+ info = g_hash_table_lookup (by_touch, GUINT_TO_POINTER (touch_id));
+
+ if (info->cluster == cluster)
+ return;
+
+ if (info->cluster)
+ {
+ /* Remove touch from old cluster, but keep the stored data around */
+ g_hash_table_steal (by_touch, GUINT_TO_POINTER (touch_id));
+
+ gdk_touch_cluster_remove_touch (info->cluster, touch_id);
+
+ g_hash_table_insert (by_touch,
+ GUINT_TO_POINTER (touch_id),
+ info);
+ }
+
+ info->cluster = cluster;
+ gdk_make_multitouch_event (window, GDK_MULTITOUCH_ADDED,
+ cluster, device, touch_id,
+ NULL);
+}
+
+static void
+touch_cluster_touch_removed (GdkTouchCluster *cluster,
+ guint touch_id,
+ gpointer user_data)
+{
+ GdkWindow *window;
+ GdkDevice *device;
+ GHashTable *by_touch;
+
+ window = user_data;
+ device = gdk_touch_cluster_get_device (cluster);
+ by_touch = g_hash_table_lookup (window->touch_event_tracker, device);
+
+ g_assert (by_touch != NULL);
+
+ gdk_make_multitouch_event (window, GDK_MULTITOUCH_REMOVED,
+ cluster, device, touch_id,
+ NULL);
+
+ g_hash_table_remove (by_touch, GUINT_TO_POINTER (touch_id));
+}
+
/**
* gdk_window_create_touch_cluster:
* @window: a #GdkWindow
@@ -11166,6 +11504,11 @@ gdk_window_create_touch_cluster (GdkWindow *window)
g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
cluster = g_object_new (GDK_TYPE_TOUCH_CLUSTER, NULL);
+ g_signal_connect (cluster, "touch-added",
+ G_CALLBACK (touch_cluster_touch_added), window);
+ g_signal_connect (cluster, "touch-removed",
+ G_CALLBACK (touch_cluster_touch_removed), window);
+
window->touch_clusters = g_list_prepend (window->touch_clusters, cluster);
return cluster;
@@ -11176,7 +11519,9 @@ gdk_window_create_touch_cluster (GdkWindow *window)
* @window: a #GdkWindow
* @cluster: a #GdkTouchCluster from @window
*
- * Removes @cluster from @window.
+ * Removes @cluster from @window. All contained touches will be
+ * removed one by one, causing %GDK_MULTITOUCH_REMOVED events
+ * for these before destroying @cluster.
**/
void
gdk_window_remove_touch_cluster (GdkWindow *window,
@@ -11190,6 +11535,10 @@ gdk_window_remove_touch_cluster (GdkWindow *window,
return;
gdk_touch_cluster_remove_all (cluster);
+
+ g_signal_handlers_disconnect_by_func (cluster, "touch-added", window);
+ g_signal_handlers_disconnect_by_func (cluster, "touch-removed", window);
+
window->touch_clusters = g_list_remove (window->touch_clusters, cluster);
g_object_unref (cluster);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]