diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index df0e7872e..677aa6ede 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -178,6 +178,7 @@ void clutter_stage_maybe_lost_implicit_grab (ClutterStage *self, void clutter_stage_implicit_grab_actor_unmapped (ClutterStage *self, ClutterActor *actor); +CLUTTER_EXPORT_TEST void clutter_stage_notify_action_implicit_grab (ClutterStage *self, ClutterInputDevice *device, ClutterEventSequence *sequence); diff --git a/src/tests/clutter/conform/event-delivery.c b/src/tests/clutter/conform/event-delivery.c index f9a97e090..0f3ca256c 100644 --- a/src/tests/clutter/conform/event-delivery.c +++ b/src/tests/clutter/conform/event-delivery.c @@ -1,5 +1,7 @@ #include +#include "clutter/clutter-mutter.h" + #include "tests/clutter-test-utils.h" static void @@ -21,6 +23,16 @@ on_event_return_stop (ClutterActor *actor, return CLUTTER_EVENT_STOP; } +static gboolean +on_event_return_propagate (ClutterActor *actor, + ClutterEvent *event, + unsigned int *n_events) +{ + (*n_events)++; + + return CLUTTER_EVENT_PROPAGATE; +} + static void wait_stage_updated (gboolean *was_updated) { @@ -68,6 +80,590 @@ event_delivery_consecutive_touch_begin_end (void) g_signal_handlers_disconnect_by_func (stage, on_after_update, &was_updated); } +static unsigned int n_action_motion_events = 0; +static unsigned int n_action_touch_events = 0; +static unsigned int n_action_sequences_cancelled = 0; +static gboolean action_claim_sequence = FALSE; +static gboolean action_handle_event_retval = CLUTTER_EVENT_PROPAGATE; + +G_BEGIN_DECLS + +#define TEST_TYPE_ACTION (test_action_get_type ()) + +G_DECLARE_DERIVABLE_TYPE (TestAction, test_action, + TEST, ACTION, ClutterAction) + +struct _TestActionClass +{ + ClutterActionClass parent_class; +}; + +G_DEFINE_TYPE (TestAction, test_action, CLUTTER_TYPE_ACTION); + +G_END_DECLS + +static gboolean +test_action_handle_event (ClutterAction *action, + const ClutterEvent *event) +{ + ClutterEventType type = clutter_event_type (event); + + if (type == CLUTTER_MOTION) + n_action_motion_events++; + + if (type == CLUTTER_TOUCH_BEGIN || + type == CLUTTER_TOUCH_UPDATE || + type == CLUTTER_TOUCH_END || + type == CLUTTER_TOUCH_CANCEL) + { + n_action_touch_events++; + + if (action_claim_sequence) + { + clutter_stage_notify_action_implicit_grab (clutter_event_get_stage (event), + clutter_event_get_device (event), + clutter_event_get_event_sequence (event)); + } + } + + return action_handle_event_retval; +} + +static void +test_action_sequence_cancelled (ClutterAction *action, + ClutterInputDevice *device, + ClutterEventSequence *sequence) +{ + n_action_sequences_cancelled++; +} + +static void +test_action_class_init (TestActionClass *klass) +{ + ClutterActionClass *action_class = CLUTTER_ACTION_CLASS (klass); + + action_class->handle_event = test_action_handle_event; + action_class->sequence_cancelled = test_action_sequence_cancelled; +} + +static void +test_action_init (TestAction *self) +{ +} + +static void +event_delivery_implicit_grabbing (void) +{ + ClutterActor *stage = clutter_test_get_stage (); + ClutterSeat *seat = + clutter_backend_get_default_seat (clutter_get_default_backend ()); + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + int64_t now_us; + ClutterActor *child; + gboolean was_updated; + unsigned int n_child_motion_events, n_stage_motion_events; + unsigned int n_child_button_events, n_stage_button_events; + unsigned int n_child_enter_events, n_stage_enter_events; + + virtual_pointer = clutter_seat_create_virtual_device (seat, CLUTTER_POINTER_DEVICE); + now_us = g_get_monotonic_time (); + + child = clutter_actor_new (); + clutter_actor_set_reactive (child, TRUE); + clutter_actor_set_position (child, 20, 0); + clutter_actor_set_size (child, 20, 20); + clutter_actor_add_child (stage, child); + + g_signal_connect (stage, "after-update", G_CALLBACK (on_after_update), + &was_updated); + g_signal_connect (stage, "event::motion", G_CALLBACK (on_event_return_propagate), + &n_stage_motion_events); + g_signal_connect (stage, "event::button", G_CALLBACK (on_event_return_propagate), + &n_stage_button_events); + g_signal_connect (child, "event::motion", G_CALLBACK (on_event_return_propagate), + &n_child_motion_events); + g_signal_connect (child, "event::button", G_CALLBACK (on_event_return_propagate), + &n_child_button_events); + + clutter_actor_show (stage); + wait_stage_updated (&was_updated); + + n_stage_motion_events = n_child_motion_events = 0; + n_stage_button_events = n_child_button_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 1, 1); + clutter_virtual_input_device_notify_button (virtual_pointer, now_us, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_PRESSED); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_motion_events, ==, 0); + g_assert_cmpint (n_child_button_events, ==, 0); + g_assert_cmpint (n_stage_motion_events, ==, 1); + g_assert_cmpint (n_stage_button_events, ==, 1); + + n_stage_motion_events = n_child_motion_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 30, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_motion_events, ==, 0); + g_assert_cmpint (n_stage_motion_events, ==, 1); + + /* After the implicit grab ends, the new actor under cursor should receive a + * GRAB_NOTIFY ENTER event. + */ + g_signal_connect (stage, "enter-event", G_CALLBACK (on_event_return_propagate), + &n_stage_enter_events); + g_signal_connect (child, "enter-event", G_CALLBACK (on_event_return_propagate), + &n_child_enter_events); + + n_stage_button_events = n_child_button_events = 0; + n_stage_enter_events = n_child_enter_events = 0; + clutter_virtual_input_device_notify_button (virtual_pointer, now_us, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_RELEASED); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_button_events, ==, 0); + g_assert_cmpint (n_child_enter_events, ==, 1); + g_assert_cmpint (n_stage_button_events, ==, 1); + g_assert_cmpint (n_stage_enter_events, ==, 0); + + g_signal_handlers_disconnect_by_func (child, on_event_return_propagate, &n_child_enter_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_enter_events); + + n_stage_motion_events = n_child_motion_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 30, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_motion_events, ==, 1); + g_assert_cmpint (n_stage_motion_events, ==, 1); + + g_signal_handlers_disconnect_by_func (child, on_event_return_propagate, &n_child_button_events); + g_signal_handlers_disconnect_by_func (child, on_event_return_propagate, &n_child_motion_events); + clutter_actor_destroy (child); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_button_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_motion_events); + g_signal_handlers_disconnect_by_func (stage, on_after_update, &was_updated); +} + +static void +event_delivery_implicit_grab_cancelled (void) +{ + ClutterActor *stage = clutter_test_get_stage (); + ClutterSeat *seat = + clutter_backend_get_default_seat (clutter_get_default_backend ()); + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + int64_t now_us; + ClutterActor *child_1, *child_2; + ClutterAction *action_1; + gboolean was_updated; + unsigned int n_child_1_button_events, n_child_2_button_events, n_stage_button_events; + unsigned int n_child_1_enter_events, n_child_2_enter_events, n_stage_enter_events; + unsigned int n_child_1_leave_events, n_child_2_leave_events, n_stage_leave_events; + + virtual_pointer = clutter_seat_create_virtual_device (seat, CLUTTER_POINTER_DEVICE); + now_us = g_get_monotonic_time (); + + child_1 = clutter_actor_new (); + action_1 = g_object_new (TEST_TYPE_ACTION, NULL); + action_handle_event_retval = CLUTTER_EVENT_PROPAGATE; + clutter_actor_add_action (child_1, CLUTTER_ACTION (action_1)); + clutter_actor_set_reactive (child_1, TRUE); + clutter_actor_set_size (child_1, 20, 20); + clutter_actor_add_child (stage, child_1); + + child_2 = clutter_actor_new (); + clutter_actor_set_reactive (child_2, TRUE); + clutter_actor_set_position (child_2, 30, 0); + clutter_actor_set_size (child_2, 20, 20); + clutter_actor_add_child (stage, child_2); + + g_signal_connect (stage, "after-update", G_CALLBACK (on_after_update), + &was_updated); + g_signal_connect (stage, "event::button", G_CALLBACK (on_event_return_propagate), + &n_stage_button_events); + g_signal_connect (child_1, "event::button", G_CALLBACK (on_event_return_propagate), + &n_child_1_button_events); + g_signal_connect (child_2, "event::button", G_CALLBACK (on_event_return_propagate), + &n_child_2_button_events); + g_signal_connect (stage, "enter-event", G_CALLBACK (on_event_return_propagate), + &n_stage_enter_events); + g_signal_connect (child_1, "enter-event", G_CALLBACK (on_event_return_propagate), + &n_child_1_enter_events); + g_signal_connect (child_2, "enter-event", G_CALLBACK (on_event_return_propagate), + &n_child_2_enter_events); + g_signal_connect (stage, "leave-event", G_CALLBACK (on_event_return_propagate), + &n_stage_leave_events); + g_signal_connect (child_1, "leave-event", G_CALLBACK (on_event_return_propagate), + &n_child_1_leave_events); + g_signal_connect (child_2, "leave-event", G_CALLBACK (on_event_return_propagate), + &n_child_2_leave_events); + + clutter_actor_show (stage); + wait_stage_updated (&was_updated); + + n_child_1_button_events = n_child_2_button_events = n_stage_button_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 1, 1); + clutter_virtual_input_device_notify_button (virtual_pointer, now_us, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_PRESSED); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_1_button_events, ==, 1); + g_assert_cmpint (n_child_2_button_events, ==, 0); + g_assert_cmpint (n_stage_button_events, ==, 1); + + n_child_1_enter_events = n_child_2_enter_events = n_stage_enter_events = 0; + n_child_1_leave_events = n_child_2_leave_events = n_stage_leave_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 32, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_1_enter_events, ==, 1); + g_assert_cmpint (n_child_1_leave_events, ==, 1); + g_assert_cmpint (n_child_2_enter_events, ==, 0); + g_assert_cmpint (n_child_2_leave_events, ==, 0); + g_assert_cmpint (n_stage_enter_events, ==, 1); + g_assert_cmpint (n_stage_leave_events, ==, 1); + + /* Destroying child_1 should not cancel the grab, instead the grab should still + * be in effect on the parent (so the stage) now. + */ + n_child_1_enter_events = n_child_2_enter_events = n_stage_enter_events = 0; + n_child_1_leave_events = n_child_2_leave_events = n_stage_leave_events = 0; + n_action_sequences_cancelled = 0; + clutter_actor_destroy (child_1); + g_assert_cmpint (n_child_1_enter_events, ==, 0); + g_assert_cmpint (n_child_1_leave_events, ==, 0); + g_assert_cmpint (n_action_sequences_cancelled, ==, 1); + g_assert_cmpint (n_child_2_enter_events, ==, 0); + g_assert_cmpint (n_child_2_leave_events, ==, 0); + g_assert_cmpint (n_stage_enter_events, ==, 0); + g_assert_cmpint (n_stage_leave_events, ==, 0); + + n_child_2_enter_events = n_stage_enter_events = 0; + n_child_2_leave_events = n_stage_leave_events = 0; + n_child_2_button_events = n_stage_button_events = 0; + clutter_virtual_input_device_notify_button (virtual_pointer, now_us, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_RELEASED); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_2_button_events, ==, 0); + g_assert_cmpint (n_child_2_enter_events, ==, 1); + g_assert_cmpint (n_child_2_leave_events, ==, 0); + g_assert_cmpint (n_stage_button_events, ==, 1); + + g_signal_handlers_disconnect_by_func (child_2, on_event_return_propagate, &n_child_2_leave_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_leave_events); + g_signal_handlers_disconnect_by_func (child_2, on_event_return_propagate, &n_child_2_enter_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_enter_events); + + g_signal_handlers_disconnect_by_func (child_2, on_event_return_propagate, &n_child_2_button_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_button_events); + clutter_actor_destroy (child_2); + + g_signal_handlers_disconnect_by_func (stage, on_after_update, &was_updated); +} + +static void +event_delivery_implicit_grab_existing_clutter_grab (void) +{ + ClutterActor *stage = clutter_test_get_stage (); + ClutterSeat *seat = + clutter_backend_get_default_seat (clutter_get_default_backend ()); + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + int64_t now_us; + ClutterActor *child_1, *child_2; + ClutterAction *action_1; + gboolean was_updated; + unsigned int n_child_1_button_events, n_child_2_button_events, n_stage_button_events; + unsigned int n_child_1_motion_events, n_child_2_motion_events, n_stage_motion_events; + unsigned int n_child_1_enter_events, n_child_2_enter_events, n_stage_enter_events; + unsigned int n_child_1_leave_events, n_child_2_leave_events, n_stage_leave_events; + ClutterGrab *grab_1, *grab_2; + + virtual_pointer = clutter_seat_create_virtual_device (seat, CLUTTER_POINTER_DEVICE); + now_us = g_get_monotonic_time (); + + child_1 = clutter_actor_new (); + action_1 = g_object_new (TEST_TYPE_ACTION, NULL); + action_handle_event_retval = CLUTTER_EVENT_PROPAGATE; + clutter_actor_add_action (child_1, CLUTTER_ACTION (action_1)); + clutter_actor_set_reactive (child_1, TRUE); + clutter_actor_set_size (child_1, 20, 20); + clutter_actor_add_child (stage, child_1); + + child_2 = clutter_actor_new (); + clutter_actor_set_reactive (child_2, TRUE); + clutter_actor_set_position (child_2, 30, 0); + clutter_actor_set_size (child_2, 20, 20); + clutter_actor_add_child (stage, child_2); + + g_signal_connect (stage, "after-update", G_CALLBACK (on_after_update), + &was_updated); + g_signal_connect (stage, "event::button", G_CALLBACK (on_event_return_propagate), + &n_stage_button_events); + g_signal_connect (child_1, "event::button", G_CALLBACK (on_event_return_propagate), + &n_child_1_button_events); + g_signal_connect (child_2, "event::button", G_CALLBACK (on_event_return_propagate), + &n_child_2_button_events); + g_signal_connect (stage, "event::motion", G_CALLBACK (on_event_return_propagate), + &n_stage_motion_events); + g_signal_connect (child_1, "event::motion", G_CALLBACK (on_event_return_propagate), + &n_child_1_motion_events); + g_signal_connect (child_2, "event::motion", G_CALLBACK (on_event_return_propagate), + &n_child_2_motion_events); + g_signal_connect (stage, "enter-event", G_CALLBACK (on_event_return_propagate), + &n_stage_enter_events); + g_signal_connect (child_1, "enter-event", G_CALLBACK (on_event_return_propagate), + &n_child_1_enter_events); + g_signal_connect (child_2, "enter-event", G_CALLBACK (on_event_return_propagate), + &n_child_2_enter_events); + g_signal_connect (stage, "leave-event", G_CALLBACK (on_event_return_propagate), + &n_stage_leave_events); + g_signal_connect (child_1, "leave-event", G_CALLBACK (on_event_return_propagate), + &n_child_1_leave_events); + g_signal_connect (child_2, "leave-event", G_CALLBACK (on_event_return_propagate), + &n_child_2_leave_events); + + clutter_actor_show (stage); + wait_stage_updated (&was_updated); + + n_child_1_button_events = n_child_2_button_events = n_stage_button_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 1, 1); + clutter_virtual_input_device_notify_button (virtual_pointer, now_us, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_PRESSED); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_1_button_events, ==, 1); + g_assert_cmpint (n_child_2_button_events, ==, 0); + g_assert_cmpint (n_stage_button_events, ==, 1); + + /* The ClutterGrab on child_1 (while that same actor is implicitly grabbed) + * should cause us to keep the implicit grab intact but send actors outside (so + * the stage) a LEAVE event. + */ + n_child_1_enter_events = n_child_2_enter_events = n_stage_enter_events = 0; + n_child_1_leave_events = n_child_2_leave_events = n_stage_leave_events = 0; + n_action_sequences_cancelled = 0; + grab_1 = clutter_stage_grab (CLUTTER_STAGE (stage), child_1); + g_assert_cmpint (n_child_1_enter_events, ==, 0); + g_assert_cmpint (n_child_1_leave_events, ==, 0); + g_assert_cmpint (n_action_sequences_cancelled, ==, 0); + g_assert_cmpint (n_child_2_enter_events, ==, 0); + g_assert_cmpint (n_child_2_leave_events, ==, 0); + g_assert_cmpint (n_stage_enter_events, ==, 0); + g_assert_cmpint (n_stage_leave_events, ==, 1); + + /* Implicit grab is still there, but only on child_1 now */ + n_child_1_motion_events = n_child_2_motion_events = n_stage_motion_events = 0; + n_action_motion_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 31, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_1_motion_events, ==, 1); + g_assert_cmpint (n_action_motion_events, ==, 1); + g_assert_cmpint (n_child_2_motion_events, ==, 0); + g_assert_cmpint (n_stage_motion_events, ==, 0); + + /* Push another ClutterGrab, this time on child_2. This will now cancel the + * implicit one. + */ + n_child_1_enter_events = n_child_2_enter_events = n_stage_enter_events = 0; + n_child_1_leave_events = n_child_2_leave_events = n_stage_leave_events = 0; + n_action_sequences_cancelled = 0; + grab_2 = clutter_stage_grab (CLUTTER_STAGE (stage), child_2); + g_assert_cmpint (n_child_1_enter_events, ==, 0); + g_assert_cmpint (n_child_1_leave_events, ==, 0); + g_assert_cmpint (n_action_sequences_cancelled, ==, 1); + g_assert_cmpint (n_child_2_enter_events, ==, 1); + g_assert_cmpint (n_child_2_leave_events, ==, 0); + g_assert_cmpint (n_stage_enter_events, ==, 0); + g_assert_cmpint (n_stage_leave_events, ==, 0); + + n_child_1_motion_events = n_child_2_motion_events = n_stage_motion_events = 0; + n_action_motion_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 1, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_1_motion_events, ==, 0); + g_assert_cmpint (n_action_motion_events, ==, 0); + g_assert_cmpint (n_child_2_motion_events, ==, 1); + g_assert_cmpint (n_stage_motion_events, ==, 0); + + n_child_1_enter_events = n_child_2_enter_events = n_stage_enter_events = 0; + n_child_1_leave_events = n_child_2_leave_events = n_stage_leave_events = 0; + clutter_grab_dismiss (grab_2); + g_assert_cmpint (n_child_1_enter_events, ==, 1); + g_assert_cmpint (n_child_1_leave_events, ==, 0); + g_assert_cmpint (n_child_2_enter_events, ==, 0); + g_assert_cmpint (n_child_2_leave_events, ==, 0); + g_assert_cmpint (n_stage_enter_events, ==, 0); + g_assert_cmpint (n_stage_leave_events, ==, 0); + + n_child_1_enter_events = n_child_2_enter_events = n_stage_enter_events = 0; + n_child_1_leave_events = n_child_2_leave_events = n_stage_leave_events = 0; + clutter_grab_dismiss (grab_1); + g_assert_cmpint (n_child_1_enter_events, ==, 0); + g_assert_cmpint (n_child_1_leave_events, ==, 0); + g_assert_cmpint (n_child_2_enter_events, ==, 0); + g_assert_cmpint (n_child_2_leave_events, ==, 0); + g_assert_cmpint (n_stage_enter_events, ==, 1); + g_assert_cmpint (n_stage_leave_events, ==, 0); + + n_child_1_button_events = n_child_2_button_events = n_stage_button_events = 0; + clutter_virtual_input_device_notify_button (virtual_pointer, now_us, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_RELEASED); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_1_button_events, ==, 1); + g_assert_cmpint (n_child_2_button_events, ==, 0); + g_assert_cmpint (n_stage_button_events, ==, 1); + + g_signal_handlers_disconnect_by_func (child_2, on_event_return_propagate, &n_child_2_leave_events); + g_signal_handlers_disconnect_by_func (child_1, on_event_return_propagate, &n_child_1_leave_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_leave_events); + g_signal_handlers_disconnect_by_func (child_2, on_event_return_propagate, &n_child_2_enter_events); + g_signal_handlers_disconnect_by_func (child_1, on_event_return_propagate, &n_child_1_enter_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_enter_events); + + g_signal_handlers_disconnect_by_func (child_2, on_event_return_propagate, &n_child_2_motion_events); + g_signal_handlers_disconnect_by_func (child_1, on_event_return_propagate, &n_child_1_motion_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_motion_events); + g_signal_handlers_disconnect_by_func (child_2, on_event_return_propagate, &n_child_2_button_events); + g_signal_handlers_disconnect_by_func (child_1, on_event_return_propagate, &n_child_1_button_events); + g_signal_handlers_disconnect_by_func (stage, on_event_return_propagate, &n_stage_button_events); + clutter_actor_destroy (child_2); + clutter_actor_destroy (child_1); + g_signal_handlers_disconnect_by_func (stage, on_after_update, &was_updated); +} + +static void +event_delivery_stop_discrete_event (void) +{ + ClutterActor *stage = clutter_test_get_stage (); + ClutterSeat *seat = + clutter_backend_get_default_seat (clutter_get_default_backend ()); + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + int64_t now_us; + TestAction *test_action; + ClutterActor *child; + gboolean was_updated; + unsigned int n_child_motion_events, n_stage_motion_events; + + virtual_pointer = clutter_seat_create_virtual_device (seat, CLUTTER_POINTER_DEVICE); + now_us = g_get_monotonic_time (); + + test_action = g_object_new (TEST_TYPE_ACTION, NULL); + action_handle_event_retval = CLUTTER_EVENT_STOP; + + child = clutter_actor_new (); + clutter_actor_set_reactive (child, TRUE); + clutter_actor_set_size (child, 20, 20); + clutter_actor_add_action (child, CLUTTER_ACTION (test_action)); + clutter_actor_add_child (stage, child); + + g_signal_connect (stage, "after-update", G_CALLBACK (on_after_update), + &was_updated); + g_signal_connect (stage, "event::motion", G_CALLBACK (on_event_return_stop), + &n_stage_motion_events); + g_signal_connect (child, "event::motion", G_CALLBACK (on_event_return_stop), + &n_child_motion_events); + + clutter_actor_show (stage); + wait_stage_updated (&was_updated); + + n_stage_motion_events = n_action_motion_events = n_child_motion_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 1, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_action_motion_events, ==, 1); + g_assert_cmpint (n_child_motion_events, ==, 0); + g_assert_cmpint (n_stage_motion_events, ==, 0); + + action_handle_event_retval = CLUTTER_EVENT_PROPAGATE; + + n_stage_motion_events = n_action_motion_events = n_child_motion_events = 0; + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, now_us, 1, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_action_motion_events, ==, 1); + g_assert_cmpint (n_child_motion_events, ==, 1); + g_assert_cmpint (n_stage_motion_events, ==, 0); + + g_signal_handlers_disconnect_by_func (child, on_event_return_propagate, &n_child_motion_events); + clutter_actor_destroy (child); + g_signal_handlers_disconnect_by_func (stage, on_event_return_stop, &n_stage_motion_events); + g_signal_handlers_disconnect_by_func (stage, on_after_update, &was_updated); +} + +static void +event_delivery_actor_stop_sequence_event (void) +{ + ClutterActor *stage = clutter_test_get_stage (); + ClutterSeat *seat = + clutter_backend_get_default_seat (clutter_get_default_backend ()); + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + int64_t now_us; + TestAction *test_action; + ClutterActor *child; + gboolean was_updated; + unsigned int n_child_touch_events, n_stage_touch_events; + + virtual_pointer = clutter_seat_create_virtual_device (seat, CLUTTER_TOUCHSCREEN_DEVICE); + now_us = g_get_monotonic_time (); + + test_action = g_object_new (TEST_TYPE_ACTION, NULL); + action_handle_event_retval = CLUTTER_EVENT_PROPAGATE; + + child = clutter_actor_new (); + clutter_actor_set_reactive (child, TRUE); + clutter_actor_set_size (child, 20, 20); + clutter_actor_add_action (child, CLUTTER_ACTION (test_action)); + clutter_actor_add_child (stage, child); + + g_signal_connect (stage, "after-update", G_CALLBACK (on_after_update), + &was_updated); + g_signal_connect (stage, "event::touch", G_CALLBACK (on_event_return_stop), + &n_stage_touch_events); + g_signal_connect (child, "captured-event::touch", G_CALLBACK (on_event_return_stop), + &n_child_touch_events); + + clutter_actor_show (stage); + wait_stage_updated (&was_updated); + + n_stage_touch_events = n_action_touch_events = n_child_touch_events = 0; + n_action_sequences_cancelled = 0; + clutter_virtual_input_device_notify_touch_down (virtual_pointer, now_us, 0, 1, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_touch_events, ==, 1); + g_assert_cmpint (n_action_sequences_cancelled, ==, 1); + g_assert_cmpint (n_action_touch_events, ==, 0); + g_assert_cmpint (n_stage_touch_events, ==, 0); + + /* Even if the child now lets events propagate, the action should no longer see them */ + g_signal_handlers_disconnect_by_func (child, on_event_return_stop, &n_child_touch_events); + g_signal_connect (child, "captured-event::touch", G_CALLBACK (on_event_return_propagate), + &n_child_touch_events); + + n_stage_touch_events = n_action_touch_events = n_child_touch_events = 0; + n_action_sequences_cancelled = 0; + clutter_virtual_input_device_notify_touch_motion (virtual_pointer, now_us, 0, 1, 1); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_touch_events, ==, 1); + g_assert_cmpint (n_action_sequences_cancelled, ==, 0); + g_assert_cmpint (n_action_touch_events, ==, 0); + g_assert_cmpint (n_stage_touch_events, ==, 1); + + n_stage_touch_events = n_action_touch_events = n_child_touch_events = 0; + n_action_sequences_cancelled = 0; + clutter_virtual_input_device_notify_touch_up (virtual_pointer, now_us, 0); + wait_stage_updated (&was_updated); + g_assert_cmpint (n_child_touch_events, ==, 1); + g_assert_cmpint (n_action_sequences_cancelled, ==, 0); + g_assert_cmpint (n_action_touch_events, ==, 0); + g_assert_cmpint (n_stage_touch_events, ==, 1); + + g_signal_handlers_disconnect_by_func (child, on_event_return_propagate, &n_child_touch_events); + clutter_actor_destroy (child); + g_signal_handlers_disconnect_by_func (stage, on_event_return_stop, &n_stage_touch_events); + g_signal_handlers_disconnect_by_func (stage, on_after_update, &was_updated); +} + CLUTTER_TEST_SUITE ( CLUTTER_TEST_UNIT ("/event/delivery/consecutive-touch-begin-end", event_delivery_consecutive_touch_begin_end); + CLUTTER_TEST_UNIT ("/event/delivery/implicit-grabbing", event_delivery_implicit_grabbing); + CLUTTER_TEST_UNIT ("/event/delivery/implicit-grab-cancelled", event_delivery_implicit_grab_cancelled); + CLUTTER_TEST_UNIT ("/event/delivery/implicit-grab-existing-clutter-grab", event_delivery_implicit_grab_existing_clutter_grab); + CLUTTER_TEST_UNIT ("/event/delivery/stop-discrete-event", event_delivery_stop_discrete_event); + CLUTTER_TEST_UNIT ("/event/delivery/actor-stop-sequence-event", event_delivery_actor_stop_sequence_event); )