From e917b7de43550d50b9087f5774696f5d32b22bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Thu, 7 Apr 2022 12:10:13 +0200 Subject: [PATCH] clutter/stage: Add input-only grabs An input only grab is a ClutterGrab on the stage that doesn't have an explicit actor associated with it. This is useful for cases where event should be captured as if focus was stolen to some mysterious place that doesn't have anything in the scene graph that represents it. Internally, it's implemented using a 0x0 sized actor attached directly to the stage, and a clutter action that consumes the events. An input-only grab takes a handler, user data and a destroy function for the user data. These are handed to the ClutterAction, which handles the actual event handling. Part-of: --- clutter/clutter/clutter-input-only-action.c | 97 +++++++++++++++ clutter/clutter/clutter-input-only-action.h | 38 ++++++ clutter/clutter/clutter-input-only-actor.c | 60 ++++++++++ clutter/clutter/clutter-input-only-actor.h | 36 ++++++ clutter/clutter/clutter-stage-private.h | 8 ++ clutter/clutter/clutter-stage.c | 86 +++++++++++--- clutter/clutter/meson.build | 4 + src/tests/clutter/conform/grab.c | 124 +++++++++++++++++++- 8 files changed, 431 insertions(+), 22 deletions(-) create mode 100644 clutter/clutter/clutter-input-only-action.c create mode 100644 clutter/clutter/clutter-input-only-action.h create mode 100644 clutter/clutter/clutter-input-only-actor.c create mode 100644 clutter/clutter/clutter-input-only-actor.h 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); )