7bf10de538
If we happen to handle a CLUTTER_TOUCH_BEGIN without a corresponding CLUTTER_TOUCH_END at MetaWaylandTouch, we would still attempt to reuse the older MetaWaylandTouchInfo, resulting in an assert triggered as there is a stale touch reference on the previous surface. Warn in place and create a new touch info struct to still fix the broken surface accounting, instead of finding out the hard way after the surface is destroyed. The assert is preserved to ensure the accounting does not sneakily break anymore/further. Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/584 Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2251>
611 lines
16 KiB
C
611 lines
16 KiB
C
/*
|
|
* 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 <carlosg@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib.h>
|
|
#include <string.h>
|
|
|
|
#include "compositor/meta-surface-actor-wayland.h"
|
|
#include "wayland/meta-wayland-private.h"
|
|
|
|
G_DEFINE_TYPE (MetaWaylandTouch, meta_wayland_touch,
|
|
META_TYPE_WAYLAND_INPUT_DEVICE)
|
|
|
|
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 start_x;
|
|
gfloat start_y;
|
|
gfloat x;
|
|
gfloat y;
|
|
guint updated : 1;
|
|
guint begin_delivered : 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 (create)
|
|
{
|
|
if (touch_info != NULL)
|
|
g_warning ("Stale touch information for sequence slot %p", sequence);
|
|
|
|
touch_info = g_new0 (MetaWaylandTouchInfo, 1);
|
|
touch_info->slot = clutter_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);
|
|
|
|
return meta_wayland_surface_get_relative_coordinates (surface,
|
|
event_x, event_y,
|
|
x, 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);
|
|
clutter_event_get_coords (event, &touch_info->start_x, &touch_info->start_y);
|
|
}
|
|
else
|
|
touch_info = touch_get_info (touch, sequence, FALSE);
|
|
|
|
if (!touch_info)
|
|
return;
|
|
|
|
if (event->type != CLUTTER_TOUCH_BEGIN &&
|
|
!touch_info->begin_delivered)
|
|
{
|
|
g_hash_table_remove (touch->touches, sequence);
|
|
return;
|
|
}
|
|
|
|
if (event->type == CLUTTER_TOUCH_BEGIN ||
|
|
event->type == CLUTTER_TOUCH_END)
|
|
{
|
|
MetaWaylandInputDevice *input_device = META_WAYLAND_INPUT_DEVICE (touch);
|
|
|
|
touch_info->slot_serial =
|
|
meta_wayland_input_device_next_serial (input_device);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
touch_info->begin_delivered = TRUE;
|
|
}
|
|
|
|
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 = 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 gboolean
|
|
queue_frame_event_cb (MetaWaylandTouch *touch)
|
|
{
|
|
touch_send_frame_event (touch);
|
|
touch->queued_frame_id = 0;
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
send_or_queue_frame_event (MetaWaylandTouch *touch)
|
|
{
|
|
if (clutter_events_pending ())
|
|
{
|
|
if (touch->queued_frame_id == 0)
|
|
{
|
|
touch->queued_frame_id =
|
|
g_idle_add_full (CLUTTER_PRIORITY_EVENTS + 1,
|
|
(GSourceFunc) queue_frame_event_cb,
|
|
touch, NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* There's no more events */
|
|
g_clear_handle_id (&touch->queued_frame_id, g_source_remove);
|
|
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;
|
|
|
|
case CLUTTER_TOUCH_CANCEL:
|
|
meta_wayland_touch_cancel (touch);
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
send_or_queue_frame_event (touch);
|
|
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);
|
|
}
|
|
|
|
void
|
|
meta_wayland_touch_cancel (MetaWaylandTouch *touch)
|
|
{
|
|
MetaWaylandInputDevice *input_device = META_WAYLAND_INPUT_DEVICE (touch);
|
|
MetaWaylandSeat *seat = meta_wayland_input_device_get_seat (input_device);
|
|
GList *surfaces, *s;
|
|
|
|
if (!meta_wayland_seat_has_touch (seat))
|
|
return;
|
|
|
|
surfaces = s = touch_get_surfaces (touch, FALSE);
|
|
|
|
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_cancel (resource);
|
|
}
|
|
|
|
g_hash_table_remove_all (touch->touches);
|
|
g_list_free (surfaces);
|
|
}
|
|
|
|
void
|
|
meta_wayland_touch_enable (MetaWaylandTouch *touch)
|
|
{
|
|
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);
|
|
}
|
|
|
|
void
|
|
meta_wayland_touch_disable (MetaWaylandTouch *touch)
|
|
{
|
|
meta_wayland_touch_cancel (touch);
|
|
|
|
g_clear_pointer (&touch->touch_surfaces, g_hash_table_unref);
|
|
g_clear_pointer (&touch->touches, g_hash_table_unref);
|
|
}
|
|
|
|
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, wl_resource_get_version (seat_resource), id);
|
|
wl_resource_set_implementation (cr, &touch_interface, touch, unbind_resource);
|
|
wl_list_insert (&touch->resource_list, wl_resource_get_link (cr));
|
|
}
|
|
|
|
gboolean
|
|
meta_wayland_touch_can_popup (MetaWaylandTouch *touch,
|
|
uint32_t serial)
|
|
{
|
|
MetaWaylandTouchInfo *touch_info;
|
|
GHashTableIter iter;
|
|
|
|
if (!touch->touches)
|
|
return FALSE;
|
|
|
|
g_hash_table_iter_init (&iter, touch->touches);
|
|
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &touch_info))
|
|
{
|
|
if (touch_info->slot_serial == serial)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
ClutterEventSequence *
|
|
meta_wayland_touch_find_grab_sequence (MetaWaylandTouch *touch,
|
|
MetaWaylandSurface *surface,
|
|
uint32_t serial)
|
|
{
|
|
MetaWaylandTouchInfo *touch_info;
|
|
ClutterEventSequence *sequence;
|
|
GHashTableIter iter;
|
|
|
|
if (!touch->touches)
|
|
return NULL;
|
|
|
|
g_hash_table_iter_init (&iter, touch->touches);
|
|
|
|
while (g_hash_table_iter_next (&iter, (gpointer*) &sequence,
|
|
(gpointer*) &touch_info))
|
|
{
|
|
if (touch_info->slot_serial == serial &&
|
|
touch_info->touch_surface->surface == surface)
|
|
return sequence;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
meta_wayland_touch_get_press_coords (MetaWaylandTouch *touch,
|
|
ClutterEventSequence *sequence,
|
|
gfloat *x,
|
|
gfloat *y)
|
|
{
|
|
MetaWaylandTouchInfo *touch_info;
|
|
|
|
if (!touch->touches)
|
|
return FALSE;
|
|
|
|
touch_info = g_hash_table_lookup (touch->touches, sequence);
|
|
|
|
if (!touch_info)
|
|
return FALSE;
|
|
|
|
if (x)
|
|
*x = touch_info->start_x;
|
|
if (y)
|
|
*y = touch_info->start_y;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
meta_wayland_touch_init (MetaWaylandTouch *touch)
|
|
{
|
|
}
|
|
|
|
static void
|
|
meta_wayland_touch_class_init (MetaWaylandTouchClass *klass)
|
|
{
|
|
}
|