From bac66fcbc4f5fa6d43b9da350f6292c29f4229d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Wed, 3 Aug 2022 17:57:13 +0200 Subject: [PATCH] clutter: Implicitly grab on button and touch event sequences We'll soon introduce a new gesture tracking framework which heavily depends on ClutterActions seeing all events of a sequence. For this to work, a larger change to event delivery is needed: Implicit grabbing of all events for button and touch press->motion->release sequences to ensure ClutterActions continue receiving events for the whole sequence. This commit takes care of that: At the start of an event sequence we collect all the event-handling actors and actions to a GArray that lives in the PointerDeviceEntry, and then deliver all events belonging to that sequence to the same actors/actions until the sequence ends. To avoid events getting pulled from under our feet when mutters event filter returns CLUTTER_EVENT_STOP, this also introduces private API (maybe_lost_implicit_grab()) on ClutterStage so that we can't end up with stale sequences. Note that this also slightly changes behavior when it comes to event delivery to actions: Because we now store actions separated from their actors, any action returning CLUTTER_EVENT_STOP now stops event propagation immediately. That was different before, where we'd emit events to all actions of the actor and only then stop propagation. Note that this isn't handling ClutterGrabs correctly right now, this will be a little tricky, so we'll take care of that in a future commit. To handle actors getting destroyed or unmapped during a grab, listen to notify::grab on the deepmost actor in the implicit grab tree. This gives us a notification when any actor inside the tree goes unmapped. Part-of: --- clutter/clutter/clutter-actor-private.h | 3 + clutter/clutter/clutter-actor.c | 25 +- clutter/clutter/clutter-main.c | 20 +- clutter/clutter/clutter-stage-private.h | 7 + clutter/clutter/clutter-stage.c | 430 ++++++++++++++++++++---- 5 files changed, 418 insertions(+), 67 deletions(-) diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h index 0e343d9e2..8f87f15ff 100644 --- a/clutter/clutter/clutter-actor-private.h +++ b/clutter/clutter/clutter-actor-private.h @@ -281,6 +281,9 @@ void clutter_actor_collect_event_actors (ClutterActor *self, const GList * clutter_actor_peek_actions (ClutterActor *self); +void clutter_actor_set_implicitly_grabbed (ClutterActor *actor, + gboolean is_implicitly_grabbed); + G_END_DECLS #endif /* __CLUTTER_ACTOR_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index 29b434ac6..dca119331 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -803,6 +803,7 @@ struct _ClutterActorPrivate GList *grabs; unsigned int n_pointers; + unsigned int implicitly_grabbed_count; /* bitfields: KEEP AT THE END */ @@ -1616,12 +1617,17 @@ clutter_actor_clear_grabs (ClutterActor *self) ClutterActorPrivate *priv = self->priv; ClutterActor *stage; - if (!priv->grabs) + if (!priv->grabs && !priv->implicitly_grabbed_count) return; stage = _clutter_actor_get_stage_internal (self); g_assert (stage != NULL); + if (priv->implicitly_grabbed_count > 0) + clutter_stage_implicit_grab_actor_unmapped (CLUTTER_STAGE (stage), self); + + g_assert (priv->implicitly_grabbed_count == 0); + /* Undo every grab that the actor may hold, priv->grabs * will be updated internally in clutter_stage_unlink_grab(). */ @@ -19135,7 +19141,7 @@ clutter_actor_collect_event_actors (ClutterActor *self, if (CLUTTER_ACTOR_IS_REACTIVE (iter) || /* an actor must be reactive */ parent == NULL) /* unless it's the stage */ - g_ptr_array_add (actors, g_object_ref (iter)); + g_ptr_array_add (actors, iter); if (iter == self) { @@ -19153,7 +19159,7 @@ clutter_actor_collect_event_actors (ClutterActor *self, if (!in_root) { g_ptr_array_remove_range (actors, 0, actors->len); - g_ptr_array_add (actors, g_object_ref (self)); + g_ptr_array_add (actors, self); } } @@ -19167,3 +19173,16 @@ clutter_actor_peek_actions (ClutterActor *self) return _clutter_meta_group_peek_metas (priv->actions); } + +void clutter_actor_set_implicitly_grabbed (ClutterActor *self, + gboolean is_implicitly_grabbed) +{ + ClutterActorPrivate *priv = self->priv; + + if (is_implicitly_grabbed) + priv->implicitly_grabbed_count++; + else + priv->implicitly_grabbed_count--; + + g_assert (priv->implicitly_grabbed_count >= 0); +} diff --git a/clutter/clutter/clutter-main.c b/clutter/clutter/clutter-main.c index b5924648a..d589174d7 100644 --- a/clutter/clutter/clutter-main.c +++ b/clutter/clutter/clutter-main.c @@ -809,8 +809,24 @@ clutter_do_event (ClutterEvent *event) context->current_event = g_slist_delete_link (context->current_event, context->current_event); - if (!filtered) - _clutter_stage_queue_event (event->any.stage, event, TRUE); + if (filtered) + { + if (event->type == CLUTTER_MOTION || + event->type == CLUTTER_BUTTON_RELEASE || + event->type == CLUTTER_TOUCH_UPDATE || + event->type == CLUTTER_TOUCH_END || + event->type == CLUTTER_TOUCH_CANCEL) + { + ClutterInputDevice *device = clutter_event_get_device (event); + ClutterEventSequence *sequence = clutter_event_get_event_sequence (event); + + clutter_stage_maybe_lost_implicit_grab (event->any.stage, device, sequence); + } + } + else + { + _clutter_stage_queue_event (event->any.stage, event, TRUE); + } if (event->type == CLUTTER_TOUCH_END || event->type == CLUTTER_TOUCH_CANCEL || diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index 309861612..c49caf8fb 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -171,6 +171,13 @@ void clutter_stage_maybe_invalidate_focus (ClutterStage *self, void clutter_stage_emit_event (ClutterStage *self, const ClutterEvent *event); +void clutter_stage_maybe_lost_implicit_grab (ClutterStage *self, + ClutterInputDevice *device, + ClutterEventSequence *sequence); + +void clutter_stage_implicit_grab_actor_unmapped (ClutterStage *self, + ClutterActor *actor); + G_END_DECLS #endif /* __CLUTTER_STAGE_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index f6118f498..9d9e0a393 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -94,6 +94,14 @@ typedef struct _PickClipRecord graphene_point_t vertex[4]; } PickClipRecord; +typedef struct _EventReceiver +{ + ClutterActor *actor; + ClutterEventPhase phase; + + ClutterAction *action; +} EventReceiver; + typedef struct _PointerDeviceEntry { ClutterStage *stage; @@ -102,6 +110,10 @@ typedef struct _PointerDeviceEntry graphene_point_t coords; ClutterActor *current_actor; cairo_region_t *clear_area; + + unsigned int press_count; + ClutterActor *implicit_grab_actor; + GArray *event_emission_chain; } PointerDeviceEntry; struct _ClutterStagePrivate @@ -123,6 +135,7 @@ struct _ClutterStagePrivate GQueue *event_queue; GPtrArray *cur_event_actors; + GArray *cur_event_emission_chain; GArray *paint_volume_stack; @@ -183,6 +196,7 @@ static const ClutterColor default_stage_color = { 255, 255, 255, 255 }; static void free_queue_redraw_entry (QueueRedrawEntry *entry); static void free_pointer_device_entry (PointerDeviceEntry *entry); +static void free_event_receiver (EventReceiver *receiver); static void clutter_stage_update_view_perspective (ClutterStage *stage); static void clutter_stage_set_viewport (ClutterStage *stage, float width, @@ -1226,6 +1240,8 @@ clutter_stage_finalize (GObject *object) g_assert (priv->cur_event_actors->len == 0); g_ptr_array_free (priv->cur_event_actors, TRUE); + g_assert (priv->cur_event_emission_chain->len == 0); + g_array_unref (priv->cur_event_emission_chain); g_hash_table_destroy (priv->pointer_devices); g_hash_table_destroy (priv->touch_sequences); @@ -1580,8 +1596,10 @@ clutter_stage_init (ClutterStage *self) priv->event_queue = g_queue_new (); priv->cur_event_actors = g_ptr_array_sized_new (32); - g_ptr_array_set_free_func (priv->cur_event_actors, - (GDestroyNotify) g_object_unref); + priv->cur_event_emission_chain = + g_array_sized_new (FALSE, TRUE, sizeof (EventReceiver), 32); + g_array_set_clear_func (priv->cur_event_emission_chain, + (GDestroyNotify) free_event_receiver); priv->pointer_devices = g_hash_table_new_full (NULL, NULL, @@ -3229,6 +3247,10 @@ free_pointer_device_entry (PointerDeviceEntry *entry) g_clear_pointer (&entry->clear_area, cairo_region_destroy); + g_assert (!entry->press_count); + g_assert (entry->event_emission_chain->len == 0); + g_array_unref (entry->event_emission_chain); + g_free (entry); } @@ -3262,6 +3284,12 @@ clutter_stage_update_device_entry (ClutterStage *self, entry->stage = self; entry->device = device; entry->sequence = sequence; + entry->press_count = 0; + entry->implicit_grab_actor = NULL; + entry->event_emission_chain = + g_array_sized_new (FALSE, TRUE, sizeof (EventReceiver), 32); + g_array_set_clear_func (entry->event_emission_chain, + (GDestroyNotify) free_event_receiver); } entry->coords = coords; @@ -3421,6 +3449,81 @@ create_crossing_event (ClutterStage *stage, return event; } +static inline void +add_actor_to_event_emission_chain (GArray *chain, + ClutterActor *actor, + ClutterEventPhase phase) +{ + EventReceiver *receiver; + + g_array_set_size (chain, chain->len + 1); + receiver = &g_array_index (chain, EventReceiver, chain->len - 1); + + receiver->actor = g_object_ref (actor); + receiver->phase = phase; +} + +static inline void +add_action_to_event_emission_chain (GArray *chain, + ClutterAction *action) +{ + EventReceiver *receiver; + + g_array_set_size (chain, chain->len + 1); + receiver = &g_array_index (chain, EventReceiver, chain->len - 1); + + receiver->action = g_object_ref (action); +} + +static void +create_event_emission_chain (ClutterStage *stage, + GArray *chain, + ClutterActor *topmost, + ClutterActor *deepmost) +{ + ClutterStagePrivate *priv = stage->priv; + int i; + + g_assert (priv->cur_event_actors->len == 0); + clutter_actor_collect_event_actors (topmost, deepmost, priv->cur_event_actors); + + for (i = priv->cur_event_actors->len - 1; i >= 0; i--) + { + ClutterActor *actor = g_ptr_array_index (priv->cur_event_actors, i); + const GList *l; + + for (l = clutter_actor_peek_actions (actor); l; l = l->next) + { + ClutterAction *action = l->data; + + if (clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (action)) && + clutter_action_get_phase (action) == CLUTTER_PHASE_CAPTURE) + add_action_to_event_emission_chain (chain, action); + } + + add_actor_to_event_emission_chain (chain, actor, CLUTTER_PHASE_CAPTURE); + } + + for (i = 0; i < priv->cur_event_actors->len; i++) + { + ClutterActor *actor = g_ptr_array_index (priv->cur_event_actors, i); + const GList *l; + + for (l = clutter_actor_peek_actions (actor); l; l = l->next) + { + ClutterAction *action = l->data; + + if (clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (action)) && + clutter_action_get_phase (action) == CLUTTER_PHASE_BUBBLE) + add_action_to_event_emission_chain (chain, action); + } + + add_actor_to_event_emission_chain (chain, actor, CLUTTER_PHASE_BUBBLE); + } + + priv->cur_event_actors->len = 0; +} + typedef enum { EVENT_NOT_HANDLED, @@ -3430,55 +3533,25 @@ typedef enum static EventHandledState emit_event (const ClutterEvent *event, - GPtrArray *actors) + GArray *event_emission_chain) { - int i; - gboolean action_handled_event = FALSE; + unsigned int i; - /* Capture: from top-level downwards */ - for (i = actors->len - 1; i >= 0; i--) + for (i = 0; i < event_emission_chain->len; i++) { - ClutterActor *actor = g_ptr_array_index (actors, i); - const GList *l; + EventReceiver *receiver = + &g_array_index (event_emission_chain, EventReceiver, i); - for (l = clutter_actor_peek_actions (actor); l; l = l->next) + if (receiver->actor) { - ClutterAction *action = l->data; - - if (clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (action)) && - clutter_action_get_phase (action) == CLUTTER_PHASE_CAPTURE && - clutter_action_handle_event (action, event)) - action_handled_event = TRUE; + if (clutter_actor_event (receiver->actor, event, receiver->phase == CLUTTER_PHASE_CAPTURE)) + return EVENT_HANDLED_BY_ACTOR; } - - if (action_handled_event) - return EVENT_HANDLED_BY_ACTION; - - if (clutter_actor_event (actor, event, TRUE)) - return EVENT_HANDLED_BY_ACTOR; - } - - /* Bubble: from source upwards */ - for (i = 0; i < actors->len; i++) - { - ClutterActor *actor = g_ptr_array_index (actors, i); - const GList *l; - - for (l = clutter_actor_peek_actions (actor); l; l = l->next) + else if (receiver->action) { - ClutterAction *action = l->data; - - if (clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (action)) && - clutter_action_get_phase (action) == CLUTTER_PHASE_BUBBLE && - clutter_action_handle_event (action, event)) - action_handled_event = TRUE; + if (clutter_action_handle_event (receiver->action, event)) + return EVENT_HANDLED_BY_ACTION; } - - if (action_handled_event) - return EVENT_HANDLED_BY_ACTION; - - if (clutter_actor_event (actor, event, FALSE)) - return EVENT_HANDLED_BY_ACTOR; } return EVENT_NOT_HANDLED; @@ -3491,34 +3564,98 @@ clutter_stage_emit_crossing_event (ClutterStage *self, ClutterActor *topmost) { ClutterStagePrivate *priv = self->priv; - gboolean in_event_emission; - GPtrArray *event_actors; + ClutterInputDevice *device = clutter_event_get_device (event); + ClutterEventSequence *sequence = clutter_event_get_event_sequence (event); + PointerDeviceEntry *entry; if (topmost == NULL) topmost = CLUTTER_ACTOR (self); - /* Crossings can happen while we're in the middle of event emission - * (for example when an actor goes unmapped or gets grabbed), so we - * can't reuse our priv->cur_event_actors here, it might already be in use. - */ - in_event_emission = priv->cur_event_actors->len != 0; + if (sequence != NULL) + entry = g_hash_table_lookup (priv->touch_sequences, sequence); + else + entry = g_hash_table_lookup (priv->pointer_devices, device); - if (in_event_emission) + g_assert (entry != NULL); + + if (entry->press_count && + !(clutter_event_get_flags (event) & CLUTTER_EVENT_FLAG_GRAB_NOTIFY)) { - event_actors = g_ptr_array_sized_new (32); - g_ptr_array_set_free_func (event_actors, g_object_unref); + emit_event (event, entry->event_emission_chain); } else { - event_actors = g_ptr_array_ref (priv->cur_event_actors); + gboolean in_event_emission; + GArray *event_emission_chain; + + /* Crossings can happen while we're in the middle of event emission + * (for example when an actor goes unmapped or gets grabbed), so we + * can't reuse priv->cur_event_emission_chain here, it might already be in use. + */ + in_event_emission = priv->cur_event_emission_chain->len != 0; + + if (in_event_emission) + { + event_emission_chain = + g_array_sized_new (FALSE, TRUE, sizeof (EventReceiver), 32); + g_array_set_clear_func (event_emission_chain, + (GDestroyNotify) free_event_receiver); + } + else + { + event_emission_chain = g_array_ref (priv->cur_event_emission_chain); + } + + create_event_emission_chain (self, event_emission_chain, topmost, deepmost); + + emit_event (event, event_emission_chain); + + g_array_remove_range (event_emission_chain, 0, event_emission_chain->len); + g_array_unref (event_emission_chain); + } +} + +static void +sync_crossings_on_implicit_grab_end (ClutterStage *self, + PointerDeviceEntry *entry) +{ + ClutterActor *deepmost, *topmost; + ClutterActor *parent; + ClutterEvent *crossing; + + deepmost = entry->current_actor; + + if (clutter_actor_contains (entry->current_actor, entry->implicit_grab_actor)) + return; + + topmost = entry->current_actor; + while ((parent = clutter_actor_get_parent (topmost))) + { + if (clutter_actor_contains (parent, entry->implicit_grab_actor)) + break; + + topmost = parent; } - clutter_actor_collect_event_actors (topmost, deepmost, event_actors); + crossing = create_crossing_event (self, + entry->device, + entry->sequence, + CLUTTER_ENTER, + CLUTTER_EVENT_FLAG_GRAB_NOTIFY, + entry->current_actor, + NULL, + entry->coords, + CLUTTER_CURRENT_TIME); - emit_event (event, event_actors); + if (!_clutter_event_process_filters (crossing, deepmost)) + { + clutter_stage_emit_crossing_event (self, + crossing, + deepmost, + topmost); + } - g_ptr_array_remove_range (event_actors, 0, event_actors->len); - g_ptr_array_unref (event_actors); + clutter_event_free (crossing); } void @@ -4119,6 +4256,80 @@ clutter_stage_get_event_actor (ClutterStage *stage, return NULL; } +static void +free_event_receiver (EventReceiver *receiver) +{ + g_clear_object (&receiver->actor); + g_clear_object (&receiver->action); +} + +static gboolean +setup_implicit_grab (PointerDeviceEntry *entry) +{ + /* With a mouse, it's possible to press two buttons at the same time, + * We ignore the second BUTTON_PRESS event here, and we'll release the + * implicit grab on the BUTTON_RELEASE of the second press. + */ + if (entry->sequence == NULL && entry->press_count) + { + entry->press_count++; + return FALSE; + } + + g_assert (entry->press_count == 0); + g_assert (entry->event_emission_chain->len == 0); + + entry->press_count = 1; + return TRUE; +} + +static gboolean +release_implicit_grab (PointerDeviceEntry *entry) +{ + if (!entry->press_count) + return FALSE; + + /* See comment in setup_implicit_grab() */ + if (entry->sequence == NULL && entry->press_count > 1) + { + entry->press_count--; + return FALSE; + } + + g_assert (entry->press_count == 1); + + entry->press_count = 0; + return TRUE; +} + +void +clutter_stage_maybe_lost_implicit_grab (ClutterStage *self, + ClutterInputDevice *device, + ClutterEventSequence *sequence) +{ + ClutterStagePrivate *priv = self->priv; + PointerDeviceEntry *entry = NULL; + + if (sequence != NULL) + entry = g_hash_table_lookup (priv->touch_sequences, sequence); + else + entry = g_hash_table_lookup (priv->pointer_devices, device); + + g_assert (entry != NULL); + + if (!entry->press_count) + return; + + sync_crossings_on_implicit_grab_end (self, entry); + + clutter_actor_set_implicitly_grabbed (entry->implicit_grab_actor, FALSE); + entry->implicit_grab_actor = NULL; + g_array_remove_range (entry->event_emission_chain, 0, + entry->event_emission_chain->len); + + entry->press_count = 0; +} + void clutter_stage_emit_event (ClutterStage *self, const ClutterEvent *event) @@ -4128,6 +4339,7 @@ clutter_stage_emit_event (ClutterStage *self, ClutterEventSequence *sequence = clutter_event_get_event_sequence (event); PointerDeviceEntry *entry; ClutterActor *target_actor = NULL, *seat_grab_actor = NULL; + gboolean is_sequence_begin, is_sequence_end; if (sequence != NULL) entry = g_hash_table_lookup (priv->touch_sequences, sequence); @@ -4195,10 +4407,104 @@ clutter_stage_emit_event (ClutterStage *self, g_assert (target_actor != NULL); seat_grab_actor = priv->topmost_grab ? priv->topmost_grab->actor : CLUTTER_ACTOR (self); - clutter_actor_collect_event_actors (seat_grab_actor, target_actor, priv->cur_event_actors); + is_sequence_begin = + event->type == CLUTTER_BUTTON_PRESS || event->type == CLUTTER_TOUCH_BEGIN; + is_sequence_end = + event->type == CLUTTER_BUTTON_RELEASE || event->type == CLUTTER_TOUCH_END || + event->type == CLUTTER_TOUCH_CANCEL; - emit_event (event, priv->cur_event_actors); + if (is_sequence_begin && setup_implicit_grab (entry)) + { + g_assert (entry->implicit_grab_actor == NULL); + entry->implicit_grab_actor = target_actor; + clutter_actor_set_implicitly_grabbed (entry->implicit_grab_actor, TRUE); - g_ptr_array_remove_range (priv->cur_event_actors, 0, - priv->cur_event_actors->len); + create_event_emission_chain (self, entry->event_emission_chain, seat_grab_actor, target_actor); + } + + if (entry && entry->press_count) + { + emit_event (event, entry->event_emission_chain); + } + else + { + create_event_emission_chain (self, priv->cur_event_emission_chain, seat_grab_actor, target_actor); + + emit_event (event, priv->cur_event_emission_chain); + + g_array_remove_range (priv->cur_event_emission_chain, 0, priv->cur_event_emission_chain->len); + } + + if (is_sequence_end && release_implicit_grab (entry)) + { + /* Sync crossings after the implicit grab for mice */ + if (event->type == CLUTTER_BUTTON_RELEASE) + sync_crossings_on_implicit_grab_end (self, entry); + + clutter_actor_set_implicitly_grabbed (entry->implicit_grab_actor, FALSE); + entry->implicit_grab_actor = NULL; + g_array_remove_range (entry->event_emission_chain, 0, entry->event_emission_chain->len); + } +} + +static void +cancel_implicit_grab_on_actor (PointerDeviceEntry *entry, + ClutterActor *actor) +{ + unsigned int i; + ClutterActor *parent = clutter_actor_get_parent (actor); + + for (i = 0; i < entry->event_emission_chain->len; i++) + { + EventReceiver *receiver = + &g_array_index (entry->event_emission_chain, EventReceiver, i); + + if (receiver->actor) + { + if (receiver->actor == actor) + g_clear_object (&receiver->actor); + } + else if (receiver->action) + { + ClutterActor *action_actor = + clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (receiver->action)); + + if (!action_actor || action_actor == actor) + g_clear_object (&receiver->action); + } + } + + clutter_actor_set_implicitly_grabbed (entry->implicit_grab_actor, FALSE); + entry->implicit_grab_actor = NULL; + + if (parent) + { + g_assert (CLUTTER_ACTOR_IS_MAPPED (parent)); + + entry->implicit_grab_actor = parent; + clutter_actor_set_implicitly_grabbed (entry->implicit_grab_actor, TRUE); + } +} + +void +clutter_stage_implicit_grab_actor_unmapped (ClutterStage *self, + ClutterActor *actor) +{ + ClutterStagePrivate *priv = self->priv; + GHashTableIter iter; + PointerDeviceEntry *entry; + + g_hash_table_iter_init (&iter, priv->pointer_devices); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &entry)) + { + if (entry->implicit_grab_actor == actor) + cancel_implicit_grab_on_actor (entry, actor); + } + + g_hash_table_iter_init (&iter, priv->touch_sequences); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &entry)) + { + if (entry->implicit_grab_actor == actor) + cancel_implicit_grab_on_actor (entry, actor); + } }