Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian....@packages.debian.org
Usertags: pu
X-Debbugs-Cc: mut...@packages.debian.org, debian-gtk-gn...@lists.debian.org
Control: affects -1 + src:mutter

[ Reason ]
Several new upstream bugfix releases. I've been trying to get these into
a suitable state for a stable update since 12.1, but every time I've
been testing one long enough to think about asking for upload approval,
there have been more bugfixes upstream and the cycle starts again.

This might be the last upstream bugfix release in the 43.x series,
or we might get a 43.9.

[ Impact ]
If not accepted, various crashes and other bugs will remain unfixed,
despite having solutions known to upstream.

[ Tests ]
A prerelease build is available at
https://people.debian.org/~smcv/12.3/pool/main/m/mutter/
and is in use on my household's bookworm laptop/desktop systems, with
no obvious regressions so far.

The diff is not small and the 12.2 deadline is coming up, so I think we
should continue testing this until after 12.2 is out, and then upload. I
would appreciate any testing that the rest of the GNOME team can provide.

43.7-1 was in testing for a while before being superseded by version 44.
I also tested a bookworm backport of 43.7-1 on my household's bookworm
laptop/desktop systems for a while.

[ Risks ]
I am not any sort of expert on compositor development, but upstream
have generally been good about backporting only bug fixes to their
stable branches. There have been some regressions in the past because
this stuff is difficult. If there are regressions from these changes,
they're likely to be of the same magnitude as the bugs that were fixed.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
      (lightly filtered, see top of debdiff)
  [x] the issue is verified as fixed in unstable

[ Changes ]
clutter/clutter/clutter-paint-volume.c:
    - Improve GNOME Shell app grid performance by avoiding repainting
      monitors other than the one it is displayed on
      (partially fixes gnome-shell#6819, a full fix needs gnome-shell 43.9
      which I am also proposing as a stable update; fixed in 44.4 for
      unstable)

clutter/clutter/clutter-stage.c, src/core/window.c,
src/core/display-private.h, src/core/display.c:
      Give focus to new app windows when launched from the gnome-shell
      overview, fixing a regression in 43.3-5 and upstream 43.4
      (Closes: #1035092, #1049934; fixed in 43.7 and 44.4 for unstable)

cogl/cogl/driver/gl/cogl-gl-framebuffer-fbo.c:
    Fix a test failure with recent Mesa (Closes: #1042055, LP: #2025287;
    fixed in 44.4 for unstable)

src/backends/meta-stage-impl.c:
    - Fix flickering and rendering artifacts when using software rendering,
      for example on older Intel hardware unsupported by the Gallium i915
      driver (mutter#2602; fixed in 44.4 for unstable)

src/backends/native/meta-output-kms.c:
      Make the choice of preferred video mode consistent between code
      paths, fixing choice of video mode on some monitors (mutter!3055;
      fixed in 44.4 for unstable)

src/tests/: More test coverage, especially for #1035092
      (included in 43.7 and 44.4 for unstable)

src/wayland/meta-wayland-touch.c:
    - Fix the ability to drag libdecor windows by their title bar on
      touchscreens (mutter#2872; fixed in 44.4 for unstable)

.gitlab-ci.yml: Upstream CI changes, filtered out of the diff

[ Other info ]
I've only tested this in conjunction with an accompanying gnome-shell
update, so it would be best if the same release team member can look
at both.

The attached diff corresponds to packaging commit 225a383fa6, test-builds
are labelled as 43.8-0+deb12u1~43.7+2+22+g225a383fa6.

    smcv
filterdiff -p1 -x.gitlab-ci.yml -x'debian/patches/*.patch'

diff --git a/NEWS b/NEWS
index 410519419e..824ba51285 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,23 @@
+43.8
+====
+* Fix restoring focus when leaving the overview [Jonas; #2690]
+* Fix touch move operations on subsurfaces [Gergo; !3125]
+* Fix flickering when DRI driver isn't available [Daniel; !3117]
+* Misc. bug fixes and cleanups [Michel, Robert, Olivier, Daniel; !3055, !3135,
+  #2848, !3112]
+
+Contributors:
+  Jonas Ådahl, Michel Dänzer, Olivier Fourdan, Gergo Koteles, Robert Mader,
+  Daniel van Vugt
+
+43.7
+====
+* Fixed crash [Keyu; !2827]
+
+Contributors:
+  Keyu Tao
+
+
 43.6
 ====
 * Fix popup issues [Jonas; !2940]
diff --git a/clutter/clutter/clutter-paint-volume.c b/clutter/clutter/clutter-paint-volume.c
index dbec4d130f..42854ba894 100644
--- a/clutter/clutter/clutter-paint-volume.c
+++ b/clutter/clutter/clutter-paint-volume.c
@@ -1061,8 +1061,8 @@ _clutter_paint_volume_get_stage_paint_box (ClutterPaintVolume *pv,
 
   _clutter_paint_volume_get_bounding_box (&projected_pv, box);
 
-  if (pv->is_2d && pv->actor &&
-      clutter_actor_get_z_position (pv->actor) == 0)
+  if (pv->is_2d &&
+      (!pv->actor || clutter_actor_get_z_position (pv->actor) == 0))
     {
       /* If the volume/actor are perfectly 2D, take the bounding box as
        * good. We won't need to add any extra room for sub-pixel positioning
diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
index 7d24cf4a8f..84ae7fc92a 100644
--- a/clutter/clutter/clutter-stage.c
+++ b/clutter/clutter/clutter-stage.c
@@ -152,6 +152,8 @@ enum
   PROP_PERSPECTIVE,
   PROP_TITLE,
   PROP_KEY_FOCUS,
+  PROP_IS_GRABBED,
+
   PROP_LAST
 };
 
@@ -1175,6 +1177,10 @@ clutter_stage_get_property (GObject    *gobject,
       g_value_set_object (value, priv->key_focused_actor);
       break;
 
+    case PROP_IS_GRABBED:
+      g_value_set_boolean (value, !!priv->topmost_grab);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
       break;
@@ -1363,6 +1369,17 @@ clutter_stage_class_init (ClutterStageClass *klass)
                            CLUTTER_PARAM_READWRITE |
                            G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   * ClutterStage:is-grabbed:
+   *
+   * %TRUE if there is currently an active grab on the stage.
+   */
+  obj_props[PROP_IS_GRABBED] =
+      g_param_spec_boolean ("is-grabbed", NULL, NULL,
+                            FALSE,
+                            CLUTTER_PARAM_READABLE |
+                            G_PARAM_EXPLICIT_NOTIFY);
+
   g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
 
   /**
@@ -3787,6 +3804,7 @@ clutter_stage_grab (ClutterStage *stage,
 {
   ClutterStagePrivate *priv;
   ClutterGrab *grab;
+  gboolean was_grabbed;
 
   g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
@@ -3815,6 +3833,8 @@ clutter_stage_grab (ClutterStage *stage,
   grab->prev = NULL;
   grab->next = priv->topmost_grab;
 
+  was_grabbed = !!priv->topmost_grab;
+
   if (priv->topmost_grab)
     priv->topmost_grab->prev = grab;
 
@@ -3822,6 +3842,9 @@ clutter_stage_grab (ClutterStage *stage,
   clutter_actor_attach_grab (actor, grab);
   clutter_stage_notify_grab (stage, grab, grab->next);
 
+  if (was_grabbed != !!priv->topmost_grab)
+    g_object_notify_by_pspec (G_OBJECT (stage), obj_props[PROP_IS_GRABBED]);
+
   return grab;
 }
 
@@ -3831,6 +3854,7 @@ clutter_stage_unlink_grab (ClutterStage *stage,
 {
   ClutterStagePrivate *priv = stage->priv;
   ClutterGrab *prev, *next;
+  gboolean was_grabbed;
 
   /* This grab is already detached */
   if (!grab->prev && !grab->next && priv->topmost_grab != grab)
@@ -3844,6 +3868,8 @@ clutter_stage_unlink_grab (ClutterStage *stage,
   if (next)
     next->prev = prev;
 
+  was_grabbed = !!priv->topmost_grab;
+
   if (priv->topmost_grab == grab)
     {
       /* This is the active grab */
@@ -3866,6 +3892,9 @@ clutter_stage_unlink_grab (ClutterStage *stage,
       priv->grab_state = CLUTTER_GRAB_STATE_NONE;
     }
 
+  if (was_grabbed != !!priv->topmost_grab)
+    g_object_notify_by_pspec (G_OBJECT (stage), obj_props[PROP_IS_GRABBED]);
+
   grab->next = NULL;
   grab->prev = NULL;
 }
diff --git a/cogl/cogl/driver/gl/cogl-gl-framebuffer-fbo.c b/cogl/cogl/driver/gl/cogl-gl-framebuffer-fbo.c
index c8db6a23a2..7cc5377637 100644
--- a/cogl/cogl/driver/gl/cogl-gl-framebuffer-fbo.c
+++ b/cogl/cogl/driver/gl/cogl-gl-framebuffer-fbo.c
@@ -76,7 +76,7 @@ ensure_bits_initialized (CoglGlFramebufferFbo *gl_framebuffer_fbo)
                                         COGL_FRAMEBUFFER_STATE_BIND);
 
 #ifdef HAVE_COGL_GL
-  if (!_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_QUERY_FRAMEBUFFER_BITS))
+  if (_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_QUERY_FRAMEBUFFER_BITS))
     {
       const struct {
         GLenum attachment, pname;
diff --git a/debian/changelog b/debian/changelog
index eeabc8782b..2deb81868d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,66 @@
+mutter (43.8-0+deb12u1) UNRELEASED; urgency=medium
+
+  * d/control.in, d/gbp.conf: Use debian/bookworm branch
+  * Apply changes from 43.7-2 to bookworm
+    (Closes: #1035092, #1049934, #1042055)
+  * New upstream stable release 43.8
+    - Fix the ability to drag libdecor windows by their title bar on
+      touchscreens (mutter#2872)
+    - Fix flickering and rendering artifacts when using software rendering,
+      for example on older Intel hardware unsupported by the Gallium i915
+      driver (mutter#2602)
+    - Improve GNOME Shell app grid performance by avoiding repainting
+      monitors other than the one it is displayed on
+      (partially fixes gnome-shell#6819)
+    - Upstream CI adjustments not relevant to Debian
+    - All other changes were previously included in 43.7-1, 43.7-2
+  * d/patches: Drop patches that were included in the upstream release
+
+ -- Simon McVittie <s...@debian.org>  Thu, 14 Sep 2023 10:49:45 +0100
+
+mutter (43.7-2) unstable; urgency=medium
+
+  * Team upload
+  * d/patches: Update to upstream gnome-43 branch commit
+    43.7-9-g3139cbb47c, excluding CI-only changes
+    - d/p/output-kms-Use-meta_kms_connector_get_preferred_mode-in-i.patch:
+      Make the choice of preferred video mode consistent between code
+      paths, fixing choice of video mode on some monitors (mutter!3055)
+    - d/p/cogl-gl-framebuffer-Fix-inverted-test-in-ensure_bits_init.patch:
+      change was already included in 43.7-1, just update metadata
+    - d/p/clutter-stage-Add-is-grabbed-property.patch,
+      d/p/window-Postpone-focusing-until-grab-ended-if-uninteractab.patch:
+      Give focus to new app windows when launched from the gnome-shell
+      overview, fixing a regression in 43.3-5 and upstream 43.4
+      (Closes: #1035092, #1049934)
+    - d/p/tests-Introduce-and-use-a-custom-test-shell.patch,
+      d/p/tests-test-shell-Emulate-overview-grabs.patch,
+      d/p/tests-test-runner-Add-toggle_overview-command.patch,
+      d/p/tests-Add-test-case-for-restoring-focus-after-overview.patch:
+      Add unit test coverage for #1035092
+  * d/p/tests-Reinstate-meta_test_get_plugin_name.patch:
+    Reinstate a symbol in libmutter-test-11 removed by the above patches,
+    in case a dependent package is using it
+  * d/libmutter-test-11.symbols: Add a new symbol introduced by the test
+    for #1035092
+
+ -- Simon McVittie <s...@debian.org>  Sun, 20 Aug 2023 11:35:31 +0100
+
+mutter (43.7-1) unstable; urgency=medium
+
+  * Team upload
+  * New upstream stable release 43.7
+    - Functionally equivalent to 43.6-1
+  * d/p/wayland-outputs-Fix-potential-crash-when-output-has-no-mo.patch:
+    Drop patch, applied upstream
+  * d/gbp.conf, d/control.in: Use debian/trixie branch
+  * d/p/cogl-gl-framebuffer-Fix-inverted-test-in-ensure_bits_init.patch:
+    Add patch from upstream 45.beta to fix a test failure with recent Mesa
+    (Closes: #1042055, LP: #2025287)
+  * d/patches: Improve tracking of upstream status
+
+ -- Simon McVittie <s...@debian.org>  Fri, 18 Aug 2023 17:40:39 +0100
+
 mutter (43.6-1~deb12u1) bookworm; urgency=medium
 
   * Rebuild for bookworm
diff --git a/debian/control b/debian/control
index b1940e6017..4433c562a3 100644
--- a/debian/control
+++ b/debian/control
@@ -6,7 +6,7 @@ Source: mutter
 Section: x11
 Priority: optional
 Maintainer: Debian GNOME Maintainers <pkg-gnome-maintain...@lists.alioth.debian.org>
-Uploaders: Jeremy Bicha <jbi...@ubuntu.com>
+Uploaders: Laurent Bigonville <bi...@debian.org>, Marco Trevisan (Treviño) <ma...@ubuntu.com>
 Build-Depends: debhelper-compat (= 13),
                dh-exec,
                dh-sequence-gir,
diff --git a/debian/libmutter-test-11.symbols b/debian/libmutter-test-11.symbols
index e253315ec7..b9ae48eaf8 100644
--- a/debian/libmutter-test-11.symbols
+++ b/debian/libmutter-test-11.symbols
@@ -49,6 +49,7 @@ libmutter-test-11.so libmutter-test-11 #MINVER#
  meta_test_client_wait_for_window_shown@Base 41.0
  meta_test_get_gpu@Base 42~beta
  meta_test_get_plugin_name@Base 41.0
+ meta_test_shell_get_type@Base 43.7-1~
  meta_wait_for_orientation@Base 42~beta
  meta_wait_for_paint@Base 42~beta
  meta_wait_for_possible_orientation_change@Base 42~beta
diff --git a/debian/patches/series b/debian/patches/series
index baff484c1d..bb392db6e4 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,4 +1,4 @@
-wayland-outputs-Fix-potential-crash-when-output-has-no-mo.patch
+tests-Reinstate-meta_test_get_plugin_name.patch
 tests-Break-up-stacking-installed-tests-into-more-smaller.patch
 tests-Use-a-more-interoperable-path-to-bash.patch
 meson-add-back-default_driver-option.patch
diff --git a/debian/patches/tests-Reinstate-meta_test_get_plugin_name.patch b/debian/patches/tests-Reinstate-meta_test_get_plugin_name.patch
new file mode 100644
index 0000000000..c7ddf5829b
diff --git a/meson.build b/meson.build
index dc13a1172c..1faf7b1c1c 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('mutter', 'c',
-  version: '43.6',
+  version: '43.8',
   meson_version: '>= 0.55.0',
   license: 'GPLv2+'
 )
diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c
index d8f009a75b..09908e8c68 100644
--- a/src/backends/meta-stage-impl.c
+++ b/src/backends/meta-stage-impl.c
@@ -561,10 +561,12 @@ meta_stage_impl_redraw_view_primary (MetaStageImpl    *stage_impl,
    * artefacts.
    */
   /* swap_region does not need damage history, set it up before that */
-  if (use_clipped_redraw)
-    swap_region = cairo_region_copy (fb_clip_region);
-  else
+  if (!use_clipped_redraw)
     swap_region = cairo_region_create ();
+  else if (clutter_stage_view_has_shadowfb (stage_view))
+    swap_region = cairo_region_reference (fb_clip_region);
+  else
+    swap_region = cairo_region_copy (fb_clip_region);
 
   swap_with_damage = FALSE;
   if (has_buffer_age)
diff --git a/src/backends/native/meta-output-kms.c b/src/backends/native/meta-output-kms.c
index 0393a62178..d5e710d231 100644
--- a/src/backends/native/meta-output-kms.c
+++ b/src/backends/native/meta-output-kms.c
@@ -365,10 +365,12 @@ init_output_modes (MetaOutputInfo    *output_info,
                    GError           **error)
 {
   const MetaKmsConnectorState *connector_state;
+  MetaKmsMode *kms_preferred_mode;
   GList *l;
   int i;
 
   connector_state = meta_kms_connector_get_current_state (kms_connector);
+  kms_preferred_mode = meta_kms_connector_get_preferred_mode (kms_connector);
 
   output_info->preferred_mode = NULL;
 
@@ -377,12 +379,11 @@ init_output_modes (MetaOutputInfo    *output_info,
   for (l = connector_state->modes, i = 0; l; l = l->next, i++)
     {
       MetaKmsMode *kms_mode = l->data;
-      const drmModeModeInfo *drm_mode = meta_kms_mode_get_drm_mode (kms_mode);
       MetaCrtcMode *crtc_mode;
 
       crtc_mode = meta_gpu_kms_get_mode_from_kms_mode (gpu_kms, kms_mode);
       output_info->modes[i] = crtc_mode;
-      if (drm_mode->type & DRM_MODE_TYPE_PREFERRED)
+      if (kms_mode == kms_preferred_mode)
         output_info->preferred_mode = output_info->modes[i];
     }
 
diff --git a/src/core/display-private.h b/src/core/display-private.h
index 5757444760..78e51f2618 100644
--- a/src/core/display-private.h
+++ b/src/core/display-private.h
@@ -382,6 +382,9 @@ void meta_display_cancel_touch (MetaDisplay *display);
 
 gboolean meta_display_windows_are_interactable (MetaDisplay *display);
 
+void meta_display_queue_focus (MetaDisplay *display,
+                               MetaWindow  *window);
+
 void meta_display_show_tablet_mapping_notification (MetaDisplay        *display,
                                                     ClutterInputDevice *pad,
                                                     const gchar        *pretty_name);
diff --git a/src/core/display.c b/src/core/display.c
index 470ff804b1..6a8af538a5 100644
--- a/src/core/display.c
+++ b/src/core/display.c
@@ -133,6 +133,11 @@ typedef struct _MetaDisplayPrivate
 
   guint queue_later_ids[META_N_QUEUE_TYPES];
   GList *queue_windows[META_N_QUEUE_TYPES];
+
+  struct {
+    MetaWindow *window;
+    gulong unmanaging_handler_id;
+  } focus_on_grab_dismissed;
 } MetaDisplayPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (MetaDisplay, meta_display, G_TYPE_OBJECT)
@@ -211,6 +216,10 @@ meta_display_show_osd (MetaDisplay *display,
                        const gchar *icon_name,
                        const gchar *message);
 
+static void on_is_grabbed_changed (ClutterStage *stage,
+                                   GParamSpec   *pspec,
+                                   MetaDisplay  *display);
+
 static void
 meta_display_get_property(GObject         *object,
                           guint            prop_id,
@@ -838,6 +847,7 @@ meta_display_new (MetaContext  *context,
                   GError      **error)
 {
   MetaBackend *backend = meta_context_get_backend (context);
+  ClutterActor *stage = meta_backend_get_stage (backend);
   MetaDisplay *display;
   MetaDisplayPrivate *priv;
   int i;
@@ -1008,6 +1018,9 @@ meta_display_new (MetaContext  *context,
       meta_display_unset_input_focus (display, timestamp);
     }
 
+  g_signal_connect (stage, "notify::is-grabbed",
+                    G_CALLBACK (on_is_grabbed_changed), display);
+
   display->sound_player = g_object_new (META_TYPE_SOUND_PLAYER, NULL);
 
   /* Done opening new display */
@@ -1293,6 +1306,52 @@ meta_display_windows_are_interactable (MetaDisplay *display)
     }
 }
 
+static void
+on_is_grabbed_changed (ClutterStage *stage,
+                       GParamSpec   *pspec,
+                       MetaDisplay  *display)
+{
+  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);
+
+  if (!priv->focus_on_grab_dismissed.window)
+    return;
+
+  meta_window_focus (priv->focus_on_grab_dismissed.window, META_CURRENT_TIME);
+
+  g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id,
+                          priv->focus_on_grab_dismissed.window);
+  priv->focus_on_grab_dismissed.window = NULL;
+}
+
+static void
+focus_on_grab_dismissed_unmanaging_cb (MetaWindow  *window,
+                                       MetaDisplay *display)
+{
+  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);
+
+  g_return_if_fail (priv->focus_on_grab_dismissed.window == window);
+
+  g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id,
+                          priv->focus_on_grab_dismissed.window);
+  priv->focus_on_grab_dismissed.window = NULL;
+}
+
+void
+meta_display_queue_focus (MetaDisplay *display,
+                          MetaWindow  *window)
+{
+  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);
+
+  g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id,
+                          priv->focus_on_grab_dismissed.window);
+
+  priv->focus_on_grab_dismissed.window = window;
+  priv->focus_on_grab_dismissed.unmanaging_handler_id =
+    g_signal_connect (window, "unmanaging",
+                      G_CALLBACK (focus_on_grab_dismissed_unmanaging_cb),
+                      display);
+}
+
 /**
  * meta_display_xserver_time_is_before:
  * @display: a #MetaDisplay
diff --git a/src/core/window.c b/src/core/window.c
index 9ae2f78a67..5700af865e 100644
--- a/src/core/window.c
+++ b/src/core/window.c
@@ -1988,15 +1988,6 @@ window_state_on_map (MetaWindow *window,
       return;
     }
 
-  /* Do not focus window on map if input is already taken by the
-   * compositor.
-   */
-  if (!meta_display_windows_are_interactable (window->display))
-    {
-      *takes_focus = FALSE;
-      return;
-    }
-
   /* Terminal usage may be different; some users intend to launch
    * many apps in quick succession or to just view things in the new
    * window while still interacting with the terminal.  In that case,
@@ -2318,7 +2309,10 @@ meta_window_show (MetaWindow *window)
 
           timestamp = meta_display_get_current_time_roundtrip (window->display);
 
-          meta_window_focus (window, timestamp);
+          if (meta_display_windows_are_interactable (window->display))
+            meta_window_focus (window, timestamp);
+          else
+            meta_display_queue_focus (window->display, window);
         }
       else if (display->x11_display)
         {
diff --git a/src/tests/clutter/conform/meson.build b/src/tests/clutter/conform/meson.build
index ccc8a5c504..d49ad5a5ff 100644
--- a/src/tests/clutter/conform/meson.build
+++ b/src/tests/clutter/conform/meson.build
@@ -56,7 +56,6 @@ test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
 test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
 test_env.set('G_ENABLE_DIAGNOSTIC', '0')
 test_env.set('CLUTTER_ENABLE_DIAGNOSTIC', '0')
-test_env.set('MUTTER_TEST_PLUGIN_PATH', '@0@'.format(default_plugin.full_path()))
 
 foreach test : clutter_conform_tests
   test_executable = executable('@0@'.format(test),
diff --git a/src/tests/cogl/conform/meson.build b/src/tests/cogl/conform/meson.build
index e076733a58..84ff403da6 100644
--- a/src/tests/cogl/conform/meson.build
+++ b/src/tests/cogl/conform/meson.build
@@ -69,7 +69,6 @@ test_env = environment()
 test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
 test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
 test_env.set('G_ENABLE_DIAGNOSTIC', '0')
-test_env.set('MUTTER_TEST_PLUGIN_PATH', '@0@'.format(default_plugin.full_path()))
 
 cogl_test_variants = [ 'gl', 'gl3', 'gles2' ]
 
diff --git a/src/tests/cogl/unit/meson.build b/src/tests/cogl/unit/meson.build
index d17346ee1c..a554dc314a 100644
--- a/src/tests/cogl/unit/meson.build
+++ b/src/tests/cogl/unit/meson.build
@@ -14,7 +14,6 @@ test_env = environment()
 test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
 test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
 test_env.set('G_ENABLE_DIAGNOSTIC', '0')
-test_env.set('MUTTER_TEST_PLUGIN_PATH', '@0@'.format(default_plugin.full_path()))
 
 foreach unit_test: cogl_unit_tests
   test_name = 'cogl-' + unit_test[0]
diff --git a/src/tests/meson.build b/src/tests/meson.build
index 1b36d8c189..ffd88848bc 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -12,6 +12,8 @@ mutter_test_sources = [
   'meta-ref-test.h',
   'meta-sensors-proxy-mock.c',
   'meta-sensors-proxy-mock.h',
+  'meta-test-shell.c',
+  'meta-test-shell.h',
   'meta-test-utils.c',
   'meta-test-utils.h',
 ]
@@ -449,6 +451,7 @@ stacking_tests = [
   'workspace-test',
   'always-on-top',
   'focus-default-window-globally-active-input',
+  'overview-focus',
 ]
 
 foreach stacking_test: stacking_tests
diff --git a/src/tests/meta-context-test.c b/src/tests/meta-context-test.c
index 7d609dad37..b2081795d6 100644
--- a/src/tests/meta-context-test.c
+++ b/src/tests/meta-context-test.c
@@ -27,6 +27,7 @@
 
 #include "core/meta-context-private.h"
 #include "tests/meta-backend-test.h"
+#include "tests/meta-test-shell.h"
 #include "tests/meta-test-utils-private.h"
 #include "wayland/meta-wayland.h"
 #include "wayland/meta-xwayland.h"
@@ -70,7 +71,6 @@ meta_context_test_configure (MetaContext   *context,
     meta_context_test_get_instance_private (context_test);
   MetaContextClass *context_class =
     META_CONTEXT_CLASS (meta_context_test_parent_class);
-  const char *plugin_name;
 
   g_test_init (argc, argv, NULL);
 
@@ -85,10 +85,7 @@ meta_context_test_configure (MetaContext   *context,
   meta_wayland_override_display_name ("mutter-test-display");
   meta_xwayland_override_display_number (512);
 
-  plugin_name = g_getenv ("MUTTER_TEST_PLUGIN_PATH");
-  if (!plugin_name)
-    plugin_name = "libdefault";
-  meta_context_set_plugin_name (context, plugin_name);
+  meta_context_set_plugin_gtype (context, META_TYPE_TEST_SHELL);
 
   return TRUE;
 }
diff --git a/src/tests/meta-test-shell.c b/src/tests/meta-test-shell.c
new file mode 100644
index 0000000000..0a396b3c59
--- /dev/null
+++ b/src/tests/meta-test-shell.c
@@ -0,0 +1,799 @@
+/*
+ * Copyright (c) 2008 Intel Corp.
+ * Copyright (c) 2023 Red Hat
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "tests/meta-test-shell.h"
+
+#include <string.h>
+
+#include "clutter/clutter.h"
+#include "meta/meta-backend.h"
+#include "meta/meta-background-actor.h"
+#include "meta/meta-background-content.h"
+#include "meta/meta-background-group.h"
+#include "meta/meta-context.h"
+#include "meta/meta-monitor-manager.h"
+#include "meta/meta-plugin.h"
+#include "meta/util.h"
+#include "meta/window.h"
+
+typedef enum
+{
+  ANIMATION_DESTROY,
+  ANIMATION_MINIMIZE,
+  ANIMATION_MAP,
+  ANIMATION_SWITCH,
+} Animation;
+
+static unsigned int animation_durations[] = {
+  100, /* destroy */
+  250, /* minimize */
+  250, /* map */
+  500, /* switch */
+};
+
+#define ACTOR_DATA_KEY "-test-shell-actor-data"
+#define DISPLAY_TILE_PREVIEW_DATA_KEY "-test-shell-display-tile-preview-data"
+
+struct _MetaTestShell
+{
+  MetaPlugin parent;
+
+  ClutterTimeline *switch_workspace1_timeline;
+  ClutterTimeline *switch_workspace2_timeline;
+  ClutterActor *desktop1;
+  ClutterActor *desktop2;
+
+  ClutterActor *background_group;
+
+  MetaPluginInfo info;
+
+  struct {
+    ClutterGrab *grab;
+    ClutterActor *prev_focus;
+  } overview;
+};
+
+typedef struct _ActorPrivate
+{
+  ClutterActor *orig_parent;
+
+  ClutterTimeline *minimize_timeline;
+  ClutterTimeline *destroy_timeline;
+  ClutterTimeline *map_timeline;
+} ActorPrivate;
+
+typedef struct
+{
+  ClutterActor *actor;
+  MetaPlugin *plugin;
+  gpointer effect_data;
+} EffectCompleteData;
+
+typedef struct _DisplayTilePreview
+{
+  ClutterActor *actor;
+
+  MetaRectangle tile_rect;
+} DisplayTilePreview;
+
+G_DEFINE_TYPE (MetaTestShell, meta_test_shell, META_TYPE_PLUGIN)
+
+static GQuark actor_data_quark = 0;
+static GQuark display_tile_preview_data_quark = 0;
+
+static void
+free_actor_private (gpointer data)
+{
+  g_free (data);
+}
+
+static ActorPrivate *
+get_actor_private (MetaWindowActor *actor)
+{
+  ActorPrivate *actor_priv = g_object_get_qdata (G_OBJECT (actor), actor_data_quark);
+
+  if (G_UNLIKELY (actor_data_quark == 0))
+    actor_data_quark = g_quark_from_static_string (ACTOR_DATA_KEY);
+
+  if (G_UNLIKELY (!actor_priv))
+    {
+      actor_priv = g_new0 (ActorPrivate, 1);
+
+      g_object_set_qdata_full (G_OBJECT (actor),
+                               actor_data_quark, actor_priv,
+                               free_actor_private);
+    }
+
+  return actor_priv;
+}
+
+static gboolean
+is_animations_disabled (void)
+{
+  static gboolean is_animations_disabled_set;
+  static gboolean is_animations_disabled;
+
+  if (!is_animations_disabled_set)
+    {
+      if (g_strcmp0 (getenv ("MUTTER_DEBUG_DISABLE_ANIMATIONS"), "1") == 0)
+        is_animations_disabled = TRUE;
+      else
+        is_animations_disabled = FALSE;
+
+      is_animations_disabled_set = TRUE;
+    }
+
+  return is_animations_disabled;
+}
+
+static unsigned int
+get_animation_duration (Animation animation)
+{
+  if (is_animations_disabled ())
+    return 0;
+
+  return animation_durations[animation];
+}
+
+static ClutterTimeline *
+actor_animate (ClutterActor         *actor,
+               ClutterAnimationMode  mode,
+               Animation             animation,
+               const char           *first_property,
+               ...)
+{
+  va_list args;
+  ClutterTransition *transition;
+
+  clutter_actor_save_easing_state (actor);
+  clutter_actor_set_easing_mode (actor, mode);
+  clutter_actor_set_easing_duration (actor, get_animation_duration (animation));
+
+  va_start (args, first_property);
+  g_object_set_valist (G_OBJECT (actor), first_property, args);
+  va_end (args);
+
+  transition = clutter_actor_get_transition (actor, first_property);
+
+  clutter_actor_restore_easing_state (actor);
+
+  return CLUTTER_TIMELINE (transition);
+}
+
+static void
+finish_timeline (ClutterTimeline *timeline)
+{
+  g_object_ref (timeline);
+  clutter_timeline_stop (timeline);
+  g_object_unref (timeline);
+}
+
+static void
+kill_workspace_switch_animation (MetaTestShell *test_shell)
+{
+  if (test_shell->switch_workspace1_timeline)
+    {
+      g_autoptr (ClutterTimeline) timeline1 = NULL;
+      g_autoptr (ClutterTimeline) timeline2 = NULL;
+
+      timeline1 = g_object_ref (test_shell->switch_workspace1_timeline);
+      timeline2 = g_object_ref (test_shell->switch_workspace2_timeline);
+
+      finish_timeline (timeline1);
+      finish_timeline (timeline2);
+    }
+}
+
+static void
+on_switch_workspace_effect_stopped (ClutterTimeline *timeline,
+                                    gboolean         is_finished,
+                                    gpointer         data)
+{
+  MetaPlugin *plugin  = META_PLUGIN (data);
+  MetaTestShell *test_shell = META_TEST_SHELL (plugin);
+  MetaDisplay *display = meta_plugin_get_display (plugin);
+  GList *l = meta_get_window_actors (display);
+
+  while (l)
+    {
+      ClutterActor *a = l->data;
+      MetaWindowActor *window_actor = META_WINDOW_ACTOR (a);
+      ActorPrivate *actor_priv = get_actor_private (window_actor);
+
+      if (actor_priv->orig_parent)
+        {
+          g_object_ref (a);
+          clutter_actor_remove_child (clutter_actor_get_parent (a), a);
+          clutter_actor_add_child (actor_priv->orig_parent, a);
+          g_object_unref (a);
+          actor_priv->orig_parent = NULL;
+        }
+
+      l = l->next;
+    }
+
+  clutter_actor_destroy (test_shell->desktop1);
+  clutter_actor_destroy (test_shell->desktop2);
+
+  test_shell->switch_workspace1_timeline = NULL;
+  test_shell->switch_workspace2_timeline = NULL;
+  test_shell->desktop1 = NULL;
+  test_shell->desktop2 = NULL;
+
+  meta_plugin_switch_workspace_completed (plugin);
+}
+
+static void
+on_monitors_changed (MetaMonitorManager *monitor_manager,
+                     MetaPlugin         *plugin)
+{
+  MetaTestShell *test_shell = META_TEST_SHELL (plugin);
+  MetaDisplay *display = meta_plugin_get_display (plugin);
+  GRand *rand;
+  int i, n;
+
+  rand = g_rand_new_with_seed (123456);
+  clutter_actor_destroy_all_children (test_shell->background_group);
+
+  n = meta_display_get_n_monitors (display);
+  for (i = 0; i < n; i++)
+    {
+      MetaBackgroundContent *background_content;
+      ClutterContent *content;
+      MetaRectangle rect;
+      ClutterActor *background_actor;
+      MetaBackground *background;
+      uint8_t red;
+      uint8_t green;
+      uint8_t blue;
+      ClutterColor color;
+
+      meta_display_get_monitor_geometry (display, i, &rect);
+
+      background_actor = meta_background_actor_new (display, i);
+      content = clutter_actor_get_content (background_actor);
+      background_content = META_BACKGROUND_CONTENT (content);
+
+      clutter_actor_set_position (background_actor, rect.x, rect.y);
+      clutter_actor_set_size (background_actor, rect.width, rect.height);
+
+      blue = g_rand_int_range (rand, 0, 255);
+      green = g_rand_int_range (rand, 0, 255);
+      red = g_rand_int_range (rand, 0, 255);
+      clutter_color_init (&color, red, green, blue, 255);
+
+      background = meta_background_new (display);
+      meta_background_set_color (background, &color);
+      meta_background_content_set_background (background_content, background);
+      g_object_unref (background);
+
+      meta_background_content_set_vignette (background_content, TRUE, 0.5, 0.5);
+
+      clutter_actor_add_child (test_shell->background_group, background_actor);
+    }
+
+  g_rand_free (rand);
+}
+
+static void
+on_overlay_key (MetaDisplay   *display,
+                MetaTestShell *test_shell)
+{
+  MetaContext *context = meta_display_get_context (display);
+  MetaBackend *backend = meta_context_get_backend (context);
+  ClutterStage *stage = CLUTTER_STAGE (meta_backend_get_stage (backend));
+
+  if (!test_shell->overview.grab)
+    {
+      test_shell->overview.grab = clutter_stage_grab (stage, CLUTTER_ACTOR (stage));
+      test_shell->overview.prev_focus = clutter_stage_get_key_focus (stage);
+      clutter_stage_set_key_focus (stage, CLUTTER_ACTOR (stage));
+    }
+  else
+    {
+      g_clear_pointer (&test_shell->overview.grab, clutter_grab_dismiss);
+      clutter_stage_set_key_focus (stage,
+                                   g_steal_pointer (&test_shell->overview.prev_focus));
+    }
+}
+
+static void
+prepare_shutdown (MetaBackend   *backend,
+                  MetaTestShell *test_shell)
+{
+  kill_workspace_switch_animation (test_shell);
+}
+
+static void
+meta_test_shell_start (MetaPlugin *plugin)
+{
+  MetaTestShell *test_shell = META_TEST_SHELL (plugin);
+  MetaDisplay *display = meta_plugin_get_display (plugin);
+  MetaContext *context = meta_display_get_context (display);
+  MetaBackend *backend = meta_context_get_backend (context);
+  MetaMonitorManager *monitor_manager =
+    meta_backend_get_monitor_manager (backend);
+
+  test_shell->background_group = meta_background_group_new ();
+  clutter_actor_insert_child_below (meta_get_window_group_for_display (display),
+                                    test_shell->background_group, NULL);
+
+  g_signal_connect (monitor_manager, "monitors-changed",
+                    G_CALLBACK (on_monitors_changed), plugin);
+  on_monitors_changed (monitor_manager, plugin);
+
+  g_signal_connect (display, "overlay-key",
+                    G_CALLBACK (on_overlay_key), plugin);
+
+  g_signal_connect (backend, "prepare-shutdown",
+                    G_CALLBACK (prepare_shutdown),
+                    test_shell);
+
+  clutter_actor_show (meta_get_stage_for_display (display));
+}
+
+static void
+meta_test_shell_switch_workspace (MetaPlugin          *plugin,
+                                  int                  from,
+                                  int                  to,
+                                  MetaMotionDirection  direction)
+{
+  MetaTestShell *test_shell = META_TEST_SHELL (plugin);
+  MetaDisplay *display;
+  ClutterActor *stage;
+  ClutterActor *workspace1, *workspace2;
+  int screen_width, screen_height;
+  GList *l;
+
+  if (from == to)
+    {
+      meta_plugin_switch_workspace_completed (plugin);
+      return;
+    }
+
+  display = meta_plugin_get_display (plugin);
+  stage = meta_get_stage_for_display (display);
+
+  meta_display_get_size (display,
+                         &screen_width,
+                         &screen_height);
+
+  workspace1 = clutter_actor_new ();
+  workspace2 = clutter_actor_new ();
+
+  clutter_actor_set_pivot_point (workspace1, 1.0, 1.0);
+  clutter_actor_set_size (workspace1,
+                          screen_width,
+                          screen_height);
+  clutter_actor_set_size (workspace2,
+                          screen_width,
+                          screen_height);
+
+  clutter_actor_set_scale (workspace1, 0.0, 0.0);
+
+  clutter_actor_add_child (stage, workspace1);
+  clutter_actor_add_child (stage, workspace2);
+
+  for (l = g_list_last (meta_get_window_actors (display)); l; l = l->prev)
+    {
+      MetaWindowActor *window_actor = l->data;
+      ActorPrivate *actor_priv = get_actor_private (window_actor);
+      ClutterActor *actor = CLUTTER_ACTOR (window_actor);
+      MetaWindow *window;
+      MetaWorkspace *workspace;
+      int workspace_idx;
+
+      window = meta_window_actor_get_meta_window (window_actor);
+      workspace = meta_window_get_workspace (window);
+
+      if (!workspace)
+        {
+          clutter_actor_hide (actor);
+          actor_priv->orig_parent = NULL;
+          continue;
+        }
+
+      if (meta_window_is_on_all_workspaces (window))
+        {
+          actor_priv->orig_parent = NULL;
+          continue;
+        }
+
+        workspace_idx = meta_workspace_index (workspace);
+
+        if (workspace_idx == to || workspace_idx == from)
+          {
+            ClutterActor *parent = workspace_idx == to ? workspace1
+                                                       : workspace2;
+            actor_priv->orig_parent = clutter_actor_get_parent (actor);
+
+            g_object_ref (actor);
+            clutter_actor_remove_child (clutter_actor_get_parent (actor),
+                                        actor);
+            clutter_actor_add_child (parent, actor);
+            clutter_actor_set_child_below_sibling (parent, actor, NULL);
+            g_object_unref (actor);
+            continue;
+          }
+
+        clutter_actor_hide (actor);
+        actor_priv->orig_parent = NULL;
+    }
+
+  test_shell->desktop1 = workspace1;
+  test_shell->desktop2 = workspace2;
+
+  test_shell->switch_workspace1_timeline =
+    actor_animate (workspace1, CLUTTER_EASE_IN_SINE,
+                   ANIMATION_SWITCH,
+                   "scale-x", 1.0,
+                   "scale-y", 1.0,
+                   NULL);
+  g_signal_connect (test_shell->switch_workspace1_timeline,
+                    "stopped",
+                    G_CALLBACK (on_switch_workspace_effect_stopped),
+                    plugin);
+
+  test_shell->switch_workspace2_timeline =
+    actor_animate (workspace2, CLUTTER_EASE_IN_SINE,
+                   ANIMATION_SWITCH,
+                   "scale-x", 0.0,
+                   "scale-y", 0.0,
+                   NULL);
+}
+
+static void
+on_minimize_effect_stopped (ClutterTimeline    *timeline,
+                            gboolean            is_finished,
+                            EffectCompleteData *data)
+{
+  MetaPlugin *plugin = data->plugin;
+  ActorPrivate *actor_priv;
+  MetaWindowActor *window_actor = META_WINDOW_ACTOR (data->actor);
+  double original_scale = *(double *) data->effect_data;
+
+  actor_priv = get_actor_private (META_WINDOW_ACTOR (data->actor));
+  actor_priv->minimize_timeline = NULL;
+
+  clutter_actor_hide (data->actor);
+  clutter_actor_set_scale (data->actor, original_scale, original_scale);
+
+  meta_plugin_minimize_completed (plugin, window_actor);
+
+  g_free (data->effect_data);
+  g_free (data);
+}
+
+static void
+meta_test_shell_minimize (MetaPlugin      *plugin,
+                          MetaWindowActor *window_actor)
+{
+  MetaWindowType type;
+  MetaWindow *window = meta_window_actor_get_meta_window (window_actor);
+  ClutterTimeline *timeline = NULL;
+  ClutterActor *actor  = CLUTTER_ACTOR (window_actor);
+
+  type = meta_window_get_window_type (window);
+
+  if (type == META_WINDOW_NORMAL)
+    {
+      timeline = actor_animate (actor,
+                                CLUTTER_EASE_IN_SINE,
+                                ANIMATION_MINIMIZE,
+                                "scale-x", 0.0,
+                                "scale-y", 0.0,
+                                "x", (double) 0,
+                                "y", (double) 0,
+                                NULL);
+    }
+
+  if (timeline)
+    {
+      EffectCompleteData *data;
+      ActorPrivate *actor_priv = get_actor_private (window_actor);
+      double scale_x, scale_y;
+
+      data = g_new0 (EffectCompleteData, 1);
+      actor_priv->minimize_timeline = timeline;
+      data->plugin = plugin;
+      data->actor = actor;
+      data->effect_data = g_new0 (double, 1);
+      clutter_actor_get_scale (actor, &scale_x, &scale_y);
+      g_assert (scale_x == scale_y);
+      *((double *) data->effect_data) = scale_x;
+      g_signal_connect (actor_priv->minimize_timeline, "stopped",
+                        G_CALLBACK (on_minimize_effect_stopped),
+                        data);
+    }
+  else
+    {
+      meta_plugin_minimize_completed (plugin, window_actor);
+    }
+}
+
+static void
+on_map_effect_stopped (ClutterTimeline    *timeline,
+                       gboolean            is_finished,
+                       EffectCompleteData *data)
+{
+  MetaPlugin *plugin = data->plugin;
+  MetaWindowActor  *window_actor = META_WINDOW_ACTOR (data->actor);
+  ActorPrivate  *actor_priv = get_actor_private (window_actor);
+
+  actor_priv->map_timeline = NULL;
+
+  meta_plugin_map_completed (plugin, window_actor);
+
+  g_free (data);
+}
+
+static void
+meta_test_shell_map (MetaPlugin      *plugin,
+                     MetaWindowActor *window_actor)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (window_actor);
+  MetaWindow *window = meta_window_actor_get_meta_window (window_actor);
+  MetaWindowType type;
+
+  type = meta_window_get_window_type (window);
+
+  if (type == META_WINDOW_NORMAL)
+    {
+      EffectCompleteData *data = g_new0 (EffectCompleteData, 1);
+      ActorPrivate *actor_priv = get_actor_private (window_actor);
+
+      clutter_actor_set_pivot_point (actor, 0.5, 0.5);
+      clutter_actor_set_opacity (actor, 0);
+      clutter_actor_set_scale (actor, 0.5, 0.5);
+      clutter_actor_show (actor);
+
+      actor_priv->map_timeline = actor_animate (actor,
+                                                CLUTTER_EASE_OUT_QUAD,
+                                                ANIMATION_MAP,
+                                                "opacity", 255,
+                                                "scale-x", 1.0,
+                                                "scale-y", 1.0,
+                                                NULL);
+      if (actor_priv->map_timeline)
+        {
+          data->actor = actor;
+          data->plugin = plugin;
+          g_signal_connect (actor_priv->map_timeline, "stopped",
+                            G_CALLBACK (on_map_effect_stopped),
+                            data);
+        }
+      else
+        {
+          g_free (data);
+          meta_plugin_map_completed (plugin, window_actor);
+        }
+    }
+  else
+    {
+      meta_plugin_map_completed (plugin, window_actor);
+    }
+}
+
+static void
+on_destroy_effect_stopped (ClutterTimeline    *timeline,
+                           gboolean            is_finished,
+                           EffectCompleteData *data)
+{
+  MetaPlugin *plugin = data->plugin;
+  MetaWindowActor *window_actor = META_WINDOW_ACTOR (data->actor);
+  ActorPrivate *actor_priv = get_actor_private (window_actor);
+
+  actor_priv->destroy_timeline = NULL;
+
+  meta_plugin_destroy_completed (plugin, window_actor);
+}
+
+static void
+meta_test_shell_destroy (MetaPlugin      *plugin,
+                         MetaWindowActor *window_actor)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (window_actor);
+  MetaWindow *window = meta_window_actor_get_meta_window (window_actor);
+  MetaWindowType type;
+  ClutterTimeline *timeline = NULL;
+
+  type = meta_window_get_window_type (window);
+
+  if (type == META_WINDOW_NORMAL)
+    {
+      timeline = actor_animate (actor,
+                                CLUTTER_EASE_OUT_QUAD,
+                                ANIMATION_DESTROY,
+                                "opacity", 0,
+                                "scale-x", 0.8,
+                                "scale-y", 0.8,
+                                NULL);
+    }
+
+  if (timeline)
+    {
+      EffectCompleteData *data = g_new0 (EffectCompleteData, 1);
+      ActorPrivate *actor_priv = get_actor_private (window_actor);
+
+      actor_priv->destroy_timeline = timeline;
+      data->plugin = plugin;
+      data->actor = actor;
+      g_signal_connect (actor_priv->destroy_timeline, "stopped",
+                        G_CALLBACK (on_destroy_effect_stopped),
+                        data);
+    }
+  else
+    {
+      meta_plugin_destroy_completed (plugin, window_actor);
+    }
+}
+
+static void
+free_display_tile_preview (DisplayTilePreview *preview)
+{
+
+  if (preview)
+    {
+      clutter_actor_destroy (preview->actor);
+      g_free (preview);
+    }
+}
+
+static void
+on_display_closing (MetaDisplay        *display,
+                    DisplayTilePreview *preview)
+{
+  free_display_tile_preview (preview);
+}
+
+static DisplayTilePreview *
+get_display_tile_preview (MetaDisplay *display)
+{
+  DisplayTilePreview *preview;
+
+  if (!display_tile_preview_data_quark)
+    {
+      display_tile_preview_data_quark =
+        g_quark_from_static_string (DISPLAY_TILE_PREVIEW_DATA_KEY);
+    }
+
+  preview = g_object_get_qdata (G_OBJECT (display),
+                                display_tile_preview_data_quark);
+  if (!preview)
+    {
+      preview = g_new0 (DisplayTilePreview, 1);
+
+      preview->actor = clutter_actor_new ();
+      clutter_actor_set_background_color (preview->actor, CLUTTER_COLOR_Blue);
+      clutter_actor_set_opacity (preview->actor, 100);
+
+      clutter_actor_add_child (meta_get_window_group_for_display (display),
+                               preview->actor);
+      g_signal_connect (display,
+                        "closing",
+                        G_CALLBACK (on_display_closing),
+                        preview);
+      g_object_set_qdata (G_OBJECT (display),
+                          display_tile_preview_data_quark,
+                          preview);
+    }
+
+  return preview;
+}
+
+static void
+meta_test_shell_show_tile_preview (MetaPlugin    *plugin,
+                                   MetaWindow    *window,
+                                   MetaRectangle *tile_rect,
+                                   int            tile_monitor_number)
+{
+  MetaDisplay *display = meta_plugin_get_display (plugin);
+  DisplayTilePreview *preview = get_display_tile_preview (display);
+  ClutterActor *window_actor;
+
+  if (clutter_actor_is_visible (preview->actor) &&
+      preview->tile_rect.x == tile_rect->x &&
+      preview->tile_rect.y == tile_rect->y &&
+      preview->tile_rect.width == tile_rect->width &&
+      preview->tile_rect.height == tile_rect->height)
+    return;
+
+  clutter_actor_set_position (preview->actor, tile_rect->x, tile_rect->y);
+  clutter_actor_set_size (preview->actor, tile_rect->width, tile_rect->height);
+
+  clutter_actor_show (preview->actor);
+
+  window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window));
+  clutter_actor_set_child_below_sibling (clutter_actor_get_parent (preview->actor),
+                                         preview->actor,
+                                         window_actor);
+
+  preview->tile_rect = *tile_rect;
+}
+
+static void
+meta_test_shell_hide_tile_preview (MetaPlugin *plugin)
+{
+  MetaDisplay *display = meta_plugin_get_display (plugin);
+  DisplayTilePreview *preview = get_display_tile_preview (display);
+
+  clutter_actor_hide (preview->actor);
+}
+
+static void
+meta_test_shell_kill_switch_workspace (MetaPlugin *plugin)
+{
+  MetaTestShell *test_shell = META_TEST_SHELL (plugin);
+
+  kill_workspace_switch_animation (test_shell);
+}
+
+static void
+meta_test_shell_kill_window_effects (MetaPlugin      *plugin,
+                                     MetaWindowActor *window_actor)
+{
+  ActorPrivate *actor_priv;
+
+  actor_priv = get_actor_private (window_actor);
+
+  if (actor_priv->minimize_timeline)
+    finish_timeline (actor_priv->minimize_timeline);
+
+  if (actor_priv->map_timeline)
+    finish_timeline (actor_priv->map_timeline);
+
+  if (actor_priv->destroy_timeline)
+    finish_timeline (actor_priv->destroy_timeline);
+}
+
+static const MetaPluginInfo *
+meta_test_shell_plugin_info (MetaPlugin *plugin)
+{
+  MetaTestShell *test_shell = META_TEST_SHELL (plugin);
+
+  return &test_shell->info;
+}
+
+static void
+meta_test_shell_class_init (MetaTestShellClass *klass)
+{
+  MetaPluginClass *plugin_class  = META_PLUGIN_CLASS (klass);
+
+  plugin_class->start = meta_test_shell_start;
+  plugin_class->map = meta_test_shell_map;
+  plugin_class->minimize = meta_test_shell_minimize;
+  plugin_class->destroy = meta_test_shell_destroy;
+  plugin_class->switch_workspace = meta_test_shell_switch_workspace;
+  plugin_class->show_tile_preview = meta_test_shell_show_tile_preview;
+  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;
+  plugin_class->plugin_info = meta_test_shell_plugin_info;
+}
+
+static void
+meta_test_shell_init (MetaTestShell *test_shell)
+{
+  test_shell->info.name = "Test Shell";
+  test_shell->info.version = VERSION;
+  test_shell->info.author = "Mutter developers";
+  test_shell->info.license = "GPL";
+  test_shell->info.description = "This is test shell plugin implementation.";
+}
diff --git a/src/tests/meta-test-shell.h b/src/tests/meta-test-shell.h
new file mode 100644
index 0000000000..01f4c8d0eb
--- /dev/null
+++ b/src/tests/meta-test-shell.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023 Red Hat
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "meta/meta-plugin.h"
+
+#define META_TYPE_TEST_SHELL (meta_test_shell_get_type ())
+META_EXPORT
+G_DECLARE_FINAL_TYPE (MetaTestShell, meta_test_shell,
+                      META, TEST_SHELL,
+                      MetaPlugin)
diff --git a/src/tests/native-persistent-virtual-monitor.c b/src/tests/native-persistent-virtual-monitor.c
index 19a16cc5e8..a04eed1fb9 100644
--- a/src/tests/native-persistent-virtual-monitor.c
+++ b/src/tests/native-persistent-virtual-monitor.c
@@ -24,6 +24,7 @@
 #include "backends/meta-monitor-manager-private.h"
 #include "meta/meta-context.h"
 #include "meta/meta-backend.h"
+#include "tests/meta-test-shell.h"
 #include "tests/meta-test-utils.h"
 
 static gboolean
@@ -88,7 +89,7 @@ main (int    argc,
 
   context = meta_create_context ("Persistent virtual monitor test");
   g_assert (meta_context_configure (context, &fake_argc, &fake_argv, &error));
-  meta_context_set_plugin_name (context, meta_test_get_plugin_name ());
+  meta_context_set_plugin_gtype (context, META_TYPE_TEST_SHELL);
   g_assert (meta_context_setup (context, &error));
   g_assert (meta_context_start (context, &error));
 
diff --git a/src/tests/stacking/overview-focus.metatest b/src/tests/stacking/overview-focus.metatest
new file mode 100644
index 0000000000..07dfe5ac48
--- /dev/null
+++ b/src/tests/stacking/overview-focus.metatest
@@ -0,0 +1,44 @@
+new_client w wayland
+
+create w/1
+show w/1
+wait
+assert_focused w/1
+hide w/1
+destroy w/1
+
+toggle_overview
+
+create w/1
+show w/1
+wait
+assert_focused none
+
+toggle_overview
+
+assert_focused w/1
+hide w/1
+destroy w/1
+
+
+new_client x x11
+
+create x/1
+show x/1
+wait
+assert_focused x/1
+hide x/1
+destroy x/1
+
+toggle_overview
+
+create x/1
+show x/1
+wait
+assert_focused none
+
+toggle_overview
+
+assert_focused x/1
+hide x/1
+destroy x/1
diff --git a/src/tests/test-runner.c b/src/tests/test-runner.c
index bf08304e87..f758db6580 100644
--- a/src/tests/test-runner.c
+++ b/src/tests/test-runner.c
@@ -1175,6 +1175,15 @@ test_case_do (TestCase *test,
 
       meta_display_focus_default_window (display, timestamp);
     }
+  else if (strcmp (argv[0], "toggle_overview") == 0)
+    {
+      MetaDisplay *display = meta_context_get_display (test->context);
+
+      if (argc != 1)
+        BAD_COMMAND ("usage: %s", argv[0]);
+
+      g_signal_emit_by_name (display, "overlay-key", 0);
+    }
   else
     {
       BAD_COMMAND("Unknown command %s", argv[0]);
diff --git a/src/wayland/meta-wayland-touch.c b/src/wayland/meta-wayland-touch.c
index 5824e229d6..949252e5a0 100644
--- a/src/wayland/meta-wayland-touch.c
+++ b/src/wayland/meta-wayland-touch.c
@@ -553,6 +553,24 @@ meta_wayland_touch_can_popup (MetaWaylandTouch *touch,
   return FALSE;
 }
 
+static gboolean
+touch_can_grab_surface (MetaWaylandTouchInfo *touch_info,
+                        MetaWaylandSurface   *surface)
+{
+  MetaWaylandSurface *subsurface;
+
+  if (touch_info->touch_surface->surface == surface)
+    return TRUE;
+
+  META_WAYLAND_SURFACE_FOREACH_SUBSURFACE (surface, subsurface)
+    {
+      if (touch_can_grab_surface (touch_info, subsurface))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
 ClutterEventSequence *
 meta_wayland_touch_find_grab_sequence (MetaWaylandTouch   *touch,
                                        MetaWaylandSurface *surface,
@@ -571,7 +589,7 @@ meta_wayland_touch_find_grab_sequence (MetaWaylandTouch   *touch,
                                  (gpointer*) &touch_info))
     {
       if (touch_info->slot_serial == serial &&
-	  touch_info->touch_surface->surface == surface)
+          touch_can_grab_surface (touch_info, surface))
         return sequence;
     }
 

Reply via email to