From fa3f6fa0ed8d32ac3dbcd5332d0f38fe09dc871b Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Fri, 11 Mar 2011 21:22:25 +0100 Subject: [PATCH] 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 | 4 + gdk/gdkwindow.c | 376 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 366 insertions(+), 14 deletions(-) diff --git a/gdk/gdkinternals.h b/gdk/gdkinternals.h index 817b260779..5f991a5bc2 100644 --- a/gdk/gdkinternals.h +++ b/gdk/gdkinternals.h @@ -249,6 +249,10 @@ struct _GdkWindow gulong device_added_handler_id; gulong device_changed_handler_id; + /* 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 c6a214701e..d2634ae02e 100644 --- a/gdk/gdkwindow.c +++ b/gdk/gdkwindow.c @@ -213,6 +213,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); @@ -567,6 +572,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); @@ -8071,7 +8079,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); @@ -8258,6 +8269,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, @@ -9102,6 +9186,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, @@ -9293,11 +9498,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); @@ -9316,9 +9527,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); + } } } @@ -9335,7 +9563,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; @@ -9349,6 +9578,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; @@ -9361,6 +9591,7 @@ proxy_button_event (GdkEvent *source_event, toplevel_window = convert_native_coords_to_toplevel (event_window, toplevel_x, toplevel_y, &toplevel_x, &toplevel_y); + *handle_ungrab = TRUE; if (type == GDK_BUTTON_PRESS && !source_event->any.send_event && @@ -9404,7 +9635,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; @@ -9437,7 +9681,7 @@ proxy_button_event (GdkEvent *source_event, if (type == GDK_BUTTON_PRESS) _gdk_event_button_generate (display, event); - return TRUE; + break; case GDK_SCROLL: event->scroll.direction = source_event->scroll.direction; @@ -9449,12 +9693,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 */ } @@ -9543,7 +9814,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); @@ -9686,9 +9957,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) { @@ -10929,6 +11202,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 @@ -10947,6 +11284,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; @@ -10957,7 +11299,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, @@ -10971,6 +11315,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); }