From 2250865eb61f3110914e09cd50010f8e695c1aeb Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Thu, 24 Apr 2014 14:50:47 +0200 Subject: [PATCH] wayland: Implement the server side bits of wl_touch_interface Clutter touch events are translated into events being sent down the interface resource, with the exception of FRAME/CANCEL events, which are handled directly via an evdev event filter. The seat now announces invariably the WL_SEAT_CAPABILITY_TOUCH capability, this should be eventually updated as devices come and go. The creation of MetaWaylandTouchSurface structs is dynamic, attached to the lifetime of first/last touch on the client surface, and only if the surface requests the wl_touch interface. MetaWaylandTouchInfo structs are created to track individual touches, and are locked to a single MetaWaylandTouchSurface (the implicit grab surface) determined on CLUTTER_TOUCH_BEGIN. https://bugzilla.gnome.org/show_bug.cgi?id=724442 --- src/Makefile.am | 2 + src/wayland/meta-wayland-seat.c | 20 +- src/wayland/meta-wayland-seat.h | 2 + src/wayland/meta-wayland-touch.c | 548 ++++++++++++++++++++++++++++ src/wayland/meta-wayland-touch.h | 62 ++++ src/wayland/meta-wayland-types.h | 1 + src/wayland/meta-wayland-versions.h | 2 +- 7 files changed, 634 insertions(+), 3 deletions(-) create mode 100644 src/wayland/meta-wayland-touch.c create mode 100644 src/wayland/meta-wayland-touch.h diff --git a/src/Makefile.am b/src/Makefile.am index 6d2920b8f..9867be414 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -222,6 +222,8 @@ libmutter_la_SOURCES = \ wayland/meta-wayland-pointer.h \ wayland/meta-wayland-seat.c \ wayland/meta-wayland-seat.h \ + wayland/meta-wayland-touch.c \ + wayland/meta-wayland-touch.h \ wayland/meta-wayland-surface.c \ wayland/meta-wayland-surface.h \ wayland/meta-wayland-types.h \ diff --git a/src/wayland/meta-wayland-seat.c b/src/wayland/meta-wayland-seat.c index 2dedf7e93..82ceeb5cc 100644 --- a/src/wayland/meta-wayland-seat.c +++ b/src/wayland/meta-wayland-seat.c @@ -59,7 +59,10 @@ seat_get_touch (struct wl_client *client, struct wl_resource *resource, uint32_t id) { - /* Touch not supported */ + MetaWaylandSeat *seat = wl_resource_get_user_data (resource); + MetaWaylandTouch *touch = &seat->touch; + + meta_wayland_touch_create_new_resource (touch, client, resource, id); } static const struct wl_seat_interface seat_interface = { @@ -84,7 +87,8 @@ bind_seat (struct wl_client *client, wl_seat_send_capabilities (resource, WL_SEAT_CAPABILITY_POINTER | - WL_SEAT_CAPABILITY_KEYBOARD); + WL_SEAT_CAPABILITY_KEYBOARD | + WL_SEAT_CAPABILITY_TOUCH); if (version >= WL_SEAT_NAME_SINCE_VERSION) wl_seat_send_name (resource, "seat0"); @@ -101,6 +105,7 @@ meta_wayland_seat_new (struct wl_display *display) meta_wayland_pointer_init (&seat->pointer, display); meta_wayland_keyboard_init (&seat->keyboard, display); + meta_wayland_touch_init (&seat->touch, display); seat->display = display; @@ -120,6 +125,7 @@ meta_wayland_seat_free (MetaWaylandSeat *seat) { meta_wayland_pointer_release (&seat->pointer); meta_wayland_keyboard_release (&seat->keyboard); + meta_wayland_touch_release (&seat->touch); g_slice_free (MetaWaylandSeat, seat); } @@ -142,6 +148,12 @@ meta_wayland_seat_update (MetaWaylandSeat *seat, meta_wayland_keyboard_update (&seat->keyboard, (const ClutterKeyEvent *) event); break; + case CLUTTER_TOUCH_BEGIN: + case CLUTTER_TOUCH_UPDATE: + case CLUTTER_TOUCH_END: + meta_wayland_touch_update (&seat->touch, event); + break; + default: break; } @@ -163,6 +175,10 @@ meta_wayland_seat_handle_event (MetaWaylandSeat *seat, case CLUTTER_KEY_RELEASE: return meta_wayland_keyboard_handle_event (&seat->keyboard, (const ClutterKeyEvent *) event); + case CLUTTER_TOUCH_BEGIN: + case CLUTTER_TOUCH_UPDATE: + case CLUTTER_TOUCH_END: + return meta_wayland_touch_handle_event (&seat->touch, event); default: break; diff --git a/src/wayland/meta-wayland-seat.h b/src/wayland/meta-wayland-seat.h index d220174a7..e471ea1dc 100644 --- a/src/wayland/meta-wayland-seat.h +++ b/src/wayland/meta-wayland-seat.h @@ -28,6 +28,7 @@ #include "meta-wayland-types.h" #include "meta-wayland-keyboard.h" #include "meta-wayland-pointer.h" +#include "meta-wayland-touch.h" struct _MetaWaylandDataOffer { @@ -53,6 +54,7 @@ struct _MetaWaylandSeat struct wl_list data_device_resource_list; MetaWaylandPointer pointer; MetaWaylandKeyboard keyboard; + MetaWaylandTouch touch; struct wl_display *display; }; diff --git a/src/wayland/meta-wayland-touch.c b/src/wayland/meta-wayland-touch.c new file mode 100644 index 000000000..a91ee2443 --- /dev/null +++ b/src/wayland/meta-wayland-touch.c @@ -0,0 +1,548 @@ +/* + * Wayland Support + * + * Copyright (C) 2014 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Carlos Garnacho + */ + +#define _GNU_SOURCE + +#include "config.h" + +#include +#include +#include + +#include "meta-surface-actor-wayland.h" +#include "meta-wayland-private.h" + +struct _MetaWaylandTouchSurface +{ + MetaWaylandSurface *surface; + MetaWaylandTouch *touch; + struct wl_listener surface_destroy_listener; + struct wl_list resource_list; + gint touch_count; +}; + +struct _MetaWaylandTouchInfo +{ + MetaWaylandTouchSurface *touch_surface; + guint32 slot_serial; + gint32 slot; + gfloat x; + gfloat y; + guint updated : 1; +}; + +static void +move_resources (struct wl_list *destination, struct wl_list *source) +{ + wl_list_insert_list (destination, source); + wl_list_init (source); +} + +static void +move_resources_for_client (struct wl_list *destination, + struct wl_list *source, + struct wl_client *client) +{ + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe (resource, tmp, source) + { + if (wl_resource_get_client (resource) == client) + { + wl_list_remove (wl_resource_get_link (resource)); + wl_list_insert (destination, wl_resource_get_link (resource)); + } + } +} + +static void +touch_surface_free (gpointer data) +{ + MetaWaylandTouchSurface *touch_surface = data; + MetaWaylandTouch *touch = touch_surface->touch; + + move_resources (&touch->resource_list, + &touch_surface->resource_list); + wl_list_remove (&touch_surface->surface_destroy_listener.link); + g_free (touch_surface); +} + +static MetaWaylandTouchSurface * +touch_surface_increment_touch (MetaWaylandTouchSurface *surface) +{ + surface->touch_count++; + return surface; +} + +static void +touch_surface_decrement_touch (MetaWaylandTouchSurface *touch_surface) +{ + touch_surface->touch_count--; + + if (touch_surface->touch_count == 0) + { + /* Now that there are no touches on the surface, free the + * MetaWaylandTouchSurface, the memory is actually owned by + * the touch_surface->touch_surfaces hashtable, so remove the + * item from there. + */ + MetaWaylandTouch *touch = touch_surface->touch; + g_hash_table_remove (touch->touch_surfaces, touch_surface->surface); + } +} + +static void +touch_handle_surface_destroy (struct wl_listener *listener, void *data) +{ + MetaWaylandTouchSurface *touch_surface = wl_container_of (listener, touch_surface, surface_destroy_listener); + MetaWaylandSurface *surface = touch_surface->surface; + MetaWaylandTouch *touch = touch_surface->touch; + MetaWaylandTouchInfo *touch_info; + GHashTableIter iter; + + g_hash_table_iter_init (&iter, touch->touches); + + /* Destroy all touches on the surface, this indirectly drops touch_count + * on the touch_surface to 0, also freeing touch_surface and removing + * from the touch_surfaces hashtable. + */ + while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &touch_info)) + { + if (touch_info->touch_surface == touch_surface) + g_hash_table_iter_remove (&iter); + } + + /* Ensure the surface no longer exists */ + g_assert (g_hash_table_remove (touch->touch_surfaces, surface) == FALSE); +} + +static MetaWaylandTouchSurface * +touch_surface_get (MetaWaylandTouch *touch, + MetaWaylandSurface *surface) +{ + MetaWaylandTouchSurface *touch_surface; + + touch_surface = g_hash_table_lookup (touch->touch_surfaces, surface); + + if (touch_surface) + return touch_surface_increment_touch (touch_surface); + + /* Create a new one for this surface */ + touch_surface = g_new0 (MetaWaylandTouchSurface, 1); + touch_surface->touch = touch; + touch_surface->surface = surface; + touch_surface->touch_count = 1; + touch_surface->surface_destroy_listener.notify = touch_handle_surface_destroy; + wl_resource_add_destroy_listener (touch_surface->surface->resource, + &touch_surface->surface_destroy_listener); + + wl_list_init (&touch_surface->resource_list); + move_resources_for_client (&touch_surface->resource_list, + &touch->resource_list, + wl_resource_get_client (touch_surface->surface->resource)); + + g_hash_table_insert (touch->touch_surfaces, surface, touch_surface); + + return touch_surface; +} + +static MetaWaylandTouchInfo * +touch_get_info (MetaWaylandTouch *touch, + ClutterEventSequence *sequence, + gboolean create) +{ + MetaWaylandTouchInfo *touch_info; + + touch_info = g_hash_table_lookup (touch->touches, sequence); + + if (!touch_info && create) + { + touch_info = g_new0 (MetaWaylandTouchInfo, 1); + touch_info->slot = clutter_evdev_event_sequence_get_slot (sequence); + g_hash_table_insert (touch->touches, sequence, touch_info); + } + + return touch_info; +} + +static void +touch_get_relative_coordinates (MetaWaylandTouch *touch, + MetaWaylandSurface *surface, + const ClutterEvent *event, + gfloat *x, + gfloat *y) +{ + gfloat event_x, event_y; + + clutter_event_get_coords (event, &event_x, &event_y); + + if (surface->surface_actor) + { + clutter_actor_transform_stage_point (CLUTTER_ACTOR (surface->surface_actor), + event_x, event_y, + &event_x, &event_y); + } + + *x = event_x; + *y = event_y; +} + + +void +meta_wayland_touch_update (MetaWaylandTouch *touch, + const ClutterEvent *event) +{ + MetaWaylandTouchInfo *touch_info; + ClutterEventSequence *sequence; + + sequence = clutter_event_get_event_sequence (event); + + if (event->type == CLUTTER_TOUCH_BEGIN) + { + MetaWaylandSurface *surface = NULL; + ClutterActor *actor; + + actor = clutter_event_get_source (event); + + if (META_IS_SURFACE_ACTOR_WAYLAND (actor)) + surface = meta_surface_actor_wayland_get_surface (META_SURFACE_ACTOR_WAYLAND (actor)); + + if (!surface) + return; + + touch_info = touch_get_info (touch, sequence, TRUE); + touch_info->touch_surface = touch_surface_get (touch, surface); + } + else + touch_info = touch_get_info (touch, sequence, FALSE); + + if (!touch_info) + return; + + if (event->type == CLUTTER_TOUCH_BEGIN || + event->type == CLUTTER_TOUCH_END) + { + MetaWaylandSurface *surface = touch_info->touch_surface->surface; + struct wl_client *client = wl_resource_get_client (surface->resource); + struct wl_display *display = wl_client_get_display (client); + + touch_info->slot_serial = wl_display_next_serial (display); + } + + touch_get_relative_coordinates (touch, touch_info->touch_surface->surface, + event, &touch_info->x, &touch_info->y); + touch_info->updated = TRUE; +} + +static void +handle_touch_begin (MetaWaylandTouch *touch, + const ClutterEvent *event) +{ + MetaWaylandTouchInfo *touch_info; + ClutterEventSequence *sequence; + struct wl_resource *resource; + struct wl_list *l; + + sequence = clutter_event_get_event_sequence (event); + touch_info = touch_get_info (touch, sequence, FALSE); + + if (!touch_info) + return; + + l = &touch_info->touch_surface->resource_list; + wl_resource_for_each(resource, l) + { + wl_touch_send_down (resource, touch_info->slot_serial, + clutter_event_get_time (event), + touch_info->touch_surface->surface->resource, + touch_info->slot, + wl_fixed_from_double (touch_info->x), + wl_fixed_from_double (touch_info->y)); + } +} + +static void +handle_touch_update (MetaWaylandTouch *touch, + const ClutterEvent *event) +{ + MetaWaylandTouchInfo *touch_info; + ClutterEventSequence *sequence; + struct wl_resource *resource; + struct wl_list *l; + + sequence = clutter_event_get_event_sequence (event); + touch_info = touch_get_info (touch, sequence, FALSE); + + if (!touch_info) + return; + + l = &touch_info->touch_surface->resource_list; + wl_resource_for_each(resource, l) + { + wl_touch_send_motion (resource, + clutter_event_get_time (event), + touch_info->slot, + wl_fixed_from_double (touch_info->x), + wl_fixed_from_double (touch_info->y)); + } +} + +static void +handle_touch_end (MetaWaylandTouch *touch, + const ClutterEvent *event) +{ + MetaWaylandTouchInfo *touch_info; + ClutterEventSequence *sequence; + struct wl_resource *resource; + struct wl_list *l; + + sequence = clutter_event_get_event_sequence (event); + touch_info = touch_get_info (touch, sequence, FALSE); + + if (!touch_info) + return; + + l = &touch_info->touch_surface->resource_list; + wl_resource_for_each(resource, l) + { + wl_touch_send_up (resource, touch_info->slot_serial, + clutter_event_get_time (event), + touch_info->slot); + } + + g_hash_table_remove (touch->touches, sequence); +} + +static GList * +touch_get_surfaces (MetaWaylandTouch *touch, + gboolean only_updated) +{ + MetaWaylandTouchInfo *touch_info; + GList *surfaces = NULL; + GHashTableIter iter; + + g_hash_table_iter_init (&iter, touch->touches); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &touch_info)) + { + if (only_updated && !touch_info->updated) + continue; + if (g_list_find (surfaces, touch_info->touch_surface)) + continue; + + surfaces = g_list_prepend (surfaces, touch_info->touch_surface); + touch_info->updated = FALSE; + } + + return g_list_reverse (surfaces); +} + +static void +touch_send_frame_event (MetaWaylandTouch *touch) +{ + GList *surfaces, *s; + + surfaces = s = touch_get_surfaces (touch, TRUE); + + for (s = surfaces; s; s = s->next) + { + MetaWaylandTouchSurface *touch_surface = s->data; + struct wl_resource *resource; + struct wl_list *l; + + l = &touch_surface->resource_list; + wl_resource_for_each(resource, l) + { + wl_touch_send_frame (resource); + } + } + + g_list_free (surfaces); +} + +static void +check_send_frame_event (MetaWaylandTouch *touch, + const ClutterEvent *event) +{ + ClutterEventSequence *sequence; + gint32 slot; + + sequence = clutter_event_get_event_sequence (event); + slot = clutter_evdev_event_sequence_get_slot (sequence); + touch->frame_slots &= ~(1 << slot); + + if (touch->frame_slots == 0) + touch_send_frame_event (touch); +} + +gboolean +meta_wayland_touch_handle_event (MetaWaylandTouch *touch, + const ClutterEvent *event) +{ + switch (event->type) + { + case CLUTTER_TOUCH_BEGIN: + handle_touch_begin (touch, event); + break; + + case CLUTTER_TOUCH_UPDATE: + handle_touch_update (touch, event); + break; + + case CLUTTER_TOUCH_END: + handle_touch_end (touch, event); + break; + + default: + return FALSE; + } + + check_send_frame_event (touch, event); + return FALSE; +} + +static void +unbind_resource (struct wl_resource *resource) +{ + wl_list_remove (wl_resource_get_link (resource)); +} + +static void +touch_release (struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static const struct wl_touch_interface touch_interface = { + touch_release, +}; + +static void +touch_info_free (MetaWaylandTouchInfo *touch_info) +{ + touch_surface_decrement_touch (touch_info->touch_surface); + g_free (touch_info); +} + +static void +touch_handle_cancel_event (MetaWaylandTouch *touch, + struct libinput_event *event) +{ + GList *surfaces, *s; + + surfaces = s = touch_get_surfaces (touch, FALSE); + + while (s) + { + MetaWaylandTouchSurface *touch_surface = s->data; + struct wl_resource *resource; + struct wl_list *l; + + l = &touch_surface->resource_list; + wl_resource_for_each(resource, l) + wl_touch_send_cancel (resource); + } + + g_hash_table_remove_all (touch->touches); + g_list_free (surfaces); +} + +static gboolean +evdev_filter_func (struct libinput_event *event, + gpointer data) +{ + MetaWaylandTouch *touch = data; + + switch (libinput_event_get_type (event)) + { + case LIBINPUT_EVENT_TOUCH_DOWN: + case LIBINPUT_EVENT_TOUCH_UP: + case LIBINPUT_EVENT_TOUCH_MOTION: { + struct libinput_event_touch *touch_event; + int32_t slot; + + touch_event = libinput_event_get_touch_event (event); + slot = libinput_event_touch_get_slot (touch_event); + + /* XXX: Could theoretically overflow, 64 slots should be + * enough for most hw/usecases though. + */ + touch->frame_slots |= (1 << slot); + break; + } + case LIBINPUT_EVENT_TOUCH_CANCEL: + /* Clutter translates this into individual CLUTTER_TOUCH_CANCEL events, + * which are not so useful when sending a global signal as the protocol + * requires. + */ + touch_handle_cancel_event (touch, event); + break; + default: + break; + } + + return CLUTTER_EVENT_PROPAGATE; +} + +void +meta_wayland_touch_init (MetaWaylandTouch *touch, + struct wl_display *display) +{ + ClutterDeviceManager *manager; + + memset (touch, 0, sizeof *touch); + + touch->display = display; + touch->touch_surfaces = g_hash_table_new_full (NULL, NULL, NULL, + (GDestroyNotify) touch_surface_free); + touch->touches = g_hash_table_new_full (NULL, NULL, NULL, + (GDestroyNotify) touch_info_free); + + wl_list_init (&touch->resource_list); + + manager = clutter_device_manager_get_default (); + touch->device = clutter_device_manager_get_core_device (manager, CLUTTER_TOUCHSCREEN_DEVICE); + clutter_evdev_add_filter (evdev_filter_func, touch, NULL); +} + +void +meta_wayland_touch_release (MetaWaylandTouch *touch) +{ + clutter_evdev_remove_filter (evdev_filter_func, touch); + g_hash_table_unref (touch->touch_surfaces); + g_hash_table_unref (touch->touches); +} + +void +meta_wayland_touch_create_new_resource (MetaWaylandTouch *touch, + struct wl_client *client, + struct wl_resource *seat_resource, + uint32_t id) +{ + struct wl_resource *cr; + + cr = wl_resource_create (client, &wl_touch_interface, + MIN (META_WL_TOUCH_VERSION, wl_resource_get_version (seat_resource)), id); + wl_resource_set_implementation (cr, NULL, touch, unbind_resource); + wl_list_insert (&touch->resource_list, wl_resource_get_link (cr)); +} diff --git a/src/wayland/meta-wayland-touch.h b/src/wayland/meta-wayland-touch.h new file mode 100644 index 000000000..e98d8b497 --- /dev/null +++ b/src/wayland/meta-wayland-touch.h @@ -0,0 +1,62 @@ +/* + * Wayland Support + * + * Copyright (C) 2014 Red Hat + * + * 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: Carlos Garnacho + */ + +#ifndef META_WAYLAND_TOUCH_H +#define META_WAYLAND_TOUCH_H + +#include + +#include + +#include "meta-wayland-types.h" + +typedef struct _MetaWaylandTouchSurface MetaWaylandTouchSurface; +typedef struct _MetaWaylandTouchInfo MetaWaylandTouchInfo; + +struct _MetaWaylandTouch +{ + struct wl_display *display; + struct wl_list resource_list; + + GHashTable *touch_surfaces; /* HT of MetaWaylandSurface->MetaWaylandTouchSurface */ + GHashTable *touches; /* HT of sequence->MetaWaylandTouchInfo */ + + ClutterInputDevice *device; + guint64 frame_slots; +}; + +void meta_wayland_touch_init (MetaWaylandTouch *touch, + struct wl_display *display); + +void meta_wayland_touch_release (MetaWaylandTouch *touch); + +void meta_wayland_touch_create_new_resource (MetaWaylandTouch *touch, + struct wl_client *client, + struct wl_resource *seat_resource, + uint32_t id); + +void meta_wayland_touch_update (MetaWaylandTouch *touch, + const ClutterEvent *event); + +gboolean meta_wayland_touch_handle_event (MetaWaylandTouch *touch, + const ClutterEvent *event); + +#endif /* META_WAYLAND_TOUCH_H */ diff --git a/src/wayland/meta-wayland-types.h b/src/wayland/meta-wayland-types.h index 177519d6e..dd51c61c7 100644 --- a/src/wayland/meta-wayland-types.h +++ b/src/wayland/meta-wayland-types.h @@ -29,6 +29,7 @@ typedef struct _MetaWaylandPointerGrabInterface MetaWaylandPointerGrabInterface; typedef struct _MetaWaylandKeyboard MetaWaylandKeyboard; typedef struct _MetaWaylandDataOffer MetaWaylandDataOffer; typedef struct _MetaWaylandDataSource MetaWaylandDataSource; +typedef struct _MetaWaylandTouch MetaWaylandTouch; typedef struct _MetaWaylandBuffer MetaWaylandBuffer; typedef struct _MetaWaylandBufferReference MetaWaylandBufferReference; diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h index bd61c7a9f..1dc9fbe18 100644 --- a/src/wayland/meta-wayland-versions.h +++ b/src/wayland/meta-wayland-versions.h @@ -51,7 +51,7 @@ #define META_WL_SURFACE_VERSION 3 /* from wl_compositor */ #define META_WL_POINTER_VERSION 3 /* from wl_seat */ #define META_WL_KEYBOARD_VERSION 3 /* from wl_seat */ -#define META_WL_TOUCH_VERSION 0 /* from wl_seat; wl_touch not supported */ +#define META_WL_TOUCH_VERSION 3 /* from wl_seat */ #define META_WL_REGION_VERSION 1 /* from wl_compositor */ #define META_XDG_SURFACE_VERSION 1 /* from xdg_shell */ #define META_XDG_POPUP_VERSION 1 /* from xdg_shell */