/* * 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, see . * * Author: Carlos Garnacho */ #include "config.h" #include #include #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 MetaBackend * backend_from_touch (MetaWaylandTouch *touch) { MetaWaylandInputDevice *input_device = META_WAYLAND_INPUT_DEVICE (touch); MetaWaylandSeat *seat = meta_wayland_input_device_get_seat (input_device); MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat); MetaContext *context = meta_wayland_compositor_get_context (compositor); return meta_context_get_backend (context); } 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; ClutterEventType event_type; sequence = clutter_event_get_event_sequence (event); event_type = clutter_event_type (event); if (event_type == CLUTTER_TOUCH_BEGIN) { MetaWaylandSurface *surface = NULL; MetaBackend *backend; ClutterStage *stage; ClutterActor *actor; backend = backend_from_touch (touch); stage = CLUTTER_STAGE (meta_backend_get_stage (backend)); actor = clutter_stage_get_device_actor (stage, clutter_event_get_device (event), clutter_event_get_event_sequence (event)); if (META_IS_SURFACE_ACTOR_WAYLAND (actor)) surface = meta_surface_actor_wayland_get_surface (META_SURFACE_ACTOR_WAYLAND (actor)); if (!surface || !surface->resource) 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 (clutter_event_type (event)) { 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; } static gboolean touch_can_grab_surface (MetaWaylandTouchInfo *touch_info, MetaWaylandSurface *surface) { MetaWaylandSurface *subsurface; if (touch_info->touch_surface->surface == surface) return TRUE; META_WAYLAND_SURFACE_FOREACH_SUBSURFACE (&surface->applied_state, subsurface) { if (touch_can_grab_surface (touch_info, subsurface)) 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_can_grab_surface (touch_info, 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) { }