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: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2342>
This commit is contained in:
Jonas Dreßler 2022-08-03 17:57:13 +02:00 committed by Marge Bot
parent d795710a14
commit bac66fcbc4
5 changed files with 418 additions and 67 deletions

View File

@ -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__ */

View File

@ -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);
}

View File

@ -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 ||

View File

@ -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__ */

View File

@ -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);
}
}