From a23d08c754f0de575d3419287d9098d8be7ee64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 29 Nov 2024 12:42:11 +0100 Subject: [PATCH 1/3] Add way to pass construct time options to plugin This is needed to allow tests to manipulate the behavior of the test shell plugin during startup. Since the plugin is created and started when the MetaDisplay is created, it needs to be handled via MetaContext, by setting the options after creating the context, but before starting. For simplicity reasons, make the options an opaque GVariant, passed via a an `"options"` property when the plugin object is created, if the passed options is non-NULL. Only passing the options when non-NULL allows for backward compatibility. Part-of: --- src/compositor/compositor-private.h | 1 + src/compositor/compositor.c | 3 ++- src/compositor/meta-plugin-manager.c | 16 ++++++++++++++-- src/compositor/meta-plugin-manager.h | 3 ++- src/core/display-private.h | 1 + src/core/display.c | 3 ++- src/core/meta-context-private.h | 4 ++++ src/core/meta-context.c | 15 ++++++++++++++- 8 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/compositor/compositor-private.h b/src/compositor/compositor-private.h index b1f465c01e2..a2b54071651 100644 --- a/src/compositor/compositor-private.h +++ b/src/compositor/compositor-private.h @@ -91,6 +91,7 @@ void meta_compositor_grab_end (MetaCompositor *compositor); void meta_compositor_destroy (MetaCompositor *compositor); gboolean meta_compositor_manage (MetaCompositor *compositor, + GVariant *plugin_options, GError **error); void meta_compositor_unmanage (MetaCompositor *compositor); diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c index e0b44a47e3e..51b4228bf85 100644 --- a/src/compositor/compositor.c +++ b/src/compositor/compositor.c @@ -319,6 +319,7 @@ meta_compositor_create_view (MetaCompositor *compositor, gboolean meta_compositor_manage (MetaCompositor *compositor, + GVariant *plugin_options, GError **error) { MetaCompositorPrivate *priv = @@ -346,7 +347,7 @@ meta_compositor_manage (MetaCompositor *compositor, if (!META_COMPOSITOR_GET_CLASS (compositor)->manage (compositor, error)) return FALSE; - priv->plugin_mgr = meta_plugin_manager_new (compositor); + priv->plugin_mgr = meta_plugin_manager_new (compositor, plugin_options); meta_plugin_manager_start (priv->plugin_mgr); return TRUE; diff --git a/src/compositor/meta-plugin-manager.c b/src/compositor/meta-plugin-manager.c index fb38917b5ce..65b7fa66c89 100644 --- a/src/compositor/meta-plugin-manager.c +++ b/src/compositor/meta-plugin-manager.c @@ -113,7 +113,8 @@ on_prepare_shutdown (MetaContext *context, } MetaPluginManager * -meta_plugin_manager_new (MetaCompositor *compositor) +meta_plugin_manager_new (MetaCompositor *compositor, + GVariant *plugin_options) { MetaBackend *backend = meta_compositor_get_backend (compositor); MetaMonitorManager *monitor_manager = @@ -123,10 +124,21 @@ meta_plugin_manager_new (MetaCompositor *compositor) MetaDisplay *display; MetaContext *context; + if (plugin_options) + { + plugin = g_object_new (plugin_type, + "options", plugin_options, + NULL); + } + else + { + plugin = g_object_new (plugin_type, NULL); + } + plugin_mgr = g_new0 (MetaPluginManager, 1); plugin_mgr->state = PLUGIN_MANAGER_STATE_STARTING; plugin_mgr->compositor = compositor; - plugin_mgr->plugin = plugin = g_object_new (plugin_type, NULL); + plugin_mgr->plugin = plugin; _meta_plugin_set_compositor (plugin, compositor); diff --git a/src/compositor/meta-plugin-manager.h b/src/compositor/meta-plugin-manager.h index f011d627615..8e5251f3f1c 100644 --- a/src/compositor/meta-plugin-manager.h +++ b/src/compositor/meta-plugin-manager.h @@ -42,7 +42,8 @@ typedef enum */ typedef struct MetaPluginManager MetaPluginManager; -MetaPluginManager * meta_plugin_manager_new (MetaCompositor *compositor); +MetaPluginManager * meta_plugin_manager_new (MetaCompositor *compositor, + GVariant *plugin_options); void meta_plugin_manager_start (MetaPluginManager *plugin_mgr); diff --git a/src/core/display-private.h b/src/core/display-private.h index b60a9587b2d..15f3e38eebb 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -189,6 +189,7 @@ struct _MetaDisplayClass ) MetaDisplay * meta_display_new (MetaContext *context, + GVariant *plugin_options, GError **error); #ifdef HAVE_X11_CLIENT diff --git a/src/core/display.c b/src/core/display.c index 03f3ad0d81f..960d071daee 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -938,6 +938,7 @@ meta_display_shutdown_x11 (MetaDisplay *display) MetaDisplay * meta_display_new (MetaContext *context, + GVariant *plugin_options, GError **error) { MetaBackend *backend = meta_context_get_backend (context); @@ -1063,7 +1064,7 @@ meta_display_new (MetaContext *context, display->last_focus_time = timestamp; display->last_user_time = timestamp; - if (!meta_compositor_manage (display->compositor, error)) + if (!meta_compositor_manage (display->compositor, plugin_options, error)) { g_object_unref (display); return NULL; diff --git a/src/core/meta-context-private.h b/src/core/meta-context-private.h index 2f46ab9944c..a53f901fb6d 100644 --- a/src/core/meta-context-private.h +++ b/src/core/meta-context-private.h @@ -91,3 +91,7 @@ void meta_context_set_trace_file (MetaContext *context, #endif MetaSessionManager * meta_context_get_session_manager (MetaContext *context); + +META_EXPORT_TEST +void meta_context_set_plugin_options (MetaContext *context, + GVariant *plugin_options); diff --git a/src/core/meta-context.c b/src/core/meta-context.c index dd9bd4974cb..996dcdd078c 100644 --- a/src/core/meta-context.c +++ b/src/core/meta-context.c @@ -78,6 +78,7 @@ typedef struct _MetaContextPrivate char *nick; char *plugin_name; GType plugin_gtype; + GVariant *plugin_options; char *gnome_wm_keybindings; gboolean unsafe_mode; @@ -185,6 +186,15 @@ meta_context_set_plugin_name (MetaContext *context, priv->plugin_name = g_strdup (plugin_name); } +void +meta_context_set_plugin_options (MetaContext *context, + GVariant *plugin_options) +{ + MetaContextPrivate *priv = meta_context_get_instance_private (context); + + priv->plugin_options = g_variant_ref (plugin_options); +} + void meta_context_set_gnome_wm_keybindings (MetaContext *context, const char *wm_keybindings) @@ -510,6 +520,7 @@ meta_context_start (MetaContext *context, GError **error) { MetaContextPrivate *priv = meta_context_get_instance_private (context); + g_autoptr (GVariant) plugin_options = NULL; g_return_val_if_fail (META_IS_CONTEXT (context), FALSE); @@ -523,7 +534,8 @@ meta_context_start (MetaContext *context, priv->wayland_compositor = meta_wayland_compositor_new (context); #endif - priv->display = meta_display_new (context, error); + plugin_options = g_steal_pointer (&priv->plugin_options), + priv->display = meta_display_new (context, plugin_options, error); if (!priv->display) { priv->state = META_CONTEXT_STATE_TERMINATED; @@ -882,6 +894,7 @@ meta_context_finalize (GObject *object) g_clear_pointer (&priv->trace_file, g_free); #endif + g_clear_pointer (&priv->plugin_options, g_variant_unref); g_clear_pointer (&priv->gnome_wm_keybindings, g_free); g_clear_pointer (&priv->plugin_name, g_free); g_clear_pointer (&priv->name, g_free); -- GitLab From 904c39116ebdbc6a3f6ea86ba360a60a158f287e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 29 Nov 2024 12:46:35 +0100 Subject: [PATCH 2/3] tests/test-shell: Allow skipping showing the stage To allow tests to emulate GNOME Shell behavior, where the stage showing is delayed. Do so by adding the `"options"` property to the plugin object type. The property expects a vararg GVariant, and supports the `"show-stage"` (boolean) vararg entry. Part-of: --- src/tests/meta-test-shell.c | 61 ++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/tests/meta-test-shell.c b/src/tests/meta-test-shell.c index 13676920bd3..e4037eb834d 100644 --- a/src/tests/meta-test-shell.c +++ b/src/tests/meta-test-shell.c @@ -33,6 +33,17 @@ #include "meta/util.h" #include "meta/window.h" +enum +{ + PROP_0, + + PROP_OPTIONS, + + N_PROPS +}; + +static GParamSpec *obj_props[N_PROPS]; + typedef enum { ANIMATION_DESTROY, @@ -66,6 +77,8 @@ struct _MetaTestShell ClutterGrab *grab; ClutterActor *prev_focus; } overview; + + gboolean show_stage; }; typedef struct _ActorPrivate @@ -348,7 +361,8 @@ meta_test_shell_start (MetaPlugin *plugin) G_CALLBACK (prepare_shutdown), test_shell); - clutter_actor_show (meta_get_stage_for_display (display)); + if (test_shell->show_stage) + clutter_actor_show (meta_get_stage_for_display (display)); } static void @@ -779,11 +793,46 @@ meta_test_shell_kill_window_effects (MetaPlugin *plugin, finish_timeline (actor_priv->destroy_timeline); } +static void +process_options (MetaTestShell *test_shell, + GVariant *options) +{ + gboolean show_stage; + + if (!options) + return; + + if (g_variant_lookup (options, "show-stage", "b", &show_stage)) + test_shell->show_stage = show_stage; +} + +static void +meta_test_shell_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MetaTestShell *test_shell = META_TEST_SHELL (object); + + switch (prop_id) + { + case PROP_OPTIONS: + process_options (test_shell, g_value_get_variant (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + static void meta_test_shell_class_init (MetaTestShellClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); MetaPluginClass *plugin_class = META_PLUGIN_CLASS (klass); + object_class->set_property = meta_test_shell_set_property; + plugin_class->start = meta_test_shell_start; plugin_class->map = meta_test_shell_map; plugin_class->minimize = meta_test_shell_minimize; @@ -793,9 +842,19 @@ meta_test_shell_class_init (MetaTestShellClass *klass) plugin_class->hide_tile_preview = meta_test_shell_hide_tile_preview; plugin_class->kill_window_effects = meta_test_shell_kill_window_effects; plugin_class->kill_switch_workspace = meta_test_shell_kill_switch_workspace; + + obj_props[PROP_OPTIONS] = + g_param_spec_variant ("options", NULL, NULL, + G_VARIANT_TYPE_VARDICT, + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPS, obj_props); } static void meta_test_shell_init (MetaTestShell *test_shell) { + test_shell->show_stage = TRUE; } -- GitLab From 24083e1e5851a13b9d307d538d84f42091a7b220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 29 Nov 2024 12:50:13 +0100 Subject: [PATCH 3/3] stage: Clear `update_scheduled` field when update discarded clutter_stage_schedule_update() sets the field `update_scheduled` to `TRUE` as an optimization to make redundant updates a no-op. This failed if there was a pending event and if the stage was not yet mapped. What happened is: * clutter_stage_schedule_update() is called - ClutterStage::update_scheduled is set to TRUE - frame clock scheduled * frame clock dispatches - frame is discarded early, no actual stage update happens * device is created (e.g. virtual device from remote desktop session) - `device-added` event reaches ClutterStage::event_queue * stage is shown - clutter_stage_schedule_update() is called - ClutterStage::update_scheduled is TRUE - ClutterStage::event_queue has events in it - These two conditions means clutter_schedule_update() becomes a no-op At this point, no more updates will happen from clutter_stage_schedule_update(). Fix this by resetting `ClutterStage::update_scheduled` to `FALSE` even if the frame was discarded due to the stage not yet being mapped. A test case is added that replicates the above descibed events. Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/3804 Part-of: --- clutter/clutter/clutter-stage-private.h | 3 + clutter/clutter/clutter-stage-view.c | 11 +-- clutter/clutter/clutter-stage.c | 10 +++ src/tests/meson.build | 5 ++ src/tests/stage-tests.c | 102 ++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 src/tests/stage-tests.c diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index 0520cae8a43..d390e565ac0 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -62,6 +62,9 @@ void clutter_stage_emit_after_paint (ClutterStage void clutter_stage_after_update (ClutterStage *stage, ClutterStageView *view, ClutterFrame *frame); +void clutter_stage_frame_discarded (ClutterStage *stage, + ClutterStageView *view, + ClutterFrame *frame); CLUTTER_EXPORT void _clutter_stage_set_window (ClutterStage *stage, diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c index 661d0934d70..2e0137bba6a 100644 --- a/clutter/clutter/clutter-stage-view.c +++ b/clutter/clutter/clutter-stage-view.c @@ -1050,11 +1050,12 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock, if (CLUTTER_ACTOR_IN_DESTRUCTION (stage)) return CLUTTER_FRAME_RESULT_IDLE; - if (!clutter_actor_is_realized (CLUTTER_ACTOR (stage))) - return CLUTTER_FRAME_RESULT_IDLE; - - if (!clutter_actor_is_mapped (CLUTTER_ACTOR (stage))) - return CLUTTER_FRAME_RESULT_IDLE; + if (!clutter_actor_is_realized (CLUTTER_ACTOR (stage)) || + !clutter_actor_is_mapped (CLUTTER_ACTOR (stage))) + { + clutter_stage_frame_discarded (stage, view, frame); + return CLUTTER_FRAME_RESULT_IDLE; + } if (clutter_context_get_show_fps (context)) begin_frame_timing_measurement (view); diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 2d390bbf50b..1e8496e5287 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -535,6 +535,16 @@ clutter_stage_after_update (ClutterStage *stage, priv->update_scheduled = FALSE; } +void +clutter_stage_frame_discarded (ClutterStage *stage, + ClutterStageView *view, + ClutterFrame *frame) +{ + ClutterStagePrivate *priv = clutter_stage_get_instance_private (stage); + + priv->update_scheduled = FALSE; +} + static gboolean clutter_stage_get_paint_volume (ClutterActor *self, ClutterPaintVolume *volume) diff --git a/src/tests/meson.build b/src/tests/meson.build index 935f990a331..ab359b36437 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -313,6 +313,11 @@ test_cases += [ '--compile-schemas', ], }, + { + 'name': 'stage', + 'suite': 'backend', + 'sources': [ 'stage-tests.c', ], + }, ] screen_cast_client = executable('mutter-screen-cast-client', diff --git a/src/tests/stage-tests.c b/src/tests/stage-tests.c new file mode 100644 index 00000000000..e5b96f3313c --- /dev/null +++ b/src/tests/stage-tests.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2024 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + +#include "config.h" + +#include "backends/meta-backend-private.h" +#include "core/meta-context-private.h" +#include "tests/meta-test-utils.h" +#include "tests/meta-test/meta-context-test.h" + +static MetaContext *test_context; + +static gboolean +event_filter_cb (const ClutterEvent *event, + ClutterActor *event_actor, + gpointer user_data) +{ + gboolean *saw_event = user_data; + + if (clutter_event_type (event) == CLUTTER_DEVICE_ADDED) + *saw_event = TRUE; + + return CLUTTER_EVENT_PROPAGATE; +} + +static void +meta_test_stage_scheduling_delayed_show (void) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + ClutterActor *stage = meta_backend_get_stage (backend); + ClutterSeat *seat = meta_backend_get_default_seat (backend); + g_autoptr (MetaVirtualMonitor) virtual_monitor = NULL; + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + guint filter_id; + gboolean saw_event = FALSE; + + virtual_monitor = meta_create_test_monitor (test_context, 800, 600, 60.0f); + g_debug ("Wait for initial dummy dispatch"); + while (TRUE) + { + if (!g_main_context_iteration (NULL, FALSE)) + break; + } + + filter_id = clutter_event_add_filter (NULL, event_filter_cb, NULL, &saw_event); + g_debug ("Creating virtual pointer"); + virtual_pointer = + clutter_seat_create_virtual_device (seat, CLUTTER_KEYBOARD_DEVICE); + while (!saw_event) + g_main_context_iteration (NULL, TRUE); + g_debug ("Scheduling update with DEVICE_ADDED in stage queue"); + clutter_stage_schedule_update (CLUTTER_STAGE (stage)); + g_debug ("Showing stage"); + clutter_actor_show (stage); + g_debug ("Waiting for paint"); + clutter_actor_queue_redraw (stage); + meta_wait_for_paint (test_context); + clutter_event_remove_filter (filter_id); +} + +int +main (int argc, + char **argv) +{ + g_autoptr (MetaContext) context = NULL; + g_auto (GVariantBuilder) plugin_options_builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr (GVariant) plugin_options = NULL; + + context = meta_create_test_context (META_CONTEXT_TEST_TYPE_HEADLESS, + META_CONTEXT_TEST_FLAG_NO_X11); + g_assert (meta_context_configure (context, &argc, &argv, NULL)); + + g_variant_builder_add (&plugin_options_builder, "{sv}", + "show-stage", g_variant_new_boolean (FALSE)); + plugin_options = + g_variant_ref_sink (g_variant_builder_end (&plugin_options_builder)); + meta_context_set_plugin_options (context, plugin_options); + + test_context = context; + + g_test_add_func ("/stage/scheduling/delayed-show", + meta_test_stage_scheduling_delayed_show); + + return meta_context_test_run_tests (META_CONTEXT_TEST (context), + META_TEST_RUN_FLAG_NONE); +} -- GitLab