diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h index 9ff4b2d4bd831d309dd1e41c29b75a6bf8d9d461..ed398bf210060e5caa29601855a9879a9df50ffc 100644 --- a/clutter/clutter/clutter-actor-private.h +++ b/clutter/clutter/clutter-actor-private.h @@ -313,7 +313,7 @@ void _clutter_actor_detach_clone void _clutter_actor_queue_redraw_on_clones (ClutterActor *actor); void _clutter_actor_queue_relayout_on_clones (ClutterActor *actor); void _clutter_actor_queue_only_relayout (ClutterActor *actor); -void _clutter_actor_queue_update_resource_scale_recursive (ClutterActor *actor); +void clutter_actor_clear_stage_views_recursive (ClutterActor *actor); gboolean _clutter_actor_get_real_resource_scale (ClutterActor *actor, float *resource_scale); @@ -321,6 +321,8 @@ gboolean _clutter_actor_get_real_resource_scale ClutterPaintNode * clutter_actor_create_texture_paint_node (ClutterActor *self, CoglTexture *texture); +void clutter_actor_update_stage_views (ClutterActor *self); + G_END_DECLS #endif /* __CLUTTER_ACTOR_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index df6b14338f15b4de21191718a167fb72c6b0230b..fe02d3abddbac038946055572d43e058576e0189 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -813,6 +813,8 @@ struct _ClutterActorPrivate gulong font_changed_id; gulong layout_changed_id; + GList *stage_views; + /* bitfields: KEEP AT THE END */ /* fixed position and sizes */ @@ -855,6 +857,7 @@ struct _ClutterActorPrivate guint had_effects_on_last_paint_volume_update : 1; guint needs_compute_resource_scale : 1; guint absolute_origin_changed : 1; + guint needs_update_stage_views : 1; }; enum @@ -1017,6 +1020,7 @@ enum TRANSITIONS_COMPLETED, TOUCH_EVENT, TRANSITION_STOPPED, + STAGE_VIEWS_CHANGED, LAST_SIGNAL }; @@ -1614,6 +1618,22 @@ clutter_actor_update_map_state (ClutterActor *self, #endif } +static void +queue_update_stage_views (ClutterActor *actor) +{ + while (actor && !actor->priv->needs_update_stage_views) + { + actor->priv->needs_update_stage_views = TRUE; + + /* We don't really need to update the stage-views of the actors up the + * hierarchy, we set the flag anyway though so we can avoid traversing + * the whole scenegraph when looking for actors which need an update + * in clutter_actor_update_stage_views(). + */ + actor = actor->priv->parent; + } +} + static void clutter_actor_real_map (ClutterActor *self) { @@ -1628,6 +1648,18 @@ clutter_actor_real_map (ClutterActor *self) self->priv->needs_paint_volume_update = TRUE; + /* We skip unmapped actors when updating the stage-views list, so if + * an actors list got invalidated while it was unmapped make sure to + * set priv->needs_update_stage_views to TRUE for all actors up the + * hierarchy now. + */ + if (self->priv->needs_update_stage_views) + { + /* Avoid the early return in queue_update_stage_views() */ + self->priv->needs_update_stage_views = FALSE; + queue_update_stage_views (self); + } + clutter_actor_ensure_resource_scale (self); /* notify on parent mapped before potentially mapping @@ -2565,6 +2597,7 @@ static void absolute_allocation_changed (ClutterActor *actor) { actor->priv->needs_compute_resource_scale = TRUE; + queue_update_stage_views (actor); } static ClutterActorTraverseVisitFlags @@ -4282,6 +4315,7 @@ typedef enum REMOVE_CHILD_FLUSH_QUEUE = 1 << 4, REMOVE_CHILD_NOTIFY_FIRST_LAST = 1 << 5, REMOVE_CHILD_STOP_TRANSITIONS = 1 << 6, + REMOVE_CHILD_CLEAR_STAGE_VIEWS = 1 << 7, /* default flags for public API */ REMOVE_CHILD_DEFAULT_FLAGS = REMOVE_CHILD_STOP_TRANSITIONS | @@ -4290,14 +4324,16 @@ typedef enum REMOVE_CHILD_EMIT_ACTOR_REMOVED | REMOVE_CHILD_CHECK_STATE | REMOVE_CHILD_FLUSH_QUEUE | - REMOVE_CHILD_NOTIFY_FIRST_LAST, + REMOVE_CHILD_NOTIFY_FIRST_LAST | + REMOVE_CHILD_CLEAR_STAGE_VIEWS, /* flags for legacy/deprecated API */ REMOVE_CHILD_LEGACY_FLAGS = REMOVE_CHILD_STOP_TRANSITIONS | REMOVE_CHILD_CHECK_STATE | REMOVE_CHILD_FLUSH_QUEUE | REMOVE_CHILD_EMIT_PARENT_SET | - REMOVE_CHILD_NOTIFY_FIRST_LAST + REMOVE_CHILD_NOTIFY_FIRST_LAST | + REMOVE_CHILD_CLEAR_STAGE_VIEWS } ClutterActorRemoveChildFlags; /*< private > @@ -4319,6 +4355,7 @@ clutter_actor_remove_child_internal (ClutterActor *self, gboolean notify_first_last; gboolean was_mapped; gboolean stop_transitions; + gboolean clear_stage_views; GObject *obj; if (self == child) @@ -4335,6 +4372,7 @@ clutter_actor_remove_child_internal (ClutterActor *self, flush_queue = (flags & REMOVE_CHILD_FLUSH_QUEUE) != 0; notify_first_last = (flags & REMOVE_CHILD_NOTIFY_FIRST_LAST) != 0; stop_transitions = (flags & REMOVE_CHILD_STOP_TRANSITIONS) != 0; + clear_stage_views = (flags & REMOVE_CHILD_CLEAR_STAGE_VIEWS) != 0; obj = G_OBJECT (self); g_object_freeze_notify (obj); @@ -4408,6 +4446,13 @@ clutter_actor_remove_child_internal (ClutterActor *self, clutter_actor_queue_compute_expand (self); } + /* Only actors which are attached to a stage get notified about changes + * to the stage views, so make sure all the stage-views lists are + * cleared as the child and its children leave the actor tree. + */ + if (clear_stage_views && !CLUTTER_ACTOR_IN_DESTRUCTION (child)) + clutter_actor_clear_stage_views_recursive (child); + if (emit_parent_set && !CLUTTER_ACTOR_IN_DESTRUCTION (child)) { child->priv->needs_compute_resource_scale = TRUE; @@ -6080,6 +6125,8 @@ clutter_actor_dispose (GObject *object) priv->clones = NULL; } + g_clear_pointer (&priv->stage_views, g_list_free); + G_OBJECT_CLASS (clutter_actor_parent_class)->dispose (object); } @@ -8616,6 +8663,27 @@ clutter_actor_class_init (ClutterActorClass *klass) g_signal_set_va_marshaller (actor_signals[TOUCH_EVENT], G_TYPE_FROM_CLASS (object_class), _clutter_marshal_BOOLEAN__BOXEDv); + + /** + * ClutterActor::stage-views-changed: + * @actor: a #ClutterActor + * + * The ::stage-views-changed signal is emitted when the position or + * size an actor is being painted at have changed so that it's visible + * on different stage views. + * + * This signal is also emitted when the actor gets detached from the stage + * or when the views of the stage have been invalidated and will be + * replaced; it's not emitted when the actor gets hidden. + */ + actor_signals[STAGE_VIEWS_CHANGED] = + g_signal_new (I_("stage-views-changed"), + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + } static void @@ -8634,6 +8702,7 @@ clutter_actor_init (ClutterActor *self) priv->needs_allocation = TRUE; priv->needs_paint_volume_update = TRUE; priv->needs_compute_resource_scale = TRUE; + priv->needs_update_stage_views = TRUE; priv->cached_width_age = 1; priv->cached_height_age = 1; @@ -17421,17 +17490,27 @@ _clutter_actor_get_resource_scale_for_rect (ClutterActor *self, float *resource_scale) { ClutterActor *stage; + g_autoptr (GList) views = NULL; + GList *l; float max_scale = 0; stage = _clutter_actor_get_stage_internal (self); if (!stage) return FALSE; - if (!_clutter_stage_get_max_view_scale_factor_for_rect (CLUTTER_STAGE (stage), - bounding_rect, - &max_scale)) + views = clutter_stage_get_views_for_rect (CLUTTER_STAGE (stage), + bounding_rect); + + if (!views) return FALSE; + for (l = views; l; l = l->next) + { + ClutterStageView *view = l->data; + + max_scale = MAX (clutter_stage_view_get_scale (view), max_scale); + } + *resource_scale = max_scale; return TRUE; @@ -17507,20 +17586,29 @@ _clutter_actor_compute_resource_scale (ClutterActor *self, } static ClutterActorTraverseVisitFlags -queue_update_resource_scale_cb (ClutterActor *actor, - int depth, - void *user_data) +clear_stage_views_cb (ClutterActor *actor, + int depth, + gpointer user_data) { + g_autoptr (GList) old_stage_views = NULL; + + actor->priv->needs_update_stage_views = TRUE; actor->priv->needs_compute_resource_scale = TRUE; + + old_stage_views = g_steal_pointer (&actor->priv->stage_views); + + if (old_stage_views) + g_signal_emit (actor, actor_signals[STAGE_VIEWS_CHANGED], 0); + return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE; } void -_clutter_actor_queue_update_resource_scale_recursive (ClutterActor *self) +clutter_actor_clear_stage_views_recursive (ClutterActor *self) { _clutter_actor_traverse (self, CLUTTER_ACTOR_TRAVERSE_DEPTH_FIRST, - queue_update_resource_scale_cb, + clear_stage_views_cb, NULL, NULL); } @@ -17612,6 +17700,125 @@ clutter_actor_get_resource_scale (ClutterActor *self, return FALSE; } +static gboolean +sorted_lists_equal (GList *list_a, + GList *list_b) +{ + GList *a, *b; + + if (!list_a && !list_b) + return TRUE; + + for (a = list_a, b = list_b; + a && b; + a = a->next, b = b->next) + { + if (a->data != b->data) + break; + + if (!a->next && !b->next) + return TRUE; + } + + return FALSE; +} + +static void +update_stage_views (ClutterActor *self) +{ + ClutterActorPrivate *priv = self->priv; + g_autoptr (GList) old_stage_views = NULL; + ClutterStage *stage; + graphene_rect_t bounding_rect; + + old_stage_views = g_steal_pointer (&priv->stage_views); + + if (priv->needs_allocation) + { + g_warning ("Can't update stage views actor %s is on because it needs an " + "allocation.", _clutter_actor_get_debug_name (self)); + goto out; + } + + stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self)); + g_return_if_fail (stage); + + clutter_actor_get_transformed_position (self, + &bounding_rect.origin.x, + &bounding_rect.origin.y); + clutter_actor_get_transformed_size (self, + &bounding_rect.size.width, + &bounding_rect.size.height); + + if (bounding_rect.size.width == 0.0 || + bounding_rect.size.height == 0.0) + goto out; + + priv->stage_views = clutter_stage_get_views_for_rect (stage, + &bounding_rect); + +out: + if (g_signal_has_handler_pending (self, actor_signals[STAGE_VIEWS_CHANGED], + 0, TRUE)) + { + if (!sorted_lists_equal (old_stage_views, priv->stage_views)) + g_signal_emit (self, actor_signals[STAGE_VIEWS_CHANGED], 0); + } +} + +void +clutter_actor_update_stage_views (ClutterActor *self) +{ + ClutterActorPrivate *priv = self->priv; + ClutterActor *child; + + if (!CLUTTER_ACTOR_IS_MAPPED (self) || + CLUTTER_ACTOR_IN_DESTRUCTION (self)) + return; + + if (!priv->needs_update_stage_views) + return; + + update_stage_views (self); + + priv->needs_update_stage_views = FALSE; + + for (child = priv->first_child; child; child = child->priv->next_sibling) + clutter_actor_update_stage_views (child); +} + +/** + * clutter_actor_peek_stage_views: + * @self: A #ClutterActor + * + * Retrieves the list of #ClutterStageViews the actor is being + * painted on. + * + * If this function is called during the paint cycle, the list is guaranteed + * to be up-to-date, if called outside the paint cycle, the list will + * contain the views the actor was painted on last. + * + * The list returned by this function is not updated when the actors + * visibility changes: If an actor gets hidden and is not being painted + * anymore, this function will return the list of views the actor was + * painted on last. + * + * If an actor is not attached to a stage (realized), this function will + * always return an empty list. + * + * Returns: (transfer none) (element-type Clutter.StageView): The list of + * #ClutterStageViews the actor is being painted on. The list and + * its contents are owned by the #ClutterActor and the list may not be + * freed or modified. + */ +GList * +clutter_actor_peek_stage_views (ClutterActor *self) +{ + g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE); + + return self->priv->stage_views; +} + /** * clutter_actor_has_overlaps: * @self: A #ClutterActor diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h index b5d4b7845e1cd9d444d4066b8da9eb1ff4337f35..616e8016999060024f8414a57111ab945dcb659c 100644 --- a/clutter/clutter/clutter-actor.h +++ b/clutter/clutter/clutter-actor.h @@ -919,6 +919,9 @@ void clutter_actor_pick_box (ClutterActor *self, ClutterPickContext *pick_context, const ClutterActorBox *box); +CLUTTER_EXPORT +GList * clutter_actor_peek_stage_views (ClutterActor *self); + G_END_DECLS #endif /* __CLUTTER_ACTOR_H__ */ diff --git a/clutter/clutter/clutter-mutter.h b/clutter/clutter/clutter-mutter.h index d5dbf89e6c9dc2563fcb6141803db4ee3015d6a4..cc1ba9a97251dc7393b29a1fc1ceb779bb725ed8 100644 --- a/clutter/clutter/clutter-mutter.h +++ b/clutter/clutter/clutter-mutter.h @@ -75,7 +75,7 @@ CLUTTER_EXPORT void clutter_stage_thaw_updates (ClutterStage *stage); CLUTTER_EXPORT -void clutter_stage_update_resource_scales (ClutterStage *stage); +void clutter_stage_clear_stage_views (ClutterStage *stage); CLUTTER_EXPORT void clutter_stage_view_assign_next_scanout (ClutterStageView *stage_view, diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index 9f018ab84d8bd9fd8be642f7595cd14bcff13436..5d785b644aa8a04f8d757ffac8de991444f936c9 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -125,9 +125,6 @@ gboolean _clutter_stage_update_state (ClutterStage *stag void _clutter_stage_set_scale_factor (ClutterStage *stage, int factor); -gboolean _clutter_stage_get_max_view_scale_factor_for_rect (ClutterStage *stage, - graphene_rect_t *rect, - float *view_scale); void _clutter_stage_presented (ClutterStage *stage, CoglFrameEvent frame_event, @@ -136,6 +133,9 @@ void _clutter_stage_presented (ClutterStage *stag void clutter_stage_queue_actor_relayout (ClutterStage *stage, ClutterActor *actor); +GList * clutter_stage_get_views_for_rect (ClutterStage *stage, + const graphene_rect_t *rect); + G_END_DECLS #endif /* __CLUTTER_STAGE_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 61737abd4b6e678203e6da8188d557d152e977e4..ff079c1adc692ee6d3a029876251819c7e2d16e3 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -1469,6 +1469,14 @@ _clutter_stage_check_updated_pointers (ClutterStage *stage) return updating; } +static void +update_actor_stage_views (ClutterStage *stage) +{ + ClutterActor *actor = CLUTTER_ACTOR (stage); + + clutter_actor_update_stage_views (actor); +} + /** * _clutter_stage_do_update: * @stage: A #ClutterStage @@ -1516,6 +1524,10 @@ _clutter_stage_do_update (ClutterStage *stage) if (stage_was_relayout) pointers = _clutter_stage_check_updated_pointers (stage); + COGL_TRACE_BEGIN (ClutterStageUpdateActorStageViews, "Actor stage-views"); + update_actor_stage_views (stage); + COGL_TRACE_END (ClutterStageUpdateActorStageViews); + COGL_TRACE_BEGIN (ClutterStagePaint, "Paint"); clutter_stage_maybe_finish_queue_redraws (stage); @@ -4077,20 +4089,29 @@ clutter_stage_get_capture_final_size (ClutterStage *stage, int *out_height, float *out_scale) { - float max_scale; + float max_scale = 1.0; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE); if (rect) { graphene_rect_t capture_rect; + g_autoptr (GList) views = NULL; + GList *l; _clutter_util_rect_from_rectangle (rect, &capture_rect); - if (!_clutter_stage_get_max_view_scale_factor_for_rect (stage, - &capture_rect, - &max_scale)) + views = clutter_stage_get_views_for_rect (stage, &capture_rect); + + if (!views) return FALSE; + for (l = views; l; l = l->next) + { + ClutterStageView *view = l->data; + + max_scale = MAX (clutter_stage_view_get_scale (view), max_scale); + } + if (out_width) *out_width = (gint) roundf (rect->width * max_scale); @@ -4371,18 +4392,17 @@ clutter_stage_peek_stage_views (ClutterStage *stage) } void -clutter_stage_update_resource_scales (ClutterStage *stage) +clutter_stage_clear_stage_views (ClutterStage *stage) { - _clutter_actor_queue_update_resource_scale_recursive (CLUTTER_ACTOR (stage)); + clutter_actor_clear_stage_views_recursive (CLUTTER_ACTOR (stage)); } -gboolean -_clutter_stage_get_max_view_scale_factor_for_rect (ClutterStage *stage, - graphene_rect_t *rect, - float *view_scale) +GList * +clutter_stage_get_views_for_rect (ClutterStage *stage, + const graphene_rect_t *rect) { ClutterStagePrivate *priv = stage->priv; - float scale = 0.0f; + GList *views_for_rect = NULL; GList *l; for (l = _clutter_stage_window_get_views (priv->impl); l; l = l->next) @@ -4395,12 +4415,8 @@ _clutter_stage_get_max_view_scale_factor_for_rect (ClutterStage *stage, _clutter_util_rect_from_rectangle (&view_layout, &view_rect); if (graphene_rect_intersection (&view_rect, rect, NULL)) - scale = MAX (clutter_stage_view_get_scale (view), scale); + views_for_rect = g_list_prepend (views_for_rect, view); } - if (scale == 0.0) - return FALSE; - - *view_scale = scale; - return TRUE; + return views_for_rect; } diff --git a/src/backends/native/meta-stage-native.c b/src/backends/native/meta-stage-native.c index 9b9c45ef30e65ec56de4a6952b97db30cd128d48..9a3d11cb9dca7627bf4f17b2dd92fbc3378108cc 100644 --- a/src/backends/native/meta-stage-native.c +++ b/src/backends/native/meta-stage-native.c @@ -140,7 +140,7 @@ meta_stage_native_rebuild_views (MetaStageNative *stage_native) ClutterActor *stage = meta_backend_get_stage (backend); meta_renderer_rebuild_views (renderer); - clutter_stage_update_resource_scales (CLUTTER_STAGE (stage)); + clutter_stage_clear_stage_views (CLUTTER_STAGE (stage)); ensure_frame_callbacks (stage_native); } diff --git a/src/backends/x11/nested/meta-backend-x11-nested.c b/src/backends/x11/nested/meta-backend-x11-nested.c index 37d611eb340ae44b8aad4f326eb8f36acff9856a..009a809787c70bb43077b11725d932a243146b0c 100644 --- a/src/backends/x11/nested/meta-backend-x11-nested.c +++ b/src/backends/x11/nested/meta-backend-x11-nested.c @@ -87,7 +87,7 @@ meta_backend_x11_nested_update_screen_size (MetaBackend *backend, if (meta_is_stage_views_enabled ()) { meta_renderer_rebuild_views (renderer); - clutter_stage_update_resource_scales (CLUTTER_STAGE (stage)); + clutter_stage_clear_stage_views (CLUTTER_STAGE (stage)); } clutter_actor_set_size (stage, width, height); } diff --git a/src/tests/meson.build b/src/tests/meson.build index 66b7f6222a57b751090d6bc22f0be6fe11bb85ed..92d110375ee3f532ccb772f3bcdb0c4aa54f3934 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -123,6 +123,27 @@ headless_start_test = executable('mutter-headless-start-test', install_dir: mutter_installed_tests_libexecdir, ) +stage_view_tests = executable('mutter-stage-view-tests', + sources: [ + 'meta-backend-test.c', + 'meta-backend-test.h', + 'meta-gpu-test.c', + 'meta-gpu-test.h', + 'meta-monitor-manager-test.c', + 'meta-monitor-manager-test.h', + 'monitor-test-utils.c', + 'monitor-test-utils.h', + 'stage-view-tests.c', + 'test-utils.c', + 'test-utils.h', + ], + include_directories: tests_includepath, + c_args: tests_c_args, + dependencies: [tests_deps], + install: have_installed_tests, + install_dir: mutter_installed_tests_libexecdir, +) + stacking_tests = [ 'basic-x11', 'basic-wayland', @@ -173,3 +194,10 @@ test('headless-start', headless_start_test, is_parallel: false, timeout: 60, ) + +test('stage-view', stage_view_tests, + suite: ['core', 'mutter/unit'], + env: test_env, + is_parallel: false, + timeout: 60, +) diff --git a/src/tests/stage-view-tests.c b/src/tests/stage-view-tests.c new file mode 100644 index 0000000000000000000000000000000000000000..47eb4bc9916fc00a613f82cf3b71fc830585214f --- /dev/null +++ b/src/tests/stage-view-tests.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2020 Jonas Dreßler + * + * 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 "compositor/meta-plugin-manager.h" +#include "core/main-private.h" +#include "meta/main.h" +#include "tests/meta-backend-test.h" +#include "tests/monitor-test-utils.h" +#include "tests/test-utils.h" + +#define FRAME_WARNING "Frame has assigned frame counter but no frame drawn time" + +static gboolean +run_tests (gpointer data) +{ + MetaBackend *backend = meta_get_backend (); + MetaSettings *settings = meta_backend_get_settings (backend); + gboolean ret; + + g_test_log_set_fatal_handler (NULL, NULL); + + meta_settings_override_experimental_features (settings); + + meta_settings_enable_experimental_feature ( + settings, + META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER); + + ret = g_test_run (); + + meta_quit (ret != 0); + + return G_SOURCE_REMOVE; +} + +static gboolean +ignore_frame_counter_warning (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + if ((log_level & G_LOG_LEVEL_WARNING) && + g_strcmp0 (log_domain, "mutter") == 0 && + g_str_has_suffix (message, FRAME_WARNING)) + return FALSE; + + return TRUE; +} + +static MonitorTestCaseSetup initial_test_case_setup = { + .modes = { + { + .width = 1024, + .height = 768, + .refresh_rate = 60.0 + } + }, + .n_modes = 1, + .outputs = { + { + .crtc = 0, + .modes = { 0 }, + .n_modes = 1, + .preferred_mode = 0, + .possible_crtcs = { 0 }, + .n_possible_crtcs = 1, + .width_mm = 222, + .height_mm = 125 + }, + { + .crtc = 1, + .modes = { 0 }, + .n_modes = 1, + .preferred_mode = 0, + .possible_crtcs = { 1 }, + .n_possible_crtcs = 1, + .width_mm = 220, + .height_mm = 124 + } + }, + .n_outputs = 2, + .crtcs = { + { + .current_mode = 0 + }, + { + .current_mode = 0 + } + }, + .n_crtcs = 2 +}; + +static void +meta_test_stage_views_exist (void) +{ + MetaBackend *backend = meta_get_backend (); + ClutterActor *stage; + GList *stage_views; + + stage = meta_backend_get_stage (backend); + g_assert_cmpint (clutter_actor_get_width (stage), ==, 1024 * 2); + g_assert_cmpint (clutter_actor_get_height (stage), ==, 768); + + stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage)); + g_assert_cmpint (g_list_length (stage_views), ==, 2); +} + +static void +on_after_paint (ClutterStage *stage, + gboolean *was_painted) +{ + *was_painted = TRUE; +} + +static void +wait_for_paint (ClutterActor *stage) +{ + gboolean was_painted = FALSE; + gulong was_painted_id; + + was_painted_id = g_signal_connect (CLUTTER_STAGE (stage), + "after-paint", + G_CALLBACK (on_after_paint), + &was_painted); + + while (!was_painted) + g_main_context_iteration (NULL, FALSE); + + g_signal_handler_disconnect (stage, was_painted_id); +} + +static void +on_stage_views_changed (ClutterActor *actor, + gboolean *stage_views_changed) +{ + *stage_views_changed = TRUE; +} + +static void +is_on_stage_views (ClutterActor *actor, + unsigned int n_views, + ...) +{ + va_list valist; + int i = 0; + GList *stage_views = clutter_actor_peek_stage_views (actor); + + va_start (valist, n_views); + for (i = 0; i < n_views; i++) + { + ClutterStageView *view = va_arg (valist, ClutterStageView*); + g_assert_nonnull (g_list_find (stage_views, view)); + } + + va_end (valist); + g_assert (g_list_length (stage_views) == n_views); +} + +static void +meta_test_actor_stage_views (void) +{ + MetaBackend *backend = meta_get_backend (); + ClutterActor *stage, *container, *test_actor; + GList *stage_views; + gboolean stage_views_changed_container = FALSE; + gboolean stage_views_changed_test_actor = FALSE; + gboolean *stage_views_changed_container_ptr = + &stage_views_changed_container; + gboolean *stage_views_changed_test_actor_ptr = + &stage_views_changed_test_actor; + + stage = meta_backend_get_stage (backend); + stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage)); + + container = clutter_actor_new (); + clutter_actor_set_size (container, 100, 100); + clutter_actor_add_child (stage, container); + + test_actor = clutter_actor_new (); + clutter_actor_set_size (test_actor, 50, 50); + clutter_actor_add_child (container, test_actor); + + g_signal_connect (container, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_container_ptr); + g_signal_connect (test_actor, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_test_actor_ptr); + + clutter_actor_show (stage); + + wait_for_paint (stage); + + is_on_stage_views (container, 1, stage_views->data); + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted for the initial change */ + g_assert (stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_container = FALSE; + stage_views_changed_test_actor = FALSE; + + /* Move the container to the second stage view */ + clutter_actor_set_x (container, 1040); + + wait_for_paint (stage); + + is_on_stage_views (container, 1, stage_views->next->data); + is_on_stage_views (test_actor, 1, stage_views->next->data); + + /* The signal was emitted again */ + g_assert (stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_container = FALSE; + stage_views_changed_test_actor = FALSE; + + /* Move the container so it's on both stage views while the test_actor + * is only on the first one. + */ + clutter_actor_set_x (container, 940); + + wait_for_paint (stage); + + is_on_stage_views (container, 2, stage_views->data, stage_views->next->data); + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted again */ + g_assert (stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + + g_signal_handlers_disconnect_by_func (container, on_stage_views_changed, + stage_views_changed_container_ptr); + g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed, + stage_views_changed_test_actor_ptr); + clutter_actor_destroy (container); +} + +static void +meta_test_actor_stage_views_reparent (void) +{ + MetaBackend *backend = meta_get_backend (); + ClutterActor *stage, *container, *test_actor; + GList *stage_views; + gboolean stage_views_changed_container = FALSE; + gboolean stage_views_changed_test_actor = FALSE; + gboolean *stage_views_changed_container_ptr = + &stage_views_changed_container; + gboolean *stage_views_changed_test_actor_ptr = + &stage_views_changed_test_actor; + + stage = meta_backend_get_stage (backend); + stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage)); + + container = clutter_actor_new (); + clutter_actor_set_size (container, 100, 100); + clutter_actor_set_x (container, 1020); + clutter_actor_add_child (stage, container); + + test_actor = clutter_actor_new (); + clutter_actor_set_size (test_actor, 20, 20); + clutter_actor_add_child (container, test_actor); + + g_signal_connect (container, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_container_ptr); + g_signal_connect (test_actor, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_test_actor_ptr); + + clutter_actor_show (stage); + + wait_for_paint (stage); + + is_on_stage_views (container, 2, stage_views->data, stage_views->next->data); + is_on_stage_views (test_actor, 2, stage_views->data, stage_views->next->data); + + /* The signal was emitted for both actors */ + g_assert (stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_container = FALSE; + stage_views_changed_test_actor = FALSE; + + /* Remove the test_actor from the scene-graph */ + g_object_ref (test_actor); + clutter_actor_remove_child (container, test_actor); + + /* While the test_actor is not on stage, it must be on no stage views */ + is_on_stage_views (test_actor, 0); + + /* When the test_actor left the stage, the signal was emitted */ + g_assert (!stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_test_actor = FALSE; + + /* Add the test_actor again as a child of the stage */ + clutter_actor_add_child (stage, test_actor); + g_object_unref (test_actor); + + wait_for_paint (stage); + + /* The container is still on both stage views... */ + is_on_stage_views (container, 2, stage_views->data, stage_views->next->data); + + /* ...while the test_actor is only on the first one now */ + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted for the test_actor again */ + g_assert (!stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_test_actor = FALSE; + + /* Move the container out of the stage... */ + clutter_actor_set_y (container, 2000); + g_object_ref (test_actor); + clutter_actor_remove_child (stage, test_actor); + + /* When the test_actor left the stage, the signal was emitted */ + g_assert (!stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_test_actor = FALSE; + + /* ...and reparent the test_actor to the container again */ + clutter_actor_add_child (container, test_actor); + g_object_unref (test_actor); + + wait_for_paint (stage); + + /* Now both actors are on no stage views */ + is_on_stage_views (container, 0); + is_on_stage_views (test_actor, 0); + + /* The signal was emitted only for the container, the test_actor already + * has no stage-views. + */ + g_assert (stage_views_changed_container); + g_assert (!stage_views_changed_test_actor); + + g_signal_handlers_disconnect_by_func (container, on_stage_views_changed, + stage_views_changed_container_ptr); + g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed, + stage_views_changed_test_actor_ptr); + clutter_actor_destroy (container); +} + +static void +meta_test_actor_stage_views_hide_parent (void) +{ + MetaBackend *backend = meta_get_backend (); + ClutterActor *stage, *outer_container, *inner_container, *test_actor; + GList *stage_views; + gboolean stage_views_changed_outer_container = FALSE; + gboolean stage_views_changed_inner_container = FALSE; + gboolean stage_views_changed_test_actor = FALSE; + gboolean *stage_views_changed_outer_container_ptr = + &stage_views_changed_outer_container; + gboolean *stage_views_changed_inner_container_ptr = + &stage_views_changed_inner_container; + gboolean *stage_views_changed_test_actor_ptr = + &stage_views_changed_test_actor; + + stage = meta_backend_get_stage (backend); + stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage)); + + outer_container = clutter_actor_new (); + clutter_actor_add_child (stage, outer_container); + + inner_container = clutter_actor_new (); + clutter_actor_add_child (outer_container, inner_container); + + test_actor = clutter_actor_new (); + clutter_actor_set_size (test_actor, 20, 20); + clutter_actor_add_child (inner_container, test_actor); + + g_signal_connect (outer_container, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_outer_container_ptr); + g_signal_connect (inner_container, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_inner_container_ptr); + g_signal_connect (test_actor, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_test_actor_ptr); + + clutter_actor_show (stage); + + wait_for_paint (stage); + + /* The containers and the test_actor are on all on the first view */ + is_on_stage_views (outer_container, 1, stage_views->data); + is_on_stage_views (inner_container, 1, stage_views->data); + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted for all three */ + g_assert (stage_views_changed_outer_container); + g_assert (stage_views_changed_inner_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_outer_container = FALSE; + stage_views_changed_inner_container = FALSE; + stage_views_changed_test_actor = FALSE; + + /* Hide the inner_container */ + clutter_actor_hide (inner_container); + + /* Move the outer_container so it's still on the first view */ + clutter_actor_set_x (outer_container, 1023); + + wait_for_paint (stage); + + /* The outer_container is still expanded so it should be on both views */ + is_on_stage_views (outer_container, 2, + stage_views->data, stage_views->next->data); + + /* The inner_container and test_actor aren't updated because they're hidden */ + is_on_stage_views (inner_container, 1, stage_views->data); + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted for the outer_container */ + g_assert (stage_views_changed_outer_container); + g_assert (!stage_views_changed_inner_container); + g_assert (!stage_views_changed_test_actor); + stage_views_changed_outer_container = FALSE; + + /* Show the inner_container again */ + clutter_actor_show (inner_container); + + wait_for_paint (stage); + + /* All actors are on both views now */ + is_on_stage_views (outer_container, 2, + stage_views->data, stage_views->next->data); + is_on_stage_views (inner_container, 2, + stage_views->data, stage_views->next->data); + is_on_stage_views (test_actor, 2, + stage_views->data, stage_views->next->data); + + /* The signal was emitted for the inner_container and test_actor */ + g_assert (!stage_views_changed_outer_container); + g_assert (stage_views_changed_inner_container); + g_assert (stage_views_changed_test_actor); + + g_signal_handlers_disconnect_by_func (outer_container, on_stage_views_changed, + stage_views_changed_outer_container_ptr); + g_signal_handlers_disconnect_by_func (inner_container, on_stage_views_changed, + stage_views_changed_inner_container_ptr); + g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed, + stage_views_changed_test_actor_ptr); + clutter_actor_destroy (outer_container); +} + +static void +init_tests (int argc, char **argv) +{ + MetaMonitorTestSetup *test_setup; + + test_setup = create_monitor_test_setup (&initial_test_case_setup, + MONITOR_TEST_FLAG_NO_STORED); + + meta_monitor_manager_test_init_test_setup (test_setup); + + g_test_add_func ("/stage-view/stage-views-exist", + meta_test_stage_views_exist); + g_test_add_func ("/stage-views/actor-stage-views", + meta_test_actor_stage_views); + g_test_add_func ("/stage-views/actor-stage-views-reparent", + meta_test_actor_stage_views_reparent); + g_test_add_func ("/stage-views/actor-stage-views-hide-parent", + meta_test_actor_stage_views_hide_parent); +} + +int +main (int argc, char *argv[]) +{ + test_init (&argc, &argv); + init_tests (argc, argv); + + meta_plugin_manager_load (test_get_plugin_name ()); + + meta_override_compositor_configuration (META_COMPOSITOR_TYPE_WAYLAND, + META_TYPE_BACKEND_TEST); + + meta_init (); + meta_register_with_session (); + + g_test_log_set_fatal_handler (ignore_frame_counter_warning, NULL); + + g_idle_add (run_tests, NULL); + + return meta_run (); +}