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: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2628>
This commit is contained in:
Jonas Ådahl 2022-04-07 12:10:13 +02:00 committed by Carlos Garnacho
parent a79f35612b
commit e917b7de43
8 changed files with 431 additions and 22 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 */

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 */

View File

@ -32,6 +32,8 @@
G_BEGIN_DECLS G_BEGIN_DECLS
typedef gboolean (* ClutterEventHandler) (const ClutterEvent *event,
gpointer user_data);
typedef enum typedef enum
{ {
CLUTTER_DEVICE_UPDATE_NONE = 0, 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, void clutter_stage_add_to_redraw_clip (ClutterStage *self,
ClutterPaintVolume *clip); ClutterPaintVolume *clip);
CLUTTER_EXPORT
ClutterGrab * clutter_stage_grab_input_only (ClutterStage *self,
ClutterEventHandler handler,
gpointer user_data,
GDestroyNotify user_data_destroy);
G_END_DECLS G_END_DECLS
#endif /* __CLUTTER_STAGE_PRIVATE_H__ */ #endif /* __CLUTTER_STAGE_PRIVATE_H__ */

View File

@ -57,6 +57,7 @@
#include "clutter-frame.h" #include "clutter-frame.h"
#include "clutter-grab.h" #include "clutter-grab.h"
#include "clutter-input-device-private.h" #include "clutter-input-device-private.h"
#include "clutter-input-only-actor.h"
#include "clutter-main.h" #include "clutter-main.h"
#include "clutter-marshal.h" #include "clutter-marshal.h"
#include "clutter-mutter.h" #include "clutter-mutter.h"
@ -148,7 +149,10 @@ struct _ClutterGrab
{ {
grefcount ref_count; grefcount ref_count;
ClutterStage *stage; ClutterStage *stage;
ClutterActor *actor; ClutterActor *actor;
gboolean owns_actor;
ClutterGrab *prev; ClutterGrab *prev;
ClutterGrab *next; ClutterGrab *next;
}; };
@ -3866,20 +3870,28 @@ clutter_grab_unref (ClutterGrab *grab)
G_DEFINE_BOXED_TYPE (ClutterGrab, clutter_grab, G_DEFINE_BOXED_TYPE (ClutterGrab, clutter_grab,
clutter_grab_ref, clutter_grab_unref) clutter_grab_ref, clutter_grab_unref)
/** static ClutterGrab *
* clutter_stage_grab: clutter_grab_new (ClutterStage *stage,
* @stage: The #ClutterStage ClutterActor *actor,
* @actor: The actor grabbing input gboolean owns_actor)
* {
* Grabs input onto a certain actor. Events will be propagated as ClutterGrab *grab;
* usual inside its hierarchy.
* grab = g_new0 (ClutterGrab, 1);
* Returns: (transfer full): an opaque #ClutterGrab handle, drop g_ref_count_init (&grab->ref_count);
* with [method@Grab.dismiss] grab->stage = stage;
**/
ClutterGrab * grab->actor = actor;
clutter_stage_grab (ClutterStage *stage, if (owns_actor)
ClutterActor *actor) grab->owns_actor = TRUE;
return grab;
}
static ClutterGrab *
clutter_stage_grab_full (ClutterStage *stage,
ClutterActor *actor,
gboolean owns_actor)
{ {
ClutterStagePrivate *priv; ClutterStagePrivate *priv;
ClutterGrab *grab; ClutterGrab *grab;
@ -3904,10 +3916,8 @@ clutter_stage_grab (ClutterStage *stage,
clutter_seat_grab (seat, clutter_get_current_event_time ()); clutter_seat_grab (seat, clutter_get_current_event_time ());
} }
grab = g_new0 (ClutterGrab, 1); grab = clutter_grab_new (stage, actor, owns_actor);
g_ref_count_init (&grab->ref_count);
grab->stage = stage;
grab->actor = actor;
grab->prev = NULL; grab->prev = NULL;
grab->next = priv->topmost_grab; grab->next = priv->topmost_grab;
@ -3935,6 +3945,43 @@ clutter_stage_grab (ClutterStage *stage,
return grab; 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 void
clutter_stage_unlink_grab (ClutterStage *stage, clutter_stage_unlink_grab (ClutterStage *stage,
ClutterGrab *grab) ClutterGrab *grab)
@ -3991,6 +4038,9 @@ clutter_stage_unlink_grab (ClutterStage *stage,
grab->next = NULL; grab->next = NULL;
grab->prev = NULL; grab->prev = NULL;
if (grab->owns_actor)
g_clear_pointer (&grab->actor, clutter_actor_destroy);
} }
/** /**

View File

@ -140,6 +140,8 @@ clutter_sources = [
'clutter-input-focus.c', 'clutter-input-focus.c',
'clutter-input-method.c', 'clutter-input-method.c',
'clutter-input-pointer-a11y.c', 'clutter-input-pointer-a11y.c',
'clutter-input-only-action.c',
'clutter-input-only-actor.c',
'clutter-virtual-input-device.c', 'clutter-virtual-input-device.c',
'clutter-interval.c', 'clutter-interval.c',
'clutter-keyframe-transition.c', 'clutter-keyframe-transition.c',
@ -208,6 +210,8 @@ clutter_private_headers = [
'clutter-input-focus-private.h', 'clutter-input-focus-private.h',
'clutter-input-method-private.h', 'clutter-input-method-private.h',
'clutter-input-pointer-a11y-private.h', 'clutter-input-pointer-a11y-private.h',
'clutter-input-only-action.h',
'clutter-input-only-actor.h',
'clutter-keymap-private.h', 'clutter-keymap-private.h',
'clutter-offscreen-effect-private.h', 'clutter-offscreen-effect-private.h',
'clutter-paint-context-private.h', 'clutter-paint-context-private.h',

View File

@ -3,6 +3,7 @@
#include "tests/clutter-test-utils.h" #include "tests/clutter-test-utils.h"
#include "clutter/clutter-event-private.h" #include "clutter/clutter-event-private.h"
#include "clutter/clutter-stage-private.h"
typedef struct typedef struct
{ {
@ -50,17 +51,30 @@ event_cb (ClutterActor *actor,
gpointer user_data) gpointer user_data)
{ {
GArray *events = user_data; GArray *events = user_data;
EventLog entry;
if ((event->type == CLUTTER_ENTER || switch (event->type)
event->type == CLUTTER_LEAVE) &&
(event->any.flags & CLUTTER_EVENT_FLAG_GRAB_NOTIFY) != 0)
{ {
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'", g_debug ("Event '%s' on actor '%s'",
clutter_event_get_name (event), clutter_event_get_name (event),
entry.name); entry.name);
g_array_append_val (events, entry); g_array_append_val (events, entry);
break;
} }
return CLUTTER_EVENT_PROPAGATE; return CLUTTER_EVENT_PROPAGATE;
@ -541,6 +555,107 @@ grab_key_focus_outside_grab (void)
test_data_shutdown (&data); 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_SUITE (
CLUTTER_TEST_UNIT ("/grab/grab-under-pointer", grab_under_pointer) CLUTTER_TEST_UNIT ("/grab/grab-under-pointer", grab_under_pointer)
CLUTTER_TEST_UNIT ("/grab/grab-under-pointers-parent", grab_under_pointers_parent) 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/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-in-grab", grab_key_focus_in_grab);
CLUTTER_TEST_UNIT ("/grab/key-focus-outside-grab", grab_key_focus_outside_grab); CLUTTER_TEST_UNIT ("/grab/key-focus-outside-grab", grab_key_focus_outside_grab);
CLUTTER_TEST_UNIT ("/grab/input-only", grab_input_only);
) )