diff --git a/clutter/clutter/clutter-input-only-action.c b/clutter/clutter/clutter-input-only-action.c new file mode 100644 index 000000000..98bd551e3 --- /dev/null +++ b/clutter/clutter/clutter-input-only-action.c @@ -0,0 +1,97 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2022 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "clutter-build-config.h" + +#include "clutter/clutter-input-only-action.h" + +#include "clutter/clutter-action-private.h" +#include "clutter/clutter.h" + +struct _ClutterInputOnlyAction +{ + ClutterAction parent; + + ClutterInputOnlyHandleEvent handle_event; + gpointer user_data; + GDestroyNotify user_data_destroy; +}; + +G_DEFINE_TYPE (ClutterInputOnlyAction, clutter_input_only_action, + CLUTTER_TYPE_ACTION) + +static void +clutter_input_only_action_dispose (GObject *object) +{ + ClutterInputOnlyAction *input_only_action = + CLUTTER_INPUT_ONLY_ACTION (object); + + if (input_only_action->user_data_destroy) + { + g_clear_pointer (&input_only_action->user_data, + input_only_action->user_data_destroy); + } + + G_OBJECT_CLASS (clutter_input_only_action_parent_class)->dispose (object); +} + +static gboolean +clutter_input_only_action_handle_event (ClutterAction *action, + const ClutterEvent *event) +{ + ClutterInputOnlyAction *input_only_action = + CLUTTER_INPUT_ONLY_ACTION (action); + + return input_only_action->handle_event (event, input_only_action->user_data); +} + +static void +clutter_input_only_action_class_init (ClutterInputOnlyActionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActionClass *action_class = CLUTTER_ACTION_CLASS (klass); + + object_class->finalize = clutter_input_only_action_dispose; + + action_class->handle_event = clutter_input_only_action_handle_event; +} + +static void +clutter_input_only_action_init (ClutterInputOnlyAction *input_only_action) +{ +} + +ClutterInputOnlyAction * +clutter_input_only_action_new (ClutterInputOnlyHandleEvent handle_event, + gpointer user_data, + GDestroyNotify user_data_destroy) +{ + ClutterInputOnlyAction *input_only_action; + + input_only_action = g_object_new (CLUTTER_TYPE_INPUT_ONLY_ACTION, NULL); + input_only_action->handle_event = handle_event; + input_only_action->user_data = user_data; + input_only_action->user_data_destroy = user_data_destroy; + clutter_action_set_phase (CLUTTER_ACTION (input_only_action), + CLUTTER_PHASE_CAPTURE); + + return input_only_action; +} diff --git a/clutter/clutter/clutter-input-only-action.h b/clutter/clutter/clutter-input-only-action.h new file mode 100644 index 000000000..22cb2e205 --- /dev/null +++ b/clutter/clutter/clutter-input-only-action.h @@ -0,0 +1,38 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2022 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef CLUTTER_INPUT_ONLY_ACTION_H +#define CLUTTER_INPUT_ONLY_ACTION_H + +#include "clutter/clutter.h" + +typedef gboolean (* ClutterInputOnlyHandleEvent) (const ClutterEvent *event, + gpointer user_data); + +#define CLUTTER_TYPE_INPUT_ONLY_ACTION (clutter_input_only_action_get_type ()) +G_DECLARE_FINAL_TYPE (ClutterInputOnlyAction, clutter_input_only_action, + CLUTTER, INPUT_ONLY_ACTION, ClutterAction) + +ClutterInputOnlyAction * clutter_input_only_action_new (ClutterInputOnlyHandleEvent handle_event, + gpointer user_data, + GDestroyNotify destroy); + +#endif /* CLUTTER_INPUT_ONLY_ACTION_H */ diff --git a/clutter/clutter/clutter-input-only-actor.c b/clutter/clutter/clutter-input-only-actor.c new file mode 100644 index 000000000..bb1e709c0 --- /dev/null +++ b/clutter/clutter/clutter-input-only-actor.c @@ -0,0 +1,60 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2022 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "clutter-build-config.h" + +#include "clutter-input-only-actor.h" + +#include "clutter-input-only-action.h" + +struct _ClutterInputOnlyActor +{ + ClutterActor parent; +}; + +G_DEFINE_TYPE (ClutterInputOnlyActor, clutter_input_only_actor, + CLUTTER_TYPE_ACTOR) + +static void +clutter_input_only_actor_class_init (ClutterInputOnlyActorClass *klass) +{ +} + +static void +clutter_input_only_actor_init (ClutterInputOnlyActor *input_only_actor) +{ +} + +ClutterInputOnlyActor * +clutter_input_only_actor_new (ClutterInputOnlyHandleEvent handle_event, + gpointer user_data, + GDestroyNotify user_data_destroy) +{ + ClutterInputOnlyAction *input_only_action; + + input_only_action = clutter_input_only_action_new (handle_event, + user_data, + user_data_destroy); + return g_object_new (CLUTTER_TYPE_INPUT_ONLY_ACTOR, + "reactive", TRUE, + "actions", input_only_action, + NULL); +} diff --git a/clutter/clutter/clutter-input-only-actor.h b/clutter/clutter/clutter-input-only-actor.h new file mode 100644 index 000000000..6217915c0 --- /dev/null +++ b/clutter/clutter/clutter-input-only-actor.h @@ -0,0 +1,36 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2022 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef CLUTTER_INPUT_ONLY_ACTOR_H +#define CLUTTER_INPUT_ONLY_ACTOR_H + +#include "clutter/clutter.h" +#include "clutter/clutter-stage-private.h" + +#define CLUTTER_TYPE_INPUT_ONLY_ACTOR (clutter_input_only_actor_get_type ()) +G_DECLARE_FINAL_TYPE (ClutterInputOnlyActor, clutter_input_only_actor, + CLUTTER, INPUT_ONLY_ACTOR, ClutterActor) + +ClutterInputOnlyActor * clutter_input_only_actor_new (ClutterEventHandler event_handler, + gpointer user_data, + GDestroyNotify destroy); + +#endif /* CLUTTER_INPUT_ONLY_ACTOR_H */ diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index e88c17d37..fb973088c 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -32,6 +32,8 @@ G_BEGIN_DECLS +typedef gboolean (* ClutterEventHandler) (const ClutterEvent *event, + gpointer user_data); typedef enum { CLUTTER_DEVICE_UPDATE_NONE = 0, @@ -163,6 +165,12 @@ void clutter_stage_notify_action_implicit_grab (ClutterStage *self, void clutter_stage_add_to_redraw_clip (ClutterStage *self, ClutterPaintVolume *clip); +CLUTTER_EXPORT +ClutterGrab * clutter_stage_grab_input_only (ClutterStage *self, + ClutterEventHandler handler, + gpointer user_data, + GDestroyNotify user_data_destroy); + G_END_DECLS #endif /* __CLUTTER_STAGE_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 048c46a58..b879db9c7 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -57,6 +57,7 @@ #include "clutter-frame.h" #include "clutter-grab.h" #include "clutter-input-device-private.h" +#include "clutter-input-only-actor.h" #include "clutter-main.h" #include "clutter-marshal.h" #include "clutter-mutter.h" @@ -148,7 +149,10 @@ struct _ClutterGrab { grefcount ref_count; ClutterStage *stage; + ClutterActor *actor; + gboolean owns_actor; + ClutterGrab *prev; ClutterGrab *next; }; @@ -3866,20 +3870,28 @@ clutter_grab_unref (ClutterGrab *grab) G_DEFINE_BOXED_TYPE (ClutterGrab, clutter_grab, clutter_grab_ref, clutter_grab_unref) -/** - * clutter_stage_grab: - * @stage: The #ClutterStage - * @actor: The actor grabbing input - * - * Grabs input onto a certain actor. Events will be propagated as - * usual inside its hierarchy. - * - * Returns: (transfer full): an opaque #ClutterGrab handle, drop - * with [method@Grab.dismiss] - **/ -ClutterGrab * -clutter_stage_grab (ClutterStage *stage, - ClutterActor *actor) +static ClutterGrab * +clutter_grab_new (ClutterStage *stage, + ClutterActor *actor, + gboolean owns_actor) +{ + ClutterGrab *grab; + + grab = g_new0 (ClutterGrab, 1); + g_ref_count_init (&grab->ref_count); + grab->stage = stage; + + grab->actor = actor; + if (owns_actor) + grab->owns_actor = TRUE; + + return grab; +} + +static ClutterGrab * +clutter_stage_grab_full (ClutterStage *stage, + ClutterActor *actor, + gboolean owns_actor) { ClutterStagePrivate *priv; ClutterGrab *grab; @@ -3904,10 +3916,8 @@ clutter_stage_grab (ClutterStage *stage, clutter_seat_grab (seat, clutter_get_current_event_time ()); } - grab = g_new0 (ClutterGrab, 1); - g_ref_count_init (&grab->ref_count); - grab->stage = stage; - grab->actor = actor; + grab = clutter_grab_new (stage, actor, owns_actor); + grab->prev = NULL; grab->next = priv->topmost_grab; @@ -3935,6 +3945,43 @@ clutter_stage_grab (ClutterStage *stage, return grab; } +/** + * clutter_stage_grab: + * @stage: The #ClutterStage + * @actor: The actor grabbing input + * + * Grabs input onto a certain actor. Events will be propagated as + * usual inside its hierarchy. + * + * Returns: (transfer full): an opaque #ClutterGrab handle, drop + * with [method@Grab.dismiss] + **/ +ClutterGrab * +clutter_stage_grab (ClutterStage *stage, + ClutterActor *actor) +{ + return clutter_stage_grab_full (stage, actor, FALSE); +} + +ClutterGrab * +clutter_stage_grab_input_only (ClutterStage *stage, + ClutterEventHandler handler, + gpointer user_data, + GDestroyNotify user_data_destroy) +{ + ClutterInputOnlyActor *input_only_actor; + ClutterActor *actor; + + input_only_actor = clutter_input_only_actor_new (handler, user_data, + user_data_destroy); + actor = CLUTTER_ACTOR (input_only_actor); + clutter_actor_set_name (actor, "input only grab actor"); + + clutter_actor_insert_child_at_index (CLUTTER_ACTOR (stage), actor, 0); + + return clutter_stage_grab_full (stage, actor, TRUE); +} + void clutter_stage_unlink_grab (ClutterStage *stage, ClutterGrab *grab) @@ -3991,6 +4038,9 @@ clutter_stage_unlink_grab (ClutterStage *stage, grab->next = NULL; grab->prev = NULL; + + if (grab->owns_actor) + g_clear_pointer (&grab->actor, clutter_actor_destroy); } /** diff --git a/clutter/clutter/meson.build b/clutter/clutter/meson.build index 8a96e3150..0a3270523 100644 --- a/clutter/clutter/meson.build +++ b/clutter/clutter/meson.build @@ -140,6 +140,8 @@ clutter_sources = [ 'clutter-input-focus.c', 'clutter-input-method.c', 'clutter-input-pointer-a11y.c', + 'clutter-input-only-action.c', + 'clutter-input-only-actor.c', 'clutter-virtual-input-device.c', 'clutter-interval.c', 'clutter-keyframe-transition.c', @@ -208,6 +210,8 @@ clutter_private_headers = [ 'clutter-input-focus-private.h', 'clutter-input-method-private.h', 'clutter-input-pointer-a11y-private.h', + 'clutter-input-only-action.h', + 'clutter-input-only-actor.h', 'clutter-keymap-private.h', 'clutter-offscreen-effect-private.h', 'clutter-paint-context-private.h', diff --git a/src/tests/clutter/conform/grab.c b/src/tests/clutter/conform/grab.c index 4040773d1..1e28606a4 100644 --- a/src/tests/clutter/conform/grab.c +++ b/src/tests/clutter/conform/grab.c @@ -3,6 +3,7 @@ #include "tests/clutter-test-utils.h" #include "clutter/clutter-event-private.h" +#include "clutter/clutter-stage-private.h" typedef struct { @@ -50,17 +51,30 @@ event_cb (ClutterActor *actor, gpointer user_data) { GArray *events = user_data; + EventLog entry; - if ((event->type == CLUTTER_ENTER || - event->type == CLUTTER_LEAVE) && - (event->any.flags & CLUTTER_EVENT_FLAG_GRAB_NOTIFY) != 0) + switch (event->type) { - EventLog entry = { clutter_actor_get_name (actor), event->type }; + case CLUTTER_ENTER: + case CLUTTER_LEAVE: + if ((event->any.flags & CLUTTER_EVENT_FLAG_GRAB_NOTIFY) != 0) + { + entry = (EventLog) { clutter_actor_get_name (actor), event->type }; + g_debug ("Event '%s' on actor '%s'", + clutter_event_get_name (event), + entry.name); + g_array_append_val (events, entry); + } + break; + + default: + entry = (EventLog) { clutter_actor_get_name (actor), event->type }; g_debug ("Event '%s' on actor '%s'", clutter_event_get_name (event), entry.name); g_array_append_val (events, entry); + break; } return CLUTTER_EVENT_PROPAGATE; @@ -541,6 +555,107 @@ grab_key_focus_outside_grab (void) test_data_shutdown (&data); } +static gboolean +handle_input_only_event (const ClutterEvent *event, + gpointer user_data) +{ + GArray *events = user_data; + EventLog entry = { "input-only grab", event->type }; + + g_debug ("Input only grab event '%s'", clutter_event_get_name (event)); + g_array_append_val (events, entry); + + return CLUTTER_EVENT_PROPAGATE; +} + +static gboolean +last_event_is (GArray *events, + ClutterEventType event_type) +{ + EventLog *entry; + + if (events->len == 0) + return FALSE; + + entry = &g_array_index (events, EventLog, events->len - 1); + return entry->type == event_type; +} + +static void +grab_input_only (void) +{ + TestData data; + ClutterGrab *grab; + EventLog grab1_log[] = { + { "b", CLUTTER_LEAVE }, + { "a", CLUTTER_LEAVE }, + { "stage", CLUTTER_LEAVE }, + { NULL, 0 }, + }; + EventLog grab2_log[] = { + { "input-only grab", CLUTTER_BUTTON_PRESS }, + { "input-only grab", CLUTTER_BUTTON_RELEASE }, + { NULL, 0 }, + }; + EventLog grab3_log[] = { + { "b", CLUTTER_ENTER }, + { "a", CLUTTER_ENTER }, + { "stage", CLUTTER_ENTER }, + { NULL, 0 }, + }; + EventLog grab4_log[] = { + { "b", CLUTTER_BUTTON_PRESS }, + { "a", CLUTTER_BUTTON_PRESS }, + { "stage", CLUTTER_BUTTON_PRESS }, + { "b", CLUTTER_BUTTON_RELEASE }, + { "a", CLUTTER_BUTTON_RELEASE }, + { "stage", CLUTTER_BUTTON_RELEASE }, + { NULL, 0 }, + }; + ClutterSeat *seat; + g_autoptr (ClutterVirtualInputDevice) pointer = NULL; + + seat = clutter_backend_get_default_seat (clutter_get_default_backend ()); + pointer = clutter_seat_create_virtual_device (seat, CLUTTER_POINTER_DEVICE); + + test_data_init (&data); + + grab = clutter_stage_grab_input_only (CLUTTER_STAGE (data.stage), + handle_input_only_event, + data.events, NULL); + event_log_compare ((EventLog *) &grab1_log, data.events); + + clutter_virtual_input_device_notify_button (pointer, + 0, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_PRESSED); + clutter_virtual_input_device_notify_button (pointer, + 0, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_RELEASED); + + while (!last_event_is (data.events, CLUTTER_BUTTON_RELEASE)) + g_main_context_iteration (NULL, TRUE); + event_log_compare ((EventLog *) &grab2_log, data.events); + + clutter_grab_unref (grab); + event_log_compare ((EventLog *) &grab3_log, data.events); + + clutter_virtual_input_device_notify_button (pointer, + 0, + CLUTTER_BUTTON_SECONDARY, + CLUTTER_BUTTON_STATE_PRESSED); + clutter_virtual_input_device_notify_button (pointer, + 0, + CLUTTER_BUTTON_SECONDARY, + CLUTTER_BUTTON_STATE_RELEASED); + while (!last_event_is (data.events, CLUTTER_BUTTON_RELEASE)) + g_main_context_iteration (NULL, TRUE); + event_log_compare ((EventLog *) &grab4_log, data.events); + + test_data_shutdown (&data); +} + CLUTTER_TEST_SUITE ( CLUTTER_TEST_UNIT ("/grab/grab-under-pointer", grab_under_pointer) CLUTTER_TEST_UNIT ("/grab/grab-under-pointers-parent", grab_under_pointers_parent) @@ -552,4 +667,5 @@ CLUTTER_TEST_SUITE ( CLUTTER_TEST_UNIT ("/grab/grab-unordered-ungrab-2", grab_unordered_ungrab_2) CLUTTER_TEST_UNIT ("/grab/key-focus-in-grab", grab_key_focus_in_grab); CLUTTER_TEST_UNIT ("/grab/key-focus-outside-grab", grab_key_focus_outside_grab); + CLUTTER_TEST_UNIT ("/grab/input-only", grab_input_only); )