diff --git a/SOURCES/0001-clutter-Backport-of-touch-mode.patch b/SOURCES/0001-clutter-Backport-of-touch-mode.patch
new file mode 100644
index 0000000..dff32fa
--- /dev/null
+++ b/SOURCES/0001-clutter-Backport-of-touch-mode.patch
@@ -0,0 +1,357 @@
+From 2a2e870c139e2130b00d582546616269bca27458 Mon Sep 17 00:00:00 2001
+From: Carlos Garnacho <carlosg@gnome.org>
+Date: Fri, 4 Sep 2020 17:11:36 +0200
+Subject: [PATCH] clutter: Backport of ::touch-mode
+
+In upstream/master this is a ClutterSeat readonly property. Add it to
+ClutterDeviceManager here, the mechanism and triggering is the same
+though.
+---
+ clutter/clutter/clutter-device-manager.c      |  24 +++
+ clutter/clutter/clutter-device-manager.h      |   2 +
+ .../evdev/clutter-device-manager-evdev.c      | 179 ++++++++++++++++++
+ 3 files changed, 205 insertions(+)
+
+diff --git a/clutter/clutter/clutter-device-manager.c b/clutter/clutter/clutter-device-manager.c
+index c676384..e1cc455 100644
+--- a/clutter/clutter/clutter-device-manager.c
++++ b/clutter/clutter/clutter-device-manager.c
+@@ -62,6 +62,7 @@ enum
+   PROP_0,
+ 
+   PROP_BACKEND,
++  PROP_TOUCH_MODE,
+ 
+   PROP_LAST
+ };
+@@ -108,6 +109,7 @@ clutter_device_manager_set_property (GObject      *gobject,
+       priv->backend = g_value_get_object (value);
+       break;
+ 
++    case PROP_TOUCH_MODE:
+     default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+     }
+@@ -127,6 +129,10 @@ clutter_device_manager_get_property (GObject    *gobject,
+       g_value_set_object (value, priv->backend);
+       break;
+ 
++    case PROP_TOUCH_MODE:
++      g_value_set_boolean (value, FALSE);
++      break;
++
+     default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+     }
+@@ -143,6 +149,12 @@ clutter_device_manager_class_init (ClutterDeviceManagerClass *klass)
+                          P_("The ClutterBackend of the device manager"),
+                          CLUTTER_TYPE_BACKEND,
+                          CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
++  obj_props[PROP_TOUCH_MODE] =
++    g_param_spec_boolean ("touch-mode",
++			  P_("Touch mode"),
++			  P_("Touch mode"),
++			  FALSE,
++			  CLUTTER_PARAM_READABLE);
+ 
+   gobject_class->set_property = clutter_device_manager_set_property;
+   gobject_class->get_property = clutter_device_manager_get_property;
+@@ -579,3 +591,15 @@ clutter_device_manager_get_kbd_a11y_settings (ClutterDeviceManager   *device_man
+ 
+   *settings = device_manager->priv->kbd_a11y_settings;
+ }
++
++gboolean
++clutter_device_manager_get_touch_mode (ClutterDeviceManager *device_manager)
++{
++  gboolean touch_mode;
++
++  g_return_val_if_fail (CLUTTER_IS_DEVICE_MANAGER (device_manager), FALSE);
++
++  g_object_get (G_OBJECT (device_manager), "touch-mode", &touch_mode, NULL);
++
++  return touch_mode;
++}
+diff --git a/clutter/clutter/clutter-device-manager.h b/clutter/clutter/clutter-device-manager.h
+index 1cbf030..a4a6271 100644
+--- a/clutter/clutter/clutter-device-manager.h
++++ b/clutter/clutter/clutter-device-manager.h
+@@ -155,6 +155,8 @@ void clutter_device_manager_set_kbd_a11y_settings (ClutterDeviceManager   *devic
+ CLUTTER_EXPORT
+ void clutter_device_manager_get_kbd_a11y_settings (ClutterDeviceManager   *device_manager,
+                                                    ClutterKbdA11ySettings *settings);
++CLUTTER_EXPORT
++gboolean clutter_device_manager_get_touch_mode (ClutterDeviceManager *device_manager);
+ 
+ G_END_DECLS
+ 
+diff --git a/clutter/clutter/evdev/clutter-device-manager-evdev.c b/clutter/clutter/evdev/clutter-device-manager-evdev.c
+index 84b0aad..78b5b64 100644
+--- a/clutter/clutter/evdev/clutter-device-manager-evdev.c
++++ b/clutter/clutter/evdev/clutter-device-manager-evdev.c
+@@ -108,6 +108,19 @@ struct _ClutterDeviceManagerEvdevPrivate
+ 
+   gint device_id_next;
+   GList *free_device_ids;
++
++  guint tablet_mode_switch_state : 1;
++  guint has_touchscreen          : 1;
++  guint has_tablet_switch        : 1;
++  guint has_pointer              : 1;
++  guint touch_mode               : 1;
++};
++
++enum
++{
++  PROP_0,
++  PROP_TOUCH_MODE,
++  N_PROPS
+ };
+ 
+ static void clutter_device_manager_evdev_event_extender_init (ClutterEventExtenderInterface *iface);
+@@ -765,6 +778,34 @@ clutter_event_source_free (ClutterEventSource *source)
+   g_source_unref (g_source);
+ }
+ 
++static void
++update_touch_mode (ClutterDeviceManagerEvdev *manager_evdev)
++{
++  ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
++  gboolean touch_mode;
++
++  /* No touch mode if we don't have a touchscreen, easy */
++  if (!priv->has_touchscreen)
++    touch_mode = FALSE;
++  /* If we have a tablet mode switch, honor it being unset */
++  else if (priv->has_tablet_switch && !priv->tablet_mode_switch_state)
++    touch_mode = FALSE;
++  /* If tablet mode is enabled, go for it */
++  else if (priv->has_tablet_switch && priv->tablet_mode_switch_state)
++    touch_mode = TRUE;
++  /* If there is no tablet mode switch (eg. kiosk machines),
++   * assume touch-mode is mutually exclusive with pointers.
++   */
++  else
++    touch_mode = !priv->has_pointer;
++
++  if (priv->touch_mode != touch_mode)
++    {
++      priv->touch_mode = touch_mode;
++      g_object_notify (G_OBJECT (manager_evdev), "touch-mode");
++    }
++}
++
+ static void
+ evdev_add_device (ClutterDeviceManagerEvdev *manager_evdev,
+                   struct libinput_device    *libinput_device)
+@@ -942,19 +983,81 @@ flush_event_queue (void)
+     }
+ }
+ 
++static gboolean
++has_touchscreen (ClutterDeviceManagerEvdev *manager_evdev)
++{
++  ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
++  GSList *l;
++
++  for (l = priv->devices; l; l = l->next)
++    {
++      ClutterInputDeviceType device_type;
++
++      device_type = clutter_input_device_get_device_type (l->data);
++
++      if (device_type == CLUTTER_TOUCHSCREEN_DEVICE)
++        return TRUE;
++    }
++
++  return FALSE;
++}
++
++static gboolean
++device_type_is_pointer (ClutterInputDeviceType device_type)
++{
++  return (device_type == CLUTTER_POINTER_DEVICE ||
++          device_type == CLUTTER_TOUCHPAD_DEVICE);
++}
++
++static gboolean
++has_pointer (ClutterDeviceManagerEvdev *manager_evdev)
++{
++  ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
++  GSList *l;
++
++  for (l = priv->devices; l; l = l->next)
++    {
++      ClutterInputDeviceType device_type;
++
++      device_type = clutter_input_device_get_device_type (l->data);
++
++      if (device_type_is_pointer (device_type))
++        return TRUE;
++    }
++
++  return FALSE;
++}
++
+ static gboolean
+ process_base_event (ClutterDeviceManagerEvdev *manager_evdev,
+                     struct libinput_event *event)
+ {
++  ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
+   ClutterInputDevice *device;
+   struct libinput_device *libinput_device;
+   gboolean handled = TRUE;
++  gboolean check_touch_mode;
+ 
+   switch (libinput_event_get_type (event))
+     {
+     case LIBINPUT_EVENT_DEVICE_ADDED:
+       libinput_device = libinput_event_get_device (event);
+ 
++      priv->has_touchscreen |=
++        libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_TOUCH);
++      priv->has_pointer |=
++        libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_POINTER);
++      check_touch_mode = priv->has_touchscreen | priv->has_pointer;
++
++      if (libinput_device_has_capability (libinput_device,
++                                          LIBINPUT_DEVICE_CAP_SWITCH) &&
++          libinput_device_switch_has_switch (libinput_device,
++                                             LIBINPUT_SWITCH_TABLET_MODE))
++        {
++          priv->has_tablet_switch = TRUE;
++          check_touch_mode = TRUE;
++        }
++
+       evdev_add_device (manager_evdev, libinput_device);
+       break;
+ 
+@@ -966,7 +1069,17 @@ process_base_event (ClutterDeviceManagerEvdev *manager_evdev,
+ 
+       libinput_device = libinput_event_get_device (event);
+ 
++      check_touch_mode =
++        libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_TOUCH);
+       device = libinput_device_get_user_data (libinput_device);
++      if (check_touch_mode)
++        priv->has_touchscreen = has_touchscreen (manager_evdev);
++      if (device_type_is_pointer (clutter_input_device_get_device_type (device)))
++        {
++          priv->has_pointer = has_pointer (manager_evdev);
++          check_touch_mode = TRUE;
++        }
++
+       evdev_remove_device (manager_evdev,
+                            CLUTTER_INPUT_DEVICE_EVDEV (device));
+       break;
+@@ -975,6 +1088,9 @@ process_base_event (ClutterDeviceManagerEvdev *manager_evdev,
+       handled = FALSE;
+     }
+ 
++  if (check_touch_mode)
++    update_touch_mode (manager_evdev);
++
+   return handled;
+ }
+ 
+@@ -1752,6 +1868,23 @@ process_device_event (ClutterDeviceManagerEvdev *manager_evdev,
+         notify_pad_ring (device, time, number, source, group, mode, angle);
+         break;
+       }
++    case LIBINPUT_EVENT_SWITCH_TOGGLE:
++      {
++        ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
++        struct libinput_event_switch *switch_event =
++          libinput_event_get_switch_event (event);
++        enum libinput_switch sw =
++          libinput_event_switch_get_switch (switch_event);
++        enum libinput_switch_state state =
++          libinput_event_switch_get_switch_state (switch_event);
++
++        if (sw == LIBINPUT_SWITCH_TABLET_MODE)
++          {
++            priv->tablet_mode_switch_state = (state == LIBINPUT_SWITCH_STATE_ON);
++            update_touch_mode (manager_evdev);
++          }
++        break;
++      }
+     default:
+       handled = FALSE;
+     }
+@@ -1967,6 +2100,10 @@ clutter_device_manager_evdev_constructed (GObject *gobject)
+ 
+   source = clutter_event_source_new (manager_evdev);
+   priv->event_source = source;
++
++  priv->has_touchscreen = has_touchscreen (manager_evdev);
++  priv->has_pointer = has_pointer (manager_evdev);
++  update_touch_mode (manager_evdev);
+ }
+ 
+ static void
+@@ -2001,6 +2138,43 @@ clutter_device_manager_evdev_dispose (GObject *object)
+   G_OBJECT_CLASS (clutter_device_manager_evdev_parent_class)->dispose (object);
+ }
+ 
++static void
++clutter_device_manager_evdev_set_property (GObject      *object,
++                                           guint         prop_id,
++                                           const GValue *value,
++                                           GParamSpec   *pspec)
++{
++  switch (prop_id)
++    {
++    case PROP_TOUCH_MODE:
++    default:
++      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++    }
++}
++
++static void
++clutter_device_manager_evdev_get_property (GObject    *object,
++                                           guint       prop_id,
++                                           GValue     *value,
++                                           GParamSpec *pspec)
++{
++  ClutterDeviceManagerEvdev *manager_evdev;
++  ClutterDeviceManagerEvdevPrivate *priv;
++
++  manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (object);
++  priv = manager_evdev->priv;
++
++  switch (prop_id)
++    {
++    case PROP_TOUCH_MODE:
++      g_value_set_boolean (value, priv->touch_mode);
++      break;
++    default:
++      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++    }
++}
++
++
+ static void
+ clutter_device_manager_evdev_finalize (GObject *object)
+ {
+@@ -2036,6 +2210,8 @@ clutter_device_manager_evdev_class_init (ClutterDeviceManagerEvdevClass *klass)
+   gobject_class->constructed = clutter_device_manager_evdev_constructed;
+   gobject_class->finalize = clutter_device_manager_evdev_finalize;
+   gobject_class->dispose = clutter_device_manager_evdev_dispose;
++  gobject_class->set_property = clutter_device_manager_evdev_set_property;
++  gobject_class->get_property = clutter_device_manager_evdev_get_property;
+ 
+   manager_class = CLUTTER_DEVICE_MANAGER_CLASS (klass);
+   manager_class->add_device = clutter_device_manager_evdev_add_device;
+@@ -2047,6 +2223,9 @@ clutter_device_manager_evdev_class_init (ClutterDeviceManagerEvdevClass *klass)
+   manager_class->get_supported_virtual_device_types = clutter_device_manager_evdev_get_supported_virtual_device_types;
+   manager_class->compress_motion = clutter_device_manager_evdev_compress_motion;
+   manager_class->apply_kbd_a11y_settings = clutter_device_manager_evdev_apply_kbd_a11y_settings;
++
++  g_object_class_override_property (gobject_class, PROP_TOUCH_MODE,
++                                    "touch-mode");
+ }
+ 
+ static void
+-- 
+2.29.2
+
diff --git a/SOURCES/geometric-picking.patch b/SOURCES/geometric-picking.patch
new file mode 100644
index 0000000..6892dca
--- /dev/null
+++ b/SOURCES/geometric-picking.patch
@@ -0,0 +1,2091 @@
+From ac946bf95ce3e4dc900f72dcb4189dd49bdb3155 Mon Sep 17 00:00:00 2001
+From: Daniel van Vugt <daniel.van.vugt@canonical.com>
+Date: Thu, 18 Jul 2019 16:56:41 +0800
+Subject: [PATCH 1/4] clutter/point: Add ClutterPoint quarilateral testing API
+
+Add a function to check whether a point is inside a quadrilateral
+by checking the cross product of vectors with the quadrilateral
+points, and the point being checked.
+
+If the passed quadrilateral is zero-sized, no point is ever reported
+to be inside it.
+
+This will be used by the next commit when comparing the transformed
+actor vertices.
+
+[feaneron: add a commit message and remove unecessary code]
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/189
+---
+ clutter/clutter/clutter-base-types.c | 62 ++++++++++++++++++++++++++++
+ clutter/clutter/clutter-types.h      |  3 ++
+ 2 files changed, 65 insertions(+)
+
+diff --git a/clutter/clutter/clutter-base-types.c b/clutter/clutter/clutter-base-types.c
+index aeb25c90ef..c84f9aa64b 100644
+--- a/clutter/clutter/clutter-base-types.c
++++ b/clutter/clutter/clutter-base-types.c
+@@ -570,6 +570,68 @@ G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterPoint, clutter_point,
+                                clutter_point_free,
+                                CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_point_progress))
+ 
++static int
++clutter_point_compare_line (const ClutterPoint *p,
++                            const ClutterPoint *a,
++                            const ClutterPoint *b)
++{
++  float x1 = b->x - a->x;
++  float y1 = b->y - a->y;
++  float x2 = p->x - a->x;
++  float y2 = p->y - a->y;
++  float cross_z = x1 * y2 - y1 * x2;
++
++  if (cross_z > 0.f)
++    return 1;
++  else if (cross_z < 0.f)
++    return -1;
++  else
++    return 0;
++}
++
++/**
++ * clutter_point_inside_quadrilateral:
++ * @point: a #ClutterPoint to test
++ * @vertices: array of vertices of the quadrilateral, in clockwise order,
++ *            from top-left to bottom-left
++ *
++ * Determines whether a point is inside the convex quadrilateral provided,
++ * and not on any of its edges or vertices.
++ *
++ * Returns: %TRUE if @point is inside the quadrilateral
++ */
++gboolean
++clutter_point_inside_quadrilateral (const ClutterPoint *point,
++                                    const ClutterPoint *vertices)
++{
++  unsigned int i;
++  int first_side;
++
++  first_side = 0;
++
++  for (i = 0; i < 4; i++)
++    {
++      int side;
++
++      side = clutter_point_compare_line (point,
++                                         &vertices[i],
++                                         &vertices[(i + 1) % 4]);
++
++      if (side)
++        {
++          if (first_side == 0)
++            first_side = side;
++          else if (side != first_side)
++            return FALSE;
++        }
++    }
++
++  if (first_side == 0)
++    return FALSE;
++
++  return TRUE;
++}
++
+ 
+ 
+ /*
+diff --git a/clutter/clutter/clutter-types.h b/clutter/clutter/clutter-types.h
+index 0f0fb1c2ac..0508028273 100644
+--- a/clutter/clutter/clutter-types.h
++++ b/clutter/clutter/clutter-types.h
+@@ -200,6 +200,9 @@ float                   clutter_point_distance  (const ClutterPoint *a,
+                                                  const ClutterPoint *b,
+                                                  float              *x_distance,
+                                                  float              *y_distance);
++CLUTTER_EXPORT
++gboolean clutter_point_inside_quadrilateral     (const ClutterPoint *point,
++                                                 const ClutterPoint *vertices);
+ 
+ /**
+  * ClutterSize:
+-- 
+2.29.2
+
+
+From 8abac81711cfef8317bb675349d6b5b0a79eb05d Mon Sep 17 00:00:00 2001
+From: Daniel van Vugt <daniel.van.vugt@canonical.com>
+Date: Thu, 2 Aug 2018 19:03:30 +0800
+Subject: [PATCH 2/4] clutter: Introduce geometric picking
+
+Currently, Clutter does picking by drawing with Cogl and reading
+the pixel that's beneath the given point. Since Cogl has a journal
+that records drawing operations, and has optimizations to read a
+single pixel from a list of rectangle, it would be expected that
+we would hit this fast path and not flush the journal while picking.
+
+However, that's not the case: dithering, clipping with scissors, etc,
+can all flush the journal, issuing commands to the GPU and making
+picking slow. On NVidia-based systems, this glReadPixels() call is
+extremely costly.
+
+Introduce geometric picking, and avoid using the Cogl journal entirely.
+Do this by introducing a stack of actors in ClutterStage. This stack
+is cached, but for now, don't use the cache as much as possible.
+
+The picking routines are still tied to painting.
+
+When projecting the actor vertexes, do it manually and take the modelview
+matrix of the framebuffer into account as well.
+
+CPU usage on an Intel i7-7700, tested with two different GPUs/drivers:
+
+  |         |     Intel | Nvidia |
+  | ------: | --------: | -----: |
+  | Moving the mouse:            |
+  | Before  |       10% |    10% |
+  | After   |        6% |     6% |
+  | Moving a window:             |
+  | Before  |       23% |    81% |
+  | After   |       19% |    40% |
+
+Closes: https://gitlab.gnome.org/GNOME/mutter/issues/154,
+        https://gitlab.gnome.org/GNOME/mutter/issues/691
+
+Helps significantly with: https://gitlab.gnome.org/GNOME/mutter/issues/283,
+                          https://gitlab.gnome.org/GNOME/mutter/issues/590,
+                          https://gitlab.gnome.org/GNOME/mutter/issues/700
+
+v2: Fix code style issues
+    Simplify quadrilateral checks
+    Remove the 0.5f hack
+    Differentiate axis-aligned rectangles
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/189
+---
+ clutter/clutter/clutter-actor-private.h      |   2 -
+ clutter/clutter/clutter-actor.c              | 232 ++++++----
+ clutter/clutter/clutter-actor.h              |   4 +
+ clutter/clutter/clutter-debug.h              |   1 -
+ clutter/clutter/clutter-main.c               | 120 -----
+ clutter/clutter/clutter-private.h            |   5 -
+ clutter/clutter/clutter-stage-private.h      |  16 +-
+ clutter/clutter/clutter-stage-window.c       |  18 -
+ clutter/clutter/clutter-stage-window.h       |   8 -
+ clutter/clutter/clutter-stage.c              | 459 +++++++++++--------
+ clutter/clutter/cogl/clutter-stage-cogl.c    |  52 ---
+ clutter/clutter/deprecated/clutter-texture.c |  93 +---
+ clutter/tests/conform/actor-pick.c           |  99 +---
+ clutter/tests/conform/meson.build            |   1 -
+ clutter/tests/conform/texture.c              |  84 ----
+ src/compositor/meta-surface-actor.c          |  27 +-
+ 16 files changed, 439 insertions(+), 782 deletions(-)
+ delete mode 100644 clutter/tests/conform/texture.c
+
+diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h
+index c44f6342fd..9bf1a30493 100644
+--- a/clutter/clutter/clutter-actor-private.h
++++ b/clutter/clutter/clutter-actor-private.h
+@@ -297,8 +297,6 @@ const gchar *                   _clutter_actor_get_debug_name
+ void                            _clutter_actor_push_clone_paint                         (void);
+ void                            _clutter_actor_pop_clone_paint                          (void);
+ 
+-guint32                         _clutter_actor_get_pick_id                              (ClutterActor *self);
+-
+ void                            _clutter_actor_shader_pre_paint                         (ClutterActor *actor,
+                                                                                          gboolean      repeat);
+ void                            _clutter_actor_shader_post_paint                        (ClutterActor *actor);
+diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c
+index 43093fe79d..01ffa51caa 100644
+--- a/clutter/clutter/clutter-actor.c
++++ b/clutter/clutter/clutter-actor.c
+@@ -730,8 +730,6 @@ struct _ClutterActorPrivate
+ 
+   gchar *name; /* a non-unique name, used for debugging */
+ 
+-  gint32 pick_id; /* per-stage unique id, used for picking */
+-
+   /* a back-pointer to the Pango context that we can use
+    * to create pre-configured PangoLayout
+    */
+@@ -1281,6 +1279,105 @@ clutter_actor_verify_map_state (ClutterActor *self)
+ 
+ #endif /* CLUTTER_ENABLE_DEBUG */
+ 
++static gboolean
++_clutter_actor_transform_local_box_to_stage (ClutterActor          *self,
++                                             ClutterStage          *stage,
++                                             const ClutterActorBox *box,
++                                             ClutterPoint           vertices[4])
++{
++  CoglFramebuffer *fb = cogl_get_draw_framebuffer ();
++  CoglMatrix stage_transform, inv_stage_transform;
++  CoglMatrix modelview, transform_to_stage;
++  int v;
++
++  clutter_actor_get_transform (CLUTTER_ACTOR (stage), &stage_transform);
++  if (!cogl_matrix_get_inverse (&stage_transform, &inv_stage_transform))
++    return FALSE;
++  cogl_framebuffer_get_modelview_matrix (fb, &modelview);
++  cogl_matrix_multiply (&transform_to_stage, &inv_stage_transform, &modelview);
++
++  vertices[0].x = box->x1;
++  vertices[0].y = box->y1;
++
++  vertices[1].x = box->x2;
++  vertices[1].y = box->y1;
++
++  vertices[2].x = box->x2;
++  vertices[2].y = box->y2;
++
++  vertices[3].x = box->x1;
++  vertices[3].y = box->y2;
++
++  for (v = 0; v < 4; v++)
++    {
++      float z = 0.f;
++      float w = 1.f;
++
++      cogl_matrix_transform_point (&transform_to_stage,
++                                   &vertices[v].x,
++                                   &vertices[v].y,
++                                   &z,
++                                   &w);
++    }
++
++  return TRUE;
++}
++
++/**
++ * clutter_actor_pick_box:
++ * @self: The #ClutterActor being "pick" painted.
++ * @box: A rectangle in the actor's own local coordinates.
++ *
++ * Logs (does a virtual paint of) a rectangle for picking. Note that @box is
++ * in the actor's own local coordinates, so is usually {0,0,width,height}
++ * to include the whole actor. That is unless the actor has a shaped input
++ * region in which case you may wish to log the (multiple) smaller rectangles
++ * that make up the input region.
++ */
++void
++clutter_actor_pick_box (ClutterActor          *self,
++                        const ClutterActorBox *box)
++{
++  ClutterStage *stage;
++  ClutterPoint vertices[4];
++
++  g_return_if_fail (CLUTTER_IS_ACTOR (self));
++  g_return_if_fail (box != NULL);
++
++  if (box->x1 >= box->x2 || box->y1 >= box->y2)
++    return;
++
++  stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self));
++
++  if (_clutter_actor_transform_local_box_to_stage (self, stage, box, vertices))
++    clutter_stage_log_pick (stage, vertices, self);
++}
++
++static gboolean
++_clutter_actor_push_pick_clip (ClutterActor          *self,
++                               const ClutterActorBox *clip)
++{
++  ClutterStage *stage;
++  ClutterPoint vertices[4];
++
++  stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self));
++
++  if (!_clutter_actor_transform_local_box_to_stage (self, stage, clip, vertices))
++    return FALSE;
++
++  clutter_stage_push_pick_clip (stage, vertices);
++  return TRUE;
++}
++
++static void
++_clutter_actor_pop_pick_clip (ClutterActor *self)
++{
++  ClutterActor *stage;
++
++  stage = _clutter_actor_get_stage_internal (self);
++  clutter_stage_pop_pick_clip (CLUTTER_STAGE (stage));
++}
++
+ static void
+ clutter_actor_set_mapped (ClutterActor *self,
+                           gboolean      mapped)
+@@ -1509,8 +1606,7 @@ clutter_actor_update_map_state (ClutterActor  *self,
+ static void
+ clutter_actor_real_map (ClutterActor *self)
+ {
+-  ClutterActorPrivate *priv = self->priv;
+-  ClutterActor *stage, *iter;
++  ClutterActor *iter;
+ 
+   g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
+ 
+@@ -1521,13 +1617,6 @@ clutter_actor_real_map (ClutterActor *self)
+ 
+   self->priv->needs_paint_volume_update = TRUE;
+ 
+-  stage = _clutter_actor_get_stage_internal (self);
+-  priv->pick_id = _clutter_stage_acquire_pick_id (CLUTTER_STAGE (stage), self);
+-
+-  CLUTTER_NOTE (ACTOR, "Pick id '%d' for actor '%s'",
+-                priv->pick_id,
+-                _clutter_actor_get_debug_name (self));
+-
+   clutter_actor_ensure_resource_scale (self);
+ 
+   /* notify on parent mapped before potentially mapping
+@@ -1632,11 +1721,6 @@ clutter_actor_real_unmap (ClutterActor *self)
+ 
+       stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self));
+ 
+-      if (stage != NULL)
+-        _clutter_stage_release_pick_id (stage, priv->pick_id);
+-
+-      priv->pick_id = -1;
+-
+       if (stage != NULL &&
+           clutter_stage_get_key_focus (stage) == self)
+         {
+@@ -2255,46 +2339,16 @@ static void
+ clutter_actor_real_pick (ClutterActor       *self,
+ 			 const ClutterColor *color)
+ {
+-  CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer ();
+-
+-  /* the default implementation is just to paint a rectangle
+-   * with the same size of the actor using the passed color
+-   */
+   if (clutter_actor_should_pick_paint (self))
+     {
+-      static CoglPipeline *default_pick_pipeline = NULL;
+-      ClutterActorBox box = { 0, };
+-      CoglPipeline *pick_pipeline;
+-      float width, height;
+-
+-      if (G_UNLIKELY (default_pick_pipeline == NULL))
+-        {
+-          CoglContext *ctx =
+-            clutter_backend_get_cogl_context (clutter_get_default_backend ());
+-
+-          default_pick_pipeline = cogl_pipeline_new (ctx);
+-        }
+-
+-      g_assert (default_pick_pipeline != NULL);
+-      pick_pipeline = cogl_pipeline_copy (default_pick_pipeline);
++      ClutterActorBox box = {
++        .x1 = 0,
++        .y1 = 0,
++        .x2 = clutter_actor_get_width (self),
++        .y2 = clutter_actor_get_height (self),
++      };
+ 
+-      clutter_actor_get_allocation_box (self, &box);
+-
+-      width = box.x2 - box.x1;
+-      height = box.y2 - box.y1;
+-
+-      cogl_pipeline_set_color4ub (pick_pipeline,
+-                                  color->red,
+-                                  color->green,
+-                                  color->blue,
+-                                  color->alpha);
+-
+-      cogl_framebuffer_draw_rectangle (framebuffer,
+-                                       pick_pipeline,
+-                                       0, 0,
+-                                       width, height);
+-
+-      cogl_object_unref (pick_pipeline);
++      clutter_actor_pick_box (self, &box);
+     }
+ 
+   /* XXX - this thoroughly sucks, but we need to maintain compatibility
+@@ -3585,15 +3639,6 @@ _clutter_actor_update_last_paint_volume (ClutterActor *self)
+   priv->last_paint_volume_valid = TRUE;
+ }
+ 
+-guint32
+-_clutter_actor_get_pick_id (ClutterActor *self)
+-{
+-  if (self->priv->pick_id < 0)
+-    return 0;
+-
+-  return self->priv->pick_id;
+-}
+-
+ /* This is the same as clutter_actor_add_effect except that it doesn't
+    queue a redraw and it doesn't notify on the effect property */
+ static void
+@@ -3826,6 +3871,7 @@ clutter_actor_paint (ClutterActor *self)
+   ClutterActorPrivate *priv;
+   ClutterPickMode pick_mode;
+   gboolean culling_inhibited;
++  ClutterActorBox clip;
+   gboolean clip_set = FALSE;
+   ClutterStage *stage;
+ 
+@@ -3919,24 +3965,38 @@ clutter_actor_paint (ClutterActor *self)
+ 
+   if (priv->has_clip)
+     {
+-      CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
+-      cogl_framebuffer_push_rectangle_clip (fb,
+-                                            priv->clip.origin.x,
+-                                            priv->clip.origin.y,
+-                                            priv->clip.origin.x + priv->clip.size.width,
+-                                            priv->clip.origin.y + priv->clip.size.height);
++      clip.x1 = priv->clip.origin.x;
++      clip.y1 = priv->clip.origin.y;
++      clip.x2 = priv->clip.origin.x + priv->clip.size.width;
++      clip.y2 = priv->clip.origin.y + priv->clip.size.height;
+       clip_set = TRUE;
+     }
+   else if (priv->clip_to_allocation)
+     {
+-      CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
+-      gfloat width, height;
++      clip.x1 = 0.f;
++      clip.y1 = 0.f;
++      clip.x2 = priv->allocation.x2 - priv->allocation.x1;
++      clip.y2 = priv->allocation.y2 - priv->allocation.y1;
++      clip_set = TRUE;
++    }
+ 
+-      width  = priv->allocation.x2 - priv->allocation.x1;
+-      height = priv->allocation.y2 - priv->allocation.y1;
++  if (clip_set)
++    {
++      if (pick_mode == CLUTTER_PICK_NONE)
++        {
++          CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
+ 
+-      cogl_framebuffer_push_rectangle_clip (fb, 0, 0, width, height);
+-      clip_set = TRUE;
++          cogl_framebuffer_push_rectangle_clip (fb,
++                                                clip.x1,
++                                                clip.y1,
++                                                clip.x2,
++                                                clip.y2);
++        }
++      else
++        {
++          if (!_clutter_actor_push_pick_clip (self, &clip))
++            clip_set = FALSE;
++        }
+     }
+ 
+   if (pick_mode == CLUTTER_PICK_NONE)
+@@ -4020,9 +4080,16 @@ clutter_actor_paint (ClutterActor *self)
+ done:
+   if (clip_set)
+     {
+-      CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
++      if (pick_mode == CLUTTER_PICK_NONE)
++        {
++          CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
+ 
+-      cogl_framebuffer_pop_clip (fb);
++          cogl_framebuffer_pop_clip (fb);
++        }
++      else
++        {
++          _clutter_actor_pop_pick_clip (self);
++        }
+     }
+ 
+   cogl_pop_matrix ();
+@@ -4093,11 +4160,12 @@ clutter_actor_continue_paint (ClutterActor *self)
+         {
+           ClutterColor col = { 0, };
+ 
+-          _clutter_id_to_color (_clutter_actor_get_pick_id (self), &col);
+-
+-          /* Actor will then paint silhouette of itself in supplied
+-           * color.  See clutter_stage_get_actor_at_pos() for where
+-           * picking is enabled.
++          /* The actor will log a silhouette of itself to the stage pick log.
++           * Note that the picking color is no longer used as the "log" instead
++           * keeps a weak pointer to the actor itself. But we keep the color
++           * parameter for now so as to maintain ABI compatibility. The color
++           * parameter can be removed when someone feels like breaking the ABI
++           * along with gnome-shell.
+            *
+            * XXX:2.0 - Call the pick() virtual directly
+            */
+@@ -8603,8 +8671,6 @@ clutter_actor_init (ClutterActor *self)
+ 
+   self->priv = priv = clutter_actor_get_instance_private (self);
+ 
+-  priv->pick_id = -1;
+-
+   priv->opacity = 0xff;
+   priv->show_on_set_parent = TRUE;
+   priv->resource_scale = -1.0f;
+diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h
+index 3e7a59ac0c..16b438ba64 100644
+--- a/clutter/clutter/clutter-actor.h
++++ b/clutter/clutter/clutter-actor.h
+@@ -910,6 +910,10 @@ void                            clutter_actor_bind_model_with_properties
+                                                                                  const char                 *first_model_property,
+                                                                                  ...);
+ 
++CLUTTER_EXPORT
++void clutter_actor_pick_box (ClutterActor          *self,
++                             const ClutterActorBox *box);
++
+ G_END_DECLS
+ 
+ #endif /* __CLUTTER_ACTOR_H__ */
+diff --git a/clutter/clutter/clutter-debug.h b/clutter/clutter/clutter-debug.h
+index 2462385f65..7d170d2d54 100644
+--- a/clutter/clutter/clutter-debug.h
++++ b/clutter/clutter/clutter-debug.h
+@@ -30,7 +30,6 @@ typedef enum
+ typedef enum
+ {
+   CLUTTER_DEBUG_NOP_PICKING         = 1 << 0,
+-  CLUTTER_DEBUG_DUMP_PICK_BUFFERS   = 1 << 1
+ } ClutterPickDebugFlag;
+ 
+ typedef enum
+diff --git a/clutter/clutter/clutter-main.c b/clutter/clutter/clutter-main.c
+index 645c8bceb6..11c221a65b 100644
+--- a/clutter/clutter/clutter-main.c
++++ b/clutter/clutter/clutter-main.c
+@@ -131,7 +131,6 @@ static const GDebugKey clutter_debug_keys[] = {
+ 
+ static const GDebugKey clutter_pick_debug_keys[] = {
+   { "nop-picking", CLUTTER_DEBUG_NOP_PICKING },
+-  { "dump-pick-buffers", CLUTTER_DEBUG_DUMP_PICK_BUFFERS },
+ };
+ 
+ static const GDebugKey clutter_paint_debug_keys[] = {
+@@ -533,125 +532,6 @@ clutter_get_motion_events_enabled (void)
+   return _clutter_context_get_motion_events_enabled ();
+ }
+ 
+-void
+-_clutter_id_to_color (guint         id_,
+-                      ClutterColor *col)
+-{
+-  ClutterMainContext *ctx;
+-  gint red, green, blue;
+-
+-  ctx = _clutter_context_get_default ();
+-
+-  if (ctx->fb_g_mask == 0)
+-    {
+-      /* Figure out framebuffer masks used for pick */
+-      cogl_get_bitmasks (&ctx->fb_r_mask,
+-			 &ctx->fb_g_mask,
+-			 &ctx->fb_b_mask, NULL);
+-
+-      ctx->fb_r_mask_used = ctx->fb_r_mask;
+-      ctx->fb_g_mask_used = ctx->fb_g_mask;
+-      ctx->fb_b_mask_used = ctx->fb_b_mask;
+-
+-      /* XXX - describe what "fuzzy picking" is */
+-      if (clutter_use_fuzzy_picking)
+-	{
+-	  ctx->fb_r_mask_used--;
+-	  ctx->fb_g_mask_used--;
+-	  ctx->fb_b_mask_used--;
+-	}
+-    }
+-
+-  /* compute the numbers we'll store in the components */
+-  red   = (id_ >> (ctx->fb_g_mask_used+ctx->fb_b_mask_used))
+-        & (0xff >> (8-ctx->fb_r_mask_used));
+-  green = (id_ >> ctx->fb_b_mask_used)
+-        & (0xff >> (8-ctx->fb_g_mask_used));
+-  blue  = (id_)
+-        & (0xff >> (8-ctx->fb_b_mask_used));
+-
+-  /* shift left bits a bit and add one, this circumvents
+-   * at least some potential rounding errors in GL/GLES
+-   * driver / hw implementation.
+-   */
+-  if (ctx->fb_r_mask_used != ctx->fb_r_mask)
+-    red = red * 2;
+-  if (ctx->fb_g_mask_used != ctx->fb_g_mask)
+-    green = green * 2;
+-  if (ctx->fb_b_mask_used != ctx->fb_b_mask)
+-    blue  = blue  * 2;
+-
+-  /* shift up to be full 8bit values */
+-  red   = (red   << (8 - ctx->fb_r_mask)) | (0x7f >> (ctx->fb_r_mask_used));
+-  green = (green << (8 - ctx->fb_g_mask)) | (0x7f >> (ctx->fb_g_mask_used));
+-  blue  = (blue  << (8 - ctx->fb_b_mask)) | (0x7f >> (ctx->fb_b_mask_used));
+-
+-  col->red   = red;
+-  col->green = green;
+-  col->blue  = blue;
+-  col->alpha = 0xff;
+-
+-  /* XXX: We rotate the nibbles of the colors here so that there is a
+-   * visible variation between colors of sequential actor identifiers;
+-   * otherwise pick buffers dumped to an image will pretty much just look
+-   * black.
+-   */
+-  if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))
+-    {
+-      col->red   = (col->red << 4)   | (col->red >> 4);
+-      col->green = (col->green << 4) | (col->green >> 4);
+-      col->blue  = (col->blue << 4)  | (col->blue >> 4);
+-    }
+-}
+-
+-guint
+-_clutter_pixel_to_id (guchar pixel[4])
+-{
+-  ClutterMainContext *ctx;
+-  gint red, green, blue;
+-  guint retval;
+-
+-  ctx = _clutter_context_get_default ();
+-
+-  /* reduce the pixel components to the number of bits actually used of the
+-   * 8bits.
+-   */
+-  if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))
+-    {
+-      guchar tmp;
+-
+-      /* XXX: In _clutter_id_to_color we rotated the nibbles of the colors so
+-       * that there is a visible variation between colors of sequential actor
+-       * identifiers (otherwise pick buffers dumped to an image will pretty
+-       * much just look black.) Here we reverse that rotation.
+-       */
+-      tmp = ((pixel[0] << 4) | (pixel[0] >> 4));
+-      red = tmp >> (8 - ctx->fb_r_mask);
+-      tmp = ((pixel[1] << 4) | (pixel[1] >> 4));
+-      green = tmp >> (8 - ctx->fb_g_mask);
+-      tmp = ((pixel[2] << 4) | (pixel[2] >> 4));
+-      blue = tmp >> (8 - ctx->fb_b_mask);
+-    }
+-  else
+-    {
+-      red   = pixel[0] >> (8 - ctx->fb_r_mask);
+-      green = pixel[1] >> (8 - ctx->fb_g_mask);
+-      blue  = pixel[2] >> (8 - ctx->fb_b_mask);
+-    }
+-
+-  /* divide potentially by two if 'fuzzy' */
+-  red   = red   >> (ctx->fb_r_mask - ctx->fb_r_mask_used);
+-  green = green >> (ctx->fb_g_mask - ctx->fb_g_mask_used);
+-  blue  = blue  >> (ctx->fb_b_mask - ctx->fb_b_mask_used);
+-
+-  /* combine the correct per component values into the final id */
+-  retval = blue
+-         + (green <<  ctx->fb_b_mask_used)
+-         + (red << (ctx->fb_b_mask_used + ctx->fb_g_mask_used));
+-
+-  return retval;
+-}
+-
+ static CoglPangoFontMap *
+ clutter_context_get_pango_fontmap (void)
+ {
+diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h
+index 5a0fed85c9..f2f870b014 100644
+--- a/clutter/clutter/clutter-private.h
++++ b/clutter/clutter/clutter-private.h
+@@ -210,11 +210,6 @@ gboolean      _clutter_feature_init (GError **error);
+ gboolean        _clutter_diagnostic_enabled     (void);
+ void            _clutter_diagnostic_message     (const char *fmt, ...) G_GNUC_PRINTF (1, 2);
+ 
+-/* Picking code */
+-guint           _clutter_pixel_to_id            (guchar        pixel[4]);
+-void            _clutter_id_to_color            (guint         id,
+-                                                 ClutterColor *col);
+-
+ void            _clutter_set_sync_to_vblank     (gboolean      sync_to_vblank);
+ 
+ /* use this function as the accumulator if you have a signal with
+diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h
+index 42474687ad..51ae47af1d 100644
+--- a/clutter/clutter/clutter-stage-private.h
++++ b/clutter/clutter/clutter-stage-private.h
+@@ -75,6 +75,15 @@ gint64    _clutter_stage_get_update_time                  (ClutterStage *stage);
+ void     _clutter_stage_clear_update_time                 (ClutterStage *stage);
+ gboolean _clutter_stage_has_full_redraw_queued            (ClutterStage *stage);
+ 
++void clutter_stage_log_pick (ClutterStage       *stage,
++                             const ClutterPoint *vertices,
++                             ClutterActor       *actor);
++
++void clutter_stage_push_pick_clip (ClutterStage       *stage,
++                                   const ClutterPoint *vertices);
++
++void clutter_stage_pop_pick_clip (ClutterStage *stage);
++
+ ClutterActor *_clutter_stage_do_pick (ClutterStage    *stage,
+                                       gint             x,
+                                       gint             y,
+@@ -93,13 +102,6 @@ void                          _clutter_stage_queue_redraw_entry_invalidate (Clut
+ 
+ CoglFramebuffer *_clutter_stage_get_active_framebuffer (ClutterStage *stage);
+ 
+-gint32          _clutter_stage_acquire_pick_id          (ClutterStage *stage,
+-                                                         ClutterActor *actor);
+-void            _clutter_stage_release_pick_id          (ClutterStage *stage,
+-                                                         gint32        pick_id);
+-ClutterActor *  _clutter_stage_get_actor_by_pick_id     (ClutterStage *stage,
+-                                                         gint32        pick_id);
+-
+ void            _clutter_stage_add_pointer_drag_actor    (ClutterStage       *stage,
+                                                           ClutterInputDevice *device,
+                                                           ClutterActor       *actor);
+diff --git a/clutter/clutter/clutter-stage-window.c b/clutter/clutter/clutter-stage-window.c
+index e8fa976a7d..4c4ef9d643 100644
+--- a/clutter/clutter/clutter-stage-window.c
++++ b/clutter/clutter/clutter-stage-window.c
+@@ -293,24 +293,6 @@ _clutter_stage_window_redraw (ClutterStageWindow *window)
+     iface->redraw (window);
+ }
+ 
+-
+-void
+-_clutter_stage_window_get_dirty_pixel (ClutterStageWindow *window,
+-                                       ClutterStageView   *view,
+-                                       int *x, int *y)
+-{
+-  ClutterStageWindowInterface *iface;
+-
+-  *x = 0;
+-  *y = 0;
+-
+-  g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (window));
+-
+-  iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
+-  if (iface->get_dirty_pixel)
+-    iface->get_dirty_pixel (window, view, x, y);
+-}
+-
+ gboolean
+ _clutter_stage_window_can_clip_redraws (ClutterStageWindow *window)
+ {
+diff --git a/clutter/clutter/clutter-stage-window.h b/clutter/clutter/clutter-stage-window.h
+index 6c3601745f..aa0c5f71cc 100644
+--- a/clutter/clutter/clutter-stage-window.h
++++ b/clutter/clutter/clutter-stage-window.h
+@@ -68,10 +68,6 @@ struct _ClutterStageWindowInterface
+ 
+   void              (* redraw)                  (ClutterStageWindow *stage_window);
+ 
+-  void              (* get_dirty_pixel)         (ClutterStageWindow *stage_window,
+-                                                 ClutterStageView   *view,
+-                                                 int *x, int *y);
+-
+   gboolean          (* can_clip_redraws)        (ClutterStageWindow *stage_window);
+ 
+   GList            *(* get_views)               (ClutterStageWindow *stage_window);
+@@ -119,10 +115,6 @@ void              _clutter_stage_window_set_accept_focus        (ClutterStageWin
+ 
+ void              _clutter_stage_window_redraw                  (ClutterStageWindow *window);
+ 
+-void              _clutter_stage_window_get_dirty_pixel         (ClutterStageWindow *window,
+-                                                                 ClutterStageView   *view,
+-                                                                 int *x, int *y);
+-
+ gboolean          _clutter_stage_window_can_clip_redraws        (ClutterStageWindow *window);
+ 
+ GList *           _clutter_stage_window_get_views               (ClutterStageWindow *window);
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index aaa77d9ede..7d88d5752f 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -97,6 +97,11 @@ typedef enum /*< prefix=CLUTTER_STAGE >*/
+ 
+ #define STAGE_NO_CLEAR_ON_PAINT(s)      ((((ClutterStage *) (s))->priv->stage_hints & CLUTTER_STAGE_NO_CLEAR_ON_PAINT) != 0)
+ 
++#ifndef G_APPROX_VALUE
++#define G_APPROX_VALUE(a, b, epsilon) \
++  (((a) > (b) ? (a) - (b) : (b) - (a)) < (epsilon))
++#endif
++
+ struct _ClutterStageQueueRedrawEntry
+ {
+   ClutterActor *actor;
+@@ -104,6 +109,19 @@ struct _ClutterStageQueueRedrawEntry
+   ClutterPaintVolume clip;
+ };
+ 
++typedef struct _PickRecord
++{
++  ClutterPoint vertex[4];
++  ClutterActor *actor;
++  int clip_stack_top;
++} PickRecord;
++
++typedef struct _PickClipRecord
++{
++  int prev;
++  ClutterPoint vertex[4];
++} PickClipRecord;
++
+ struct _ClutterStagePrivate
+ {
+   /* the stage implementation */
+@@ -137,7 +155,11 @@ struct _ClutterStagePrivate
+   GTimer *fps_timer;
+   gint32 timer_n_frames;
+ 
+-  ClutterIDPool *pick_id_pool;
++  GArray *pick_stack;
++  GArray *pick_clip_stack;
++  int pick_clip_stack_top;
++  gboolean pick_stack_frozen;
++  ClutterPickMode cached_pick_mode;
+ 
+ #ifdef CLUTTER_ENABLE_DEBUG
+   gulong redraw_count;
+@@ -326,6 +348,211 @@ clutter_stage_get_preferred_height (ClutterActor *self,
+     *natural_height_p = geom.height;
+ }
+ 
++static void
++add_pick_stack_weak_refs (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv = stage->priv;
++  int i;
++
++  if (priv->pick_stack_frozen)
++    return;
++
++  for (i = 0; i < priv->pick_stack->len; i++)
++    {
++      PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
++
++      if (rec->actor)
++        g_object_add_weak_pointer (G_OBJECT (rec->actor),
++                                   (gpointer) &rec->actor);
++    }
++
++  priv->pick_stack_frozen = TRUE;
++}
++
++static void
++remove_pick_stack_weak_refs (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv = stage->priv;
++  int i;
++
++  if (!priv->pick_stack_frozen)
++    return;
++
++  for (i = 0; i < priv->pick_stack->len; i++)
++    {
++      PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
++
++      if (rec->actor)
++        g_object_remove_weak_pointer (G_OBJECT (rec->actor),
++                                      (gpointer) &rec->actor);
++    }
++
++  priv->pick_stack_frozen = FALSE;
++}
++
++static void
++_clutter_stage_clear_pick_stack (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv = stage->priv;
++
++  remove_pick_stack_weak_refs (stage);
++  g_array_set_size (priv->pick_stack, 0);
++  g_array_set_size (priv->pick_clip_stack, 0);
++  priv->pick_clip_stack_top = -1;
++  priv->cached_pick_mode = CLUTTER_PICK_NONE;
++}
++
++void
++clutter_stage_log_pick (ClutterStage       *stage,
++                        const ClutterPoint *vertices,
++                        ClutterActor       *actor)
++{
++  ClutterStagePrivate *priv;
++  PickRecord rec;
++
++  g_return_if_fail (CLUTTER_IS_STAGE (stage));
++  g_return_if_fail (actor != NULL);
++
++  priv = stage->priv;
++
++  g_assert (!priv->pick_stack_frozen);
++
++  memcpy (rec.vertex, vertices, 4 * sizeof (ClutterPoint));
++  rec.actor = actor;
++  rec.clip_stack_top = priv->pick_clip_stack_top;
++
++  g_array_append_val (priv->pick_stack, rec);
++}
++
++void
++clutter_stage_push_pick_clip (ClutterStage       *stage,
++                              const ClutterPoint *vertices)
++{
++  ClutterStagePrivate *priv;
++  PickClipRecord clip;
++
++  g_return_if_fail (CLUTTER_IS_STAGE (stage));
++
++  priv = stage->priv;
++
++  g_assert (!priv->pick_stack_frozen);
++
++  clip.prev = priv->pick_clip_stack_top;
++  memcpy (clip.vertex, vertices, 4 * sizeof (ClutterPoint));
++
++  g_array_append_val (priv->pick_clip_stack, clip);
++  priv->pick_clip_stack_top = priv->pick_clip_stack->len - 1;
++}
++
++void
++clutter_stage_pop_pick_clip (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv;
++  const PickClipRecord *top;
++
++  g_return_if_fail (CLUTTER_IS_STAGE (stage));
++
++  priv = stage->priv;
++
++  g_assert (!priv->pick_stack_frozen);
++  g_assert (priv->pick_clip_stack_top >= 0);
++
++  /* Individual elements of pick_clip_stack are not freed. This is so they
++   * can be shared as part of a tree of different stacks used by different
++   * actors in the pick_stack. The whole pick_clip_stack does however get
++   * freed later in _clutter_stage_clear_pick_stack.
++   */
++
++  top = &g_array_index (priv->pick_clip_stack,
++                        PickClipRecord,
++                        priv->pick_clip_stack_top);
++
++  priv->pick_clip_stack_top = top->prev;
++}
++
++static gboolean
++is_quadrilateral_axis_aligned_rectangle (const ClutterPoint *vertices)
++{
++  int i;
++
++  for (i = 0; i < 4; i++)
++    {
++      if (!G_APPROX_VALUE (vertices[i].x,
++                           vertices[(i + 1) % 4].x,
++                           FLT_EPSILON) &&
++          !G_APPROX_VALUE (vertices[i].y,
++                           vertices[(i + 1) % 4].y,
++                           FLT_EPSILON))
++        return FALSE;
++    }
++  return TRUE;
++}
++
++static gboolean
++is_inside_axis_aligned_rectangle (const ClutterPoint *point,
++                                  const ClutterPoint *vertices)
++{
++  float min_x = FLT_MAX;
++  float max_x = FLT_MIN;
++  float min_y = FLT_MAX;
++  float max_y = FLT_MIN;
++  int i;
++
++  for (i = 0; i < 3; i++)
++    {
++      min_x = MIN (min_x, vertices[i].x);
++      min_y = MIN (min_y, vertices[i].y);
++      max_x = MAX (max_x, vertices[i].x);
++      max_y = MAX (max_y, vertices[i].y);
++    }
++
++  return (point->x >= min_x &&
++          point->y >= min_y &&
++          point->x < max_x &&
++          point->y < max_y);
++}
++
++static gboolean
++is_inside_input_region (const ClutterPoint *point,
++                        const ClutterPoint *vertices)
++{
++
++  if (is_quadrilateral_axis_aligned_rectangle (vertices))
++    return is_inside_axis_aligned_rectangle (point, vertices);
++  else
++    return clutter_point_inside_quadrilateral (point, vertices);
++}
++
++static gboolean
++pick_record_contains_pixel (ClutterStage     *stage,
++                            const PickRecord *rec,
++                            int               x,
++                            int               y)
++{
++  const ClutterPoint point = CLUTTER_POINT_INIT (x, y);
++  ClutterStagePrivate *priv;
++  int clip_index;
++
++  if (!is_inside_input_region (&point, rec->vertex))
++      return FALSE;
++
++  priv = stage->priv;
++  clip_index = rec->clip_stack_top;
++  while (clip_index >= 0)
++    {
++      const PickClipRecord *clip = &g_array_index (priv->pick_clip_stack,
++                                                   PickClipRecord,
++                                                   clip_index);
++
++      if (!is_inside_input_region (&point, clip->vertex))
++        return FALSE;
++
++      clip_index = clip->prev;
++    }
++
++  return TRUE;
++}
++
+ static inline void
+ queue_full_redraw (ClutterStage *stage)
+ {
+@@ -636,6 +863,12 @@ clutter_stage_do_paint_view (ClutterStage                *stage,
+   float viewport[4];
+   cairo_rectangle_int_t geom;
+ 
++  /* Any mode of painting/picking invalidates the pick cache, unless we're
++   * in the middle of building it. So we reset the cached flag but don't
++   * completely clear the pick stack.
++   */
++  priv->cached_pick_mode = CLUTTER_PICK_NONE;
++
+   _clutter_stage_window_get_geometry (priv->impl, &geom);
+ 
+   viewport[0] = priv->viewport[0];
+@@ -1414,40 +1647,6 @@ clutter_stage_get_redraw_clip_bounds (ClutterStage          *stage,
+     }
+ }
+ 
+-static void
+-read_pixels_to_file (CoglFramebuffer *fb,
+-                     char            *filename_stem,
+-                     int              x,
+-                     int              y,
+-                     int              width,
+-                     int              height)
+-{
+-  guint8 *data;
+-  cairo_surface_t *surface;
+-  static int read_count = 0;
+-  char *filename = g_strdup_printf ("%s-%05d.png",
+-                                    filename_stem,
+-                                    read_count);
+-
+-  data = g_malloc (4 * width * height);
+-  cogl_framebuffer_read_pixels (fb,
+-                                x, y, width, height,
+-                                CLUTTER_CAIRO_FORMAT_ARGB32,
+-                                data);
+-
+-  surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24,
+-                                                 width, height,
+-                                                 width * 4);
+-
+-  cairo_surface_write_to_png (surface, filename);
+-  cairo_surface_destroy (surface);
+-
+-  g_free (data);
+-  g_free (filename);
+-
+-  read_count++;
+-}
+-
+ static ClutterActor *
+ _clutter_stage_do_pick_on_view (ClutterStage     *stage,
+                                 gint              x,
+@@ -1455,140 +1654,42 @@ _clutter_stage_do_pick_on_view (ClutterStage     *stage,
+                                 ClutterPickMode   mode,
+                                 ClutterStageView *view)
+ {
+-  ClutterActor *actor = CLUTTER_ACTOR (stage);
++  ClutterMainContext *context = _clutter_context_get_default ();
+   ClutterStagePrivate *priv = stage->priv;
+   CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
+-  cairo_rectangle_int_t view_layout;
+-  ClutterMainContext *context;
+-  guchar pixel[4] = { 0xff, 0xff, 0xff, 0xff };
+-  CoglColor stage_pick_id;
+-  gboolean dither_enabled_save;
+-  ClutterActor *retval;
+-  gint dirty_x;
+-  gint dirty_y;
+-  gint read_x;
+-  gint read_y;
+-  float fb_width, fb_height;
+-  float fb_scale;
+-  float viewport_offset_x;
+-  float viewport_offset_y;
+-
+-  priv = stage->priv;
+-
+-  context = _clutter_context_get_default ();
+-  fb_scale = clutter_stage_view_get_scale (view);
+-  clutter_stage_view_get_layout (view, &view_layout);
+-
+-  fb_width = view_layout.width * fb_scale;
+-  fb_height = view_layout.height * fb_scale;
+-  cogl_push_framebuffer (fb);
+-
+-  /* needed for when a context switch happens */
+-  _clutter_stage_maybe_setup_viewport (stage, view);
+-
+-  /* FIXME: For some reason leaving the cogl clip stack empty causes the
+-   * picking to not work at all, so setting it the whole framebuffer content
+-   * for now. */
+-  cogl_framebuffer_push_scissor_clip (fb, 0, 0,
+-                                      view_layout.width * fb_scale,
+-                                      view_layout.height * fb_scale);
+-
+-  _clutter_stage_window_get_dirty_pixel (priv->impl, view, &dirty_x, &dirty_y);
++  int i;
+ 
+-  if (G_LIKELY (!(clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS)))
+-    {
+-      CLUTTER_NOTE (PICK, "Pushing pick scissor clip x: %d, y: %d, 1x1",
+-                    (int) (dirty_x * fb_scale),
+-                    (int) (dirty_y * fb_scale));
+-      cogl_framebuffer_push_scissor_clip (fb, dirty_x * fb_scale, dirty_y * fb_scale, 1, 1);
+-    }
++  g_assert (context->pick_mode == CLUTTER_PICK_NONE);
+ 
+-  viewport_offset_x = x * fb_scale - dirty_x * fb_scale;
+-  viewport_offset_y = y * fb_scale - dirty_y * fb_scale;
+-  CLUTTER_NOTE (PICK, "Setting viewport to %f, %f, %f, %f",
+-                priv->viewport[0] * fb_scale - viewport_offset_x,
+-                priv->viewport[1] * fb_scale - viewport_offset_y,
+-                priv->viewport[2] * fb_scale,
+-                priv->viewport[3] * fb_scale);
+-  cogl_framebuffer_set_viewport (fb,
+-                                 priv->viewport[0] * fb_scale - viewport_offset_x,
+-                                 priv->viewport[1] * fb_scale - viewport_offset_y,
+-                                 priv->viewport[2] * fb_scale,
+-                                 priv->viewport[3] * fb_scale);
+-
+-  read_x = dirty_x * fb_scale;
+-  read_y = dirty_y * fb_scale;
+-
+-  CLUTTER_NOTE (PICK, "Performing pick at %i,%i on view %dx%d+%d+%d s: %f",
+-                x, y,
+-                view_layout.width, view_layout.height,
+-                view_layout.x, view_layout.y, fb_scale);
+-
+-  cogl_color_init_from_4ub (&stage_pick_id, 255, 255, 255, 255);
+-  cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR | COGL_BUFFER_BIT_DEPTH, &stage_pick_id);
+-
+-  /* Disable dithering (if any) when doing the painting in pick mode */
+-  dither_enabled_save = cogl_framebuffer_get_dither_enabled (fb);
+-  cogl_framebuffer_set_dither_enabled (fb, FALSE);
+-
+-  /* Render the entire scence in pick mode - just single colored silhouette's
+-   * are drawn offscreen (as we never swap buffers)
+-  */
+-  context->pick_mode = mode;
+-
+-  clutter_stage_do_paint_view (stage, view, NULL);
+-  context->pick_mode = CLUTTER_PICK_NONE;
+-
+-  /* Read the color of the screen co-ords pixel. RGBA_8888_PRE is used
+-     even though we don't care about the alpha component because under
+-     GLES this is the only format that is guaranteed to work so Cogl
+-     will end up having to do a conversion if any other format is
+-     used. The format is requested as pre-multiplied because Cogl
+-     assumes that all pixels in the framebuffer are premultiplied so
+-     it avoids a conversion. */
+-  cogl_framebuffer_read_pixels (fb,
+-                                read_x, read_y, 1, 1,
+-                                COGL_PIXEL_FORMAT_RGBA_8888_PRE,
+-                                pixel);
+-
+-  if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))
++  if (mode != priv->cached_pick_mode)
+     {
+-      char *file_name =
+-        g_strdup_printf ("pick-buffer-%s-view-x-%d",
+-                         _clutter_actor_get_debug_name (actor),
+-                         view_layout.x);
++      _clutter_stage_clear_pick_stack (stage);
+ 
+-      read_pixels_to_file (fb, file_name, 0, 0, fb_width, fb_height);
++      cogl_push_framebuffer (fb);
+ 
+-      g_free (file_name);
+-    }
+-
+-  /* Restore whether GL_DITHER was enabled */
+-  cogl_framebuffer_set_dither_enabled (fb, dither_enabled_save);
+-
+-  if (G_LIKELY (!(clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS)))
+-    cogl_framebuffer_pop_clip (fb);
++      context->pick_mode = mode;
++      clutter_stage_do_paint_view (stage, view, NULL);
++      context->pick_mode = CLUTTER_PICK_NONE;
++      priv->cached_pick_mode = mode;
+ 
+-  cogl_framebuffer_pop_clip (fb);
++      cogl_pop_framebuffer ();
+ 
+-  _clutter_stage_dirty_viewport (stage);
++      add_pick_stack_weak_refs (stage);
++    }
+ 
+-  if (pixel[0] == 0xff && pixel[1] == 0xff && pixel[2] == 0xff)
+-    retval = actor;
+-  else
++  /* Search all "painted" pickable actors from front to back. A linear search
++   * is required, and also performs fine since there is typically only
++   * on the order of dozens of actors in the list (on screen) at a time.
++   */
++  for (i = priv->pick_stack->len - 1; i >= 0; i--)
+     {
+-      guint32 id_ = _clutter_pixel_to_id (pixel);
++      const PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
+ 
+-      retval = _clutter_stage_get_actor_by_pick_id (stage, id_);
+-      CLUTTER_NOTE (PICK, "Picking actor %s with id %u (pixel: 0x%x%x%x%x",
+-                    G_OBJECT_TYPE_NAME (retval),
+-                    id_,
+-                    pixel[0], pixel[1], pixel[2], pixel[3]);
++      if (rec->actor && pick_record_contains_pixel (stage, rec, x, y))
++        return rec->actor;
+     }
+ 
+-  cogl_pop_framebuffer ();
+-
+-  return retval;
++  return CLUTTER_ACTOR (stage);
+ }
+ 
+ static ClutterStageView *
+@@ -1901,7 +2002,9 @@ clutter_stage_finalize (GObject *object)
+ 
+   g_array_free (priv->paint_volume_stack, TRUE);
+ 
+-  _clutter_id_pool_free (priv->pick_id_pool);
++  _clutter_stage_clear_pick_stack (stage);
++  g_array_free (priv->pick_clip_stack, TRUE);
++  g_array_free (priv->pick_stack, TRUE);
+ 
+   if (priv->fps_timer != NULL)
+     g_timer_destroy (priv->fps_timer);
+@@ -2435,7 +2538,10 @@ clutter_stage_init (ClutterStage *self)
+   priv->paint_volume_stack =
+     g_array_new (FALSE, FALSE, sizeof (ClutterPaintVolume));
+ 
+-  priv->pick_id_pool = _clutter_id_pool_new (256);
++  priv->pick_stack = g_array_new (FALSE, FALSE, sizeof (PickRecord));
++  priv->pick_clip_stack = g_array_new (FALSE, FALSE, sizeof (PickClipRecord));
++  priv->pick_clip_stack_top = -1;
++  priv->cached_pick_mode = CLUTTER_PICK_NONE;
+ }
+ 
+ /**
+@@ -4253,6 +4359,12 @@ _clutter_stage_queue_actor_redraw (ClutterStage                 *stage,
+   CLUTTER_NOTE (CLIPPING, "stage_queue_actor_redraw (actor=%s, clip=%p): ",
+                 _clutter_actor_get_debug_name (actor), clip);
+ 
++  /* Queuing a redraw or clip change invalidates the pick cache, unless we're
++   * in the middle of building it. So we reset the cached flag but don't
++   * completely clear the pick stack...
++   */
++  priv->cached_pick_mode = CLUTTER_PICK_NONE;
++
+   if (!priv->redraw_pending)
+     {
+       ClutterMasterClock *master_clock;
+@@ -4513,39 +4625,6 @@ _clutter_stage_get_active_framebuffer (ClutterStage *stage)
+   return stage->priv->active_framebuffer;
+ }
+ 
+-gint32
+-_clutter_stage_acquire_pick_id (ClutterStage *stage,
+-                                ClutterActor *actor)
+-{
+-  ClutterStagePrivate *priv = stage->priv;
+-
+-  g_assert (priv->pick_id_pool != NULL);
+-
+-  return _clutter_id_pool_add (priv->pick_id_pool, actor);
+-}
+-
+-void
+-_clutter_stage_release_pick_id (ClutterStage *stage,
+-                                gint32        pick_id)
+-{
+-  ClutterStagePrivate *priv = stage->priv;
+-
+-  g_assert (priv->pick_id_pool != NULL);
+-
+-  _clutter_id_pool_remove (priv->pick_id_pool, pick_id);
+-}
+-
+-ClutterActor *
+-_clutter_stage_get_actor_by_pick_id (ClutterStage *stage,
+-                                     gint32        pick_id)
+-{
+-  ClutterStagePrivate *priv = stage->priv;
+-
+-  g_assert (priv->pick_id_pool != NULL);
+-
+-  return _clutter_id_pool_lookup (priv->pick_id_pool, pick_id);
+-}
+-
+ void
+ _clutter_stage_add_pointer_drag_actor (ClutterStage       *stage,
+                                        ClutterInputDevice *device,
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 3f1f609c4e..effed79759 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -926,57 +926,6 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
+   stage_cogl->frame_count++;
+ }
+ 
+-static void
+-clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
+-                                    ClutterStageView   *view,
+-                                    int                *x,
+-                                    int                *y)
+-{
+-  CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
+-  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
+-  ClutterStageViewCoglPrivate *view_priv =
+-    clutter_stage_view_cogl_get_instance_private (view_cogl);
+-  gboolean has_buffer_age =
+-    cogl_is_onscreen (onscreen) &&
+-    is_buffer_age_enabled ();
+-  float fb_scale;
+-  gboolean scale_is_fractional;
+-
+-  fb_scale = clutter_stage_view_get_scale (view);
+-  if (fb_scale != floorf (fb_scale))
+-    scale_is_fractional = TRUE;
+-  else
+-    scale_is_fractional = FALSE;
+-
+-  /*
+-   * Buffer damage is tracked in the framebuffer coordinate space
+-   * using the damage history. When fractional scaling is used, a
+-   * coordinate on the stage might not correspond to the exact position of any
+-   * physical pixel, which causes issues when painting using the pick mode.
+-   *
+-   * For now, always use the (0, 0) pixel for picking when using fractional
+-   * framebuffer scaling.
+-   */
+-  if (!has_buffer_age ||
+-      scale_is_fractional ||
+-      !clutter_damage_history_is_age_valid (view_priv->damage_history, 0))
+-    {
+-      *x = 0;
+-      *y = 0;
+-    }
+-  else
+-    {
+-      cairo_rectangle_int_t view_layout;
+-      const cairo_rectangle_int_t *fb_damage;
+-
+-      clutter_stage_view_get_layout (view, &view_layout);
+-
+-      fb_damage = clutter_damage_history_lookup (view_priv->damage_history, 0);
+-      *x = fb_damage->x / fb_scale;
+-      *y = fb_damage->y / fb_scale;
+-    }
+-}
+-
+ static void
+ clutter_stage_window_iface_init (ClutterStageWindowInterface *iface)
+ {
+@@ -994,7 +943,6 @@ clutter_stage_window_iface_init (ClutterStageWindowInterface *iface)
+   iface->ignoring_redraw_clips = clutter_stage_cogl_ignoring_redraw_clips;
+   iface->get_redraw_clip_bounds = clutter_stage_cogl_get_redraw_clip_bounds;
+   iface->redraw = clutter_stage_cogl_redraw;
+-  iface->get_dirty_pixel = clutter_stage_cogl_get_dirty_pixel;
+ }
+ 
+ static void
+diff --git a/clutter/clutter/deprecated/clutter-texture.c b/clutter/clutter/deprecated/clutter-texture.c
+index bea239f454..2c677b8a44 100644
+--- a/clutter/clutter/deprecated/clutter-texture.c
++++ b/clutter/clutter/deprecated/clutter-texture.c
+@@ -572,83 +572,6 @@ gen_texcoords_and_draw_cogl_rectangle (ClutterActor    *self,
+                                             0, 0, t_w, t_h);
+ }
+ 
+-static CoglPipeline *
+-create_pick_pipeline (ClutterActor *self)
+-{
+-  ClutterTexture *texture = CLUTTER_TEXTURE (self);
+-  ClutterTexturePrivate *priv = texture->priv;
+-  CoglPipeline *pick_pipeline = cogl_pipeline_copy (texture_template_pipeline);
+-  GError *error = NULL;
+-
+-  if (!cogl_pipeline_set_layer_combine (pick_pipeline, 0,
+-                                        "RGBA = "
+-                                        "  MODULATE (CONSTANT, TEXTURE[A])",
+-                                        &error))
+-    {
+-      if (!priv->seen_create_pick_pipeline_warning)
+-        g_warning ("Error setting up texture combine for shaped "
+-                   "texture picking: %s", error->message);
+-      priv->seen_create_pick_pipeline_warning = TRUE;
+-      g_error_free (error);
+-      cogl_object_unref (pick_pipeline);
+-      return NULL;
+-    }
+-
+-  cogl_pipeline_set_blend (pick_pipeline,
+-                           "RGBA = ADD (SRC_COLOR[RGBA], 0)",
+-                           NULL);
+-
+-  cogl_pipeline_set_alpha_test_function (pick_pipeline,
+-                                         COGL_PIPELINE_ALPHA_FUNC_EQUAL,
+-                                         1.0);
+-
+-  return pick_pipeline;
+-}
+-
+-static void
+-clutter_texture_pick (ClutterActor       *self,
+-                      const ClutterColor *color)
+-{
+-  ClutterTexture *texture = CLUTTER_TEXTURE (self);
+-  ClutterTexturePrivate *priv = texture->priv;
+-  CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer ();
+-
+-  if (!clutter_actor_should_pick_paint (self))
+-    return;
+-
+-  if (G_LIKELY (priv->pick_with_alpha_supported) && priv->pick_with_alpha)
+-    {
+-      CoglColor pick_color;
+-
+-      if (priv->pick_pipeline == NULL)
+-        priv->pick_pipeline = create_pick_pipeline (self);
+-
+-      if (priv->pick_pipeline == NULL)
+-        {
+-          priv->pick_with_alpha_supported = FALSE;
+-          CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->pick (self,
+-                                                                    color);
+-          return;
+-        }
+-
+-      if (priv->fbo_handle != NULL)
+-        update_fbo (self);
+-
+-      cogl_color_init_from_4ub (&pick_color,
+-                                color->red,
+-                                color->green,
+-                                color->blue,
+-                                0xff);
+-      cogl_pipeline_set_layer_combine_constant (priv->pick_pipeline,
+-                                                0, &pick_color);
+-      cogl_pipeline_set_layer_texture (priv->pick_pipeline, 0,
+-                                       clutter_texture_get_cogl_texture (texture));
+-      gen_texcoords_and_draw_cogl_rectangle (self, priv->pick_pipeline, framebuffer);
+-    }
+-  else
+-    CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->pick (self, color);
+-}
+-
+ static void
+ clutter_texture_paint (ClutterActor *self)
+ {
+@@ -767,12 +690,6 @@ clutter_texture_dispose (GObject *object)
+       priv->pipeline = NULL;
+     }
+ 
+-  if (priv->pick_pipeline != NULL)
+-    {
+-      cogl_object_unref (priv->pick_pipeline);
+-      priv->pick_pipeline = NULL;
+-    }
+-
+   G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object);
+ }
+ 
+@@ -944,7 +861,6 @@ clutter_texture_class_init (ClutterTextureClass *klass)
+   GParamSpec *pspec;
+ 
+   actor_class->paint            = clutter_texture_paint;
+-  actor_class->pick             = clutter_texture_pick;
+   actor_class->get_paint_volume = clutter_texture_get_paint_volume;
+   actor_class->realize          = clutter_texture_realize;
+   actor_class->unrealize        = clutter_texture_unrealize;
+@@ -1263,11 +1179,9 @@ clutter_texture_init (ClutterTexture *self)
+   priv->repeat_y          = FALSE;
+   priv->sync_actor_size   = TRUE;
+   priv->fbo_handle        = NULL;
+-  priv->pick_pipeline     = NULL;
+   priv->keep_aspect_ratio = FALSE;
+   priv->pick_with_alpha   = FALSE;
+   priv->pick_with_alpha_supported = TRUE;
+-  priv->seen_create_pick_pipeline_warning = FALSE;
+ 
+   if (G_UNLIKELY (texture_template_pipeline == NULL))
+     {
+@@ -3052,13 +2966,8 @@ clutter_texture_set_pick_with_alpha (ClutterTexture *texture,
+   if (priv->pick_with_alpha == pick_with_alpha)
+     return;
+ 
+-  if (!pick_with_alpha && priv->pick_pipeline != NULL)
+-    {
+-      cogl_object_unref (priv->pick_pipeline);
+-      priv->pick_pipeline = NULL;
+-    }
++  g_assert (!pick_with_alpha);  /* No longer supported */
+ 
+-  /* NB: the pick pipeline is created lazily when we first pick */
+   priv->pick_with_alpha = pick_with_alpha;
+ 
+   /* NB: actors are expected to call clutter_actor_queue_redraw when
+diff --git a/clutter/tests/conform/actor-pick.c b/clutter/tests/conform/actor-pick.c
+index 969b4920ac..2bf5954c73 100644
+--- a/clutter/tests/conform/actor-pick.c
++++ b/clutter/tests/conform/actor-pick.c
+@@ -5,7 +5,6 @@
+ #define STAGE_HEIGHT 480
+ #define ACTORS_X 12
+ #define ACTORS_Y 16
+-#define SHIFT_STEP STAGE_WIDTH / ACTORS_X
+ 
+ typedef struct _State State;
+ 
+@@ -20,84 +19,11 @@ struct _State
+   gboolean pass;
+ };
+ 
+-struct _ShiftEffect
+-{
+-  ClutterShaderEffect parent_instance;
+-};
+-
+-struct _ShiftEffectClass
+-{
+-  ClutterShaderEffectClass parent_class;
+-};
+-
+-typedef struct _ShiftEffect       ShiftEffect;
+-typedef struct _ShiftEffectClass  ShiftEffectClass;
+-
+-#define TYPE_SHIFT_EFFECT        (shift_effect_get_type ())
+-
+-GType shift_effect_get_type (void);
+-
+-G_DEFINE_TYPE (ShiftEffect,
+-               shift_effect,
+-               CLUTTER_TYPE_SHADER_EFFECT);
+-
+-static void
+-shader_paint (ClutterEffect           *effect,
+-              ClutterEffectPaintFlags  flags)
+-{
+-  ClutterShaderEffect *shader = CLUTTER_SHADER_EFFECT (effect);
+-  float tex_width;
+-  ClutterActor *actor =
+-    clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
+-
+-  if (g_test_verbose ())
+-    g_debug ("shader_paint");
+-
+-  clutter_shader_effect_set_shader_source (shader,
+-    "uniform sampler2D tex;\n"
+-    "uniform float step;\n"
+-    "void main (void)\n"
+-    "{\n"
+-    "  cogl_color_out = texture2D(tex, vec2 (cogl_tex_coord_in[0].s + step,\n"
+-    "                                        cogl_tex_coord_in[0].t));\n"
+-    "}\n");
+-
+-  tex_width = clutter_actor_get_width (actor);
+-
+-  clutter_shader_effect_set_uniform (shader, "tex", G_TYPE_INT, 1, 0);
+-  clutter_shader_effect_set_uniform (shader, "step", G_TYPE_FLOAT, 1,
+-                                     SHIFT_STEP / tex_width);
+-
+-  CLUTTER_EFFECT_CLASS (shift_effect_parent_class)->paint (effect, flags);
+-}
+-
+-static void
+-shader_pick (ClutterEffect           *effect,
+-             ClutterEffectPaintFlags  flags)
+-{
+-  shader_paint (effect, flags);
+-}
+-
+-static void
+-shift_effect_class_init (ShiftEffectClass *klass)
+-{
+-  ClutterEffectClass *shader_class = CLUTTER_EFFECT_CLASS (klass);
+-
+-  shader_class->paint = shader_paint;
+-  shader_class->pick = shader_pick;
+-}
+-
+-static void
+-shift_effect_init (ShiftEffect *self)
+-{
+-}
+-
+ static const char *test_passes[] = {
+   "No covering actor",
+   "Invisible covering actor",
+   "Clipped covering actor",
+   "Blur effect",
+-  "Shift effect",
+ };
+ 
+ static gboolean
+@@ -165,30 +91,10 @@ on_timeout (gpointer data)
+           if (g_test_verbose ())
+             g_print ("With blur effect:\n");
+         }
+-      else if (test_num == 4)
+-        {
+-          if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
+-            continue;
+-
+-          clutter_actor_hide (over_actor);
+-          clutter_actor_remove_effect_by_name (CLUTTER_ACTOR (state->stage),
+-                                               "blur");
+-
+-          clutter_actor_add_effect_with_name (CLUTTER_ACTOR (state->stage),
+-                                              "shift",
+-                                              g_object_new (TYPE_SHIFT_EFFECT,
+-                                                            NULL));
+-
+-          if (g_test_verbose ())
+-            g_print ("With shift effect:\n");
+-        }
+ 
+       for (y = 0; y < ACTORS_Y; y++)
+         {
+-          if (test_num == 4)
+-            x = 1;
+-          else
+-            x = 0;
++          x = 0;
+ 
+           for (; x < ACTORS_X; x++)
+             {
+@@ -198,9 +104,6 @@ on_timeout (gpointer data)
+ 
+               pick_x = x * state->actor_width + state->actor_width / 2;
+ 
+-              if (test_num == 4)
+-                pick_x -= SHIFT_STEP;
+-
+               actor =
+                 clutter_stage_get_actor_at_pos (CLUTTER_STAGE (state->stage),
+                                                 CLUTTER_PICK_ALL,
+diff --git a/clutter/tests/conform/meson.build b/clutter/tests/conform/meson.build
+index a9f2d7e20c..fffc9014c4 100644
+--- a/clutter/tests/conform/meson.build
++++ b/clutter/tests/conform/meson.build
+@@ -42,7 +42,6 @@ clutter_conform_tests_deprecated_tests = [
+   'behaviours',
+   'group',
+   'rectangle',
+-  'texture',
+ ]
+ 
+ clutter_conform_tests = []
+diff --git a/clutter/tests/conform/texture.c b/clutter/tests/conform/texture.c
+deleted file mode 100644
+index 392fd5c47e..0000000000
+--- a/clutter/tests/conform/texture.c
++++ /dev/null
+@@ -1,84 +0,0 @@
+-#define CLUTTER_DISABLE_DEPRECATION_WARNINGS
+-#include <clutter/clutter.h>
+-#include <string.h>
+-
+-static CoglHandle
+-make_texture (void)
+-{
+-  guint32 *data = g_malloc (100 * 100 * 4);
+-  int x;
+-  int y;
+-
+-  for (y = 0; y < 100; y ++)
+-    for (x = 0; x < 100; x++)
+-      {
+-        if (x < 50 && y < 50)
+-          data[y * 100 + x] = 0xff00ff00;
+-        else
+-          data[y * 100 + x] = 0xff00ffff;
+-      }
+-  return cogl_texture_new_from_data (100,
+-                                     100,
+-                                     COGL_TEXTURE_NONE,
+-                                     COGL_PIXEL_FORMAT_ARGB_8888,
+-                                     COGL_PIXEL_FORMAT_ARGB_8888,
+-                                     400,
+-                                     (guchar *)data);
+-}
+-
+-static void
+-texture_pick_with_alpha (void)
+-{
+-  ClutterTexture *tex = CLUTTER_TEXTURE (clutter_texture_new ());
+-  ClutterStage *stage = CLUTTER_STAGE (clutter_test_get_stage ());
+-  ClutterActor *actor;
+-
+-  clutter_texture_set_cogl_texture (tex, make_texture ());
+-
+-  clutter_actor_add_child (CLUTTER_ACTOR (stage), CLUTTER_ACTOR (tex));
+-
+-  clutter_actor_show (CLUTTER_ACTOR (stage));
+-
+-  if (g_test_verbose ())
+-    {
+-      g_print ("\nstage = %p\n", stage);
+-      g_print ("texture = %p\n\n", tex);
+-    }
+-
+-  clutter_texture_set_pick_with_alpha (tex, TRUE);
+-  if (g_test_verbose ())
+-    g_print ("Testing with pick-with-alpha enabled:\n");
+-
+-  /* This should fall through and hit the stage: */
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 10);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (10, 10) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (stage));
+-
+-  /* The rest should hit the texture */
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 90, 10);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (90, 10) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (tex));
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 90, 90);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (90, 90) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (tex));
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 90);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (10, 90) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (tex));
+-
+-  clutter_texture_set_pick_with_alpha (tex, FALSE);
+-  if (g_test_verbose ())
+-    g_print ("Testing with pick-with-alpha disabled:\n");
+-
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 10);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (10, 10) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (tex));
+-}
+-
+-CLUTTER_TEST_SUITE (
+-  CLUTTER_TEST_UNIT ("/texture/pick-with-alpha", texture_pick_with_alpha)
+-)
+diff --git a/src/compositor/meta-surface-actor.c b/src/compositor/meta-surface-actor.c
+index ca4ca19a99..814199145a 100644
+--- a/src/compositor/meta-surface-actor.c
++++ b/src/compositor/meta-surface-actor.c
+@@ -70,38 +70,23 @@ meta_surface_actor_pick (ClutterActor       *actor,
+   else
+     {
+       int n_rects;
+-      float *rectangles;
+       int i;
+-      CoglPipeline *pipeline;
+-      CoglContext *ctx;
+-      CoglFramebuffer *fb;
+-      CoglColor cogl_color;
+ 
+       n_rects = cairo_region_num_rectangles (priv->input_region);
+-      rectangles = g_alloca (sizeof (float) * 4 * n_rects);
+ 
+       for (i = 0; i < n_rects; i++)
+         {
+           cairo_rectangle_int_t rect;
+-          int pos = i * 4;
++          ClutterActorBox box;
+ 
+           cairo_region_get_rectangle (priv->input_region, i, &rect);
+ 
+-          rectangles[pos + 0] = rect.x;
+-          rectangles[pos + 1] = rect.y;
+-          rectangles[pos + 2] = rect.x + rect.width;
+-          rectangles[pos + 3] = rect.y + rect.height;
++          box.x1 = rect.x;
++          box.y1 = rect.y;
++          box.x2 = rect.x + rect.width;
++          box.y2 = rect.y + rect.height;
++          clutter_actor_pick_box (actor, &box);
+         }
+-
+-      ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+-      fb = cogl_get_draw_framebuffer ();
+-
+-      cogl_color_init_from_4ub (&cogl_color, color->red, color->green, color->blue, color->alpha);
+-
+-      pipeline = cogl_pipeline_new (ctx);
+-      cogl_pipeline_set_color (pipeline, &cogl_color);
+-      cogl_framebuffer_draw_rectangles (fb, pipeline, rectangles, n_rects);
+-      cogl_object_unref (pipeline);
+     }
+ 
+   clutter_actor_iter_init (&iter, actor);
+-- 
+2.29.2
+
+
+From 254e93de8d60393ca94fa430c0acc6f6a7b9516e Mon Sep 17 00:00:00 2001
+From: Iain Lane <laney@debian.org>
+Date: Mon, 9 Sep 2019 10:17:22 +0100
+Subject: [PATCH 3/4] build: Compile with `-ffloat-store` on x86 (32 bit)
+
+GCC's manpage says that this flag does the following:
+
+  Do not store floating-point variables in registers, and inhibit other
+  options that might change whether a floating-point value is taken from
+  a register or memory.
+
+  This option prevents undesirable excess precision on machines such as
+  the 68000 where the floating registers (of the 68881) keep more
+  precision than a "double" is supposed to have.  Similarly for the x86
+  architecture.  For most programs, the excess precision does only good,
+  but a few programs rely on the precise definition of IEEE floating
+  point.
+
+We rely on this behaviour in our fork of clutter. When performing
+floating point computations on x86, we are getting the wrong results
+because of this architecture's use of the CPU's extended (x87, non-IEEE
+confirming) precision by default. If we enable `-ffloat-store` here,
+then we'll get the same results everywhere by storing into variables
+instead of registers. This does not remove the need to be correct when
+handling floats, but it does mean we don't need to be more correct than
+the IEEE spec requires.
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/785
+---
+ meson.build | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/meson.build b/meson.build
+index 8ef592bc58..e1edb78ba7 100644
+--- a/meson.build
++++ b/meson.build
+@@ -267,6 +267,9 @@ foreach function : required_functions
+   endif
+ endforeach
+ 
++if host_machine.cpu_family() == 'x86'
++  add_project_arguments('-ffloat-store', language: 'c')
++endif
+ add_project_arguments('-D_GNU_SOURCE', language: 'c')
+ 
+ all_warnings = [
+-- 
+2.29.2
+
+
+From 2d42caef14772984344e62ce40957d3b40e1f2b6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
+Date: Thu, 19 Sep 2019 11:27:50 +0200
+Subject: [PATCH 4/4] stage: Compute view perspective when parameters changed
+
+Clutter stage used to compute the initial projection using a fixed z
+translation which wasn't matching the one we computed in
+calculate_z_translation().
+This caused to have a wrong initial projection on startup which was then
+correctly recomputed only at the first paint.
+
+However, since this calculation doesn't depend on view, but only on viewport
+size, perspective's fovy and z_near we can safely do this at startup and
+only when any of those parameters change.
+
+Then we can move the computation out _clutter_stage_maybe_setup_viewport()
+since the cogl framebuffer viewport sizes aren't affecting this.
+
+Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1639
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/803
+---
+ clutter/clutter/clutter-stage.c | 104 +++++++++++++++-----------------
+ 1 file changed, 47 insertions(+), 57 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index 7d88d5752f..0cfa87486e 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -234,6 +234,7 @@ static void capture_view_into (ClutterStage          *stage,
+                                cairo_rectangle_int_t *rect,
+                                uint8_t               *data,
+                                int                    stride);
++static void clutter_stage_update_view_perspective (ClutterStage *stage);
+ 
+ static void clutter_container_iface_init (ClutterContainerIface *iface);
+ 
+@@ -2492,29 +2493,6 @@ clutter_stage_init (ClutterStage *self)
+   clutter_actor_set_background_color (CLUTTER_ACTOR (self),
+                                       &default_stage_color);
+ 
+-  priv->perspective.fovy   = 60.0; /* 60 Degrees */
+-  priv->perspective.aspect = (float) geom.width / (float) geom.height;
+-  priv->perspective.z_near = 0.1;
+-  priv->perspective.z_far  = 100.0;
+-
+-  cogl_matrix_init_identity (&priv->projection);
+-  cogl_matrix_perspective (&priv->projection,
+-                           priv->perspective.fovy,
+-                           priv->perspective.aspect,
+-                           priv->perspective.z_near,
+-                           priv->perspective.z_far);
+-  cogl_matrix_get_inverse (&priv->projection,
+-                           &priv->inverse_projection);
+-  cogl_matrix_init_identity (&priv->view);
+-  cogl_matrix_view_2d_in_perspective (&priv->view,
+-                                      priv->perspective.fovy,
+-                                      priv->perspective.aspect,
+-                                      priv->perspective.z_near,
+-                                      50, /* distance to 2d plane */
+-                                      geom.width,
+-                                      geom.height);
+-
+-
+   /* FIXME - remove for 2.0 */
+   priv->fog.z_near = 1.0;
+   priv->fog.z_far  = 2.0;
+@@ -2682,6 +2660,7 @@ clutter_stage_set_perspective (ClutterStage       *stage,
+   priv->has_custom_perspective = TRUE;
+ 
+   clutter_stage_set_perspective_internal (stage, perspective);
++  clutter_stage_update_view_perspective (stage);
+ }
+ 
+ /**
+@@ -2808,6 +2787,7 @@ _clutter_stage_set_viewport (ClutterStage *stage,
+   priv->viewport[2] = width;
+   priv->viewport[3] = height;
+ 
++  clutter_stage_update_view_perspective (stage);
+   _clutter_stage_dirty_viewport (stage);
+ 
+   queue_full_redraw (stage);
+@@ -3788,6 +3768,50 @@ calculate_z_translation (float z_near)
+        + z_near;
+ }
+ 
++static void
++clutter_stage_update_view_perspective (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv = stage->priv;
++  ClutterPerspective perspective;
++  float z_2d;
++
++  perspective = priv->perspective;
++
++  /* Ideally we want to regenerate the perspective matrix whenever
++   * the size changes but if the user has provided a custom matrix
++   * then we don't want to override it */
++  if (!priv->has_custom_perspective)
++    {
++      perspective.fovy = 60.0; /* 60 Degrees */
++      perspective.z_near = 0.1;
++      perspective.aspect = priv->viewport[2] / priv->viewport[3];
++      z_2d = calculate_z_translation (perspective.z_near);
++
++      /* NB: z_2d is only enough room for 85% of the stage_height between
++       * the stage and the z_near plane. For behind the stage plane we
++       * want a more consistent gap of 10 times the stage_height before
++       * hitting the far plane so we calculate that relative to the final
++       * height of the stage plane at the z_2d_distance we got... */
++      perspective.z_far = z_2d +
++        tanf (_DEG_TO_RAD (perspective.fovy / 2.0f)) * z_2d * 20.0f;
++
++      clutter_stage_set_perspective_internal (stage, &perspective);
++    }
++  else
++    {
++      z_2d = calculate_z_translation (perspective.z_near);
++    }
++
++  cogl_matrix_init_identity (&priv->view);
++  cogl_matrix_view_2d_in_perspective (&priv->view,
++                                      perspective.fovy,
++                                      perspective.aspect,
++                                      perspective.z_near,
++                                      z_2d,
++                                      priv->viewport[2],
++                                      priv->viewport[3]);
++}
++
+ void
+ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+                                      ClutterStageView *view)
+@@ -3797,7 +3821,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+   if (clutter_stage_view_is_dirty_viewport (view))
+     {
+       cairo_rectangle_int_t view_layout;
+-      ClutterPerspective perspective;
+       float fb_scale;
+       float viewport_offset_x;
+       float viewport_offset_y;
+@@ -3805,7 +3828,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+       float viewport_y;
+       float viewport_width;
+       float viewport_height;
+-      float z_2d;
+ 
+       CLUTTER_NOTE (PAINT,
+                     "Setting up the viewport { w:%f, h:%f }",
+@@ -3825,38 +3847,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+       clutter_stage_view_set_viewport (view,
+                                        viewport_x, viewport_y,
+                                        viewport_width, viewport_height);
+-
+-      perspective = priv->perspective;
+-
+-      /* Ideally we want to regenerate the perspective matrix whenever
+-       * the size changes but if the user has provided a custom matrix
+-       * then we don't want to override it */
+-      if (!priv->has_custom_perspective)
+-        {
+-          perspective.aspect = priv->viewport[2] / priv->viewport[3];
+-          z_2d = calculate_z_translation (perspective.z_near);
+-
+-          /* NB: z_2d is only enough room for 85% of the stage_height between
+-           * the stage and the z_near plane. For behind the stage plane we
+-           * want a more consistent gap of 10 times the stage_height before
+-           * hitting the far plane so we calculate that relative to the final
+-           * height of the stage plane at the z_2d_distance we got... */
+-          perspective.z_far = z_2d +
+-            tanf (_DEG_TO_RAD (perspective.fovy / 2.0f)) * z_2d * 20.0f;
+-
+-          clutter_stage_set_perspective_internal (stage, &perspective);
+-        }
+-      else
+-        z_2d = calculate_z_translation (perspective.z_near);
+-
+-      cogl_matrix_init_identity (&priv->view);
+-      cogl_matrix_view_2d_in_perspective (&priv->view,
+-                                          perspective.fovy,
+-                                          perspective.aspect,
+-                                          perspective.z_near,
+-                                          z_2d,
+-                                          priv->viewport[2],
+-                                          priv->viewport[3]);
+     }
+ 
+   if (clutter_stage_view_is_dirty_projection (view))
+-- 
+2.29.2
+
diff --git a/SOURCES/wayland-frame-callback-rework.patch b/SOURCES/wayland-frame-callback-rework.patch
new file mode 100644
index 0000000..0a6717f
--- /dev/null
+++ b/SOURCES/wayland-frame-callback-rework.patch
@@ -0,0 +1,1045 @@
+From 4232f2056cd31811d047104d1223f3e5ced4b107 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 16 Apr 2020 19:42:03 +0200
+Subject: [PATCH 1/5] clutter/stage: Make clutter_stage_schedule_update()
+ always schedule
+
+We could call clutter_stage_schedule_update() and it wouldn't actually
+schedule anything, as the master frame clock only tries to reschedule if
+1) there is an active timeline, 2) there are pending relayouts, 3) there
+are pending redraws, or 4) there are pending events. Thus, a call to
+clutter_stage_schedule_update() didn't have any effect if it was called
+at the wrong time.
+
+Fix this by adding a boolean state "needs_update" to the stage, set on
+clutter_stage_schedule_update() and cleared on
+_clutter_stage_do_update(), that will make the master clock reschedule
+an update if it is TRUE.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1218
+(cherry picked from commit b8003807b0772e97354302b5cc2825e0b22c6c83)
+---
+ clutter/clutter/clutter-stage.c | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index aaa77d9ede..5a914a3d0b 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -152,6 +152,8 @@ struct _ClutterStagePrivate
+ 
+   int update_freeze_count;
+ 
++  gboolean needs_update;
++
+   guint relayout_pending       : 1;
+   guint redraw_pending         : 1;
+   guint is_fullscreen          : 1;
+@@ -1074,7 +1076,9 @@ _clutter_stage_needs_update (ClutterStage *stage)
+ 
+   priv = stage->priv;
+ 
+-  return priv->relayout_pending || priv->redraw_pending;
++  return priv->relayout_pending ||
++         priv->needs_update ||
++         priv->redraw_pending;
+ }
+ 
+ void
+@@ -1232,6 +1236,8 @@ _clutter_stage_do_update (ClutterStage *stage)
+ 
+   priv->stage_was_relayout = FALSE;
+ 
++  priv->needs_update = FALSE;
++
+   /* if the stage is being destroyed, or if the destruction already
+    * happened and we don't have an StageWindow any more, then we
+    * should bail out
+@@ -4080,6 +4086,8 @@ _clutter_stage_schedule_update (ClutterStage *stage)
+   if (stage_window == NULL)
+     return;
+ 
++  stage->priv->needs_update = TRUE;
++
+   return _clutter_stage_window_schedule_update (stage_window,
+                                                 stage->priv->sync_delay);
+ }
+-- 
+2.31.1
+
+
+From 2d4453cfdcaf0c6a9f492d2c2395dbfc8cf833db Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 16 Apr 2020 19:11:37 +0200
+Subject: [PATCH 2/5] clutter/stage: Make clutter_stage_schedule_update()
+ public API
+
+It's effectively used by mutter by abusing a ClutterTimeline to scedule
+updates.  Timelines are not really suited in places that is done, as it
+is really just about getting a single new update scheduled whenever
+suitable, so expose the API so we can use it directly.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1218
+(cherry picked from commit 99c9a14bc8058830232cd4e07c7bb897e84a8c9c)
+---
+ clutter/clutter/clutter-master-clock-default.c |  4 ++--
+ clutter/clutter/clutter-stage-private.h        |  1 -
+ clutter/clutter/clutter-stage.c                | 16 ++++++++--------
+ clutter/clutter/clutter-stage.h                |  3 +++
+ 4 files changed, 13 insertions(+), 11 deletions(-)
+
+diff --git a/clutter/clutter/clutter-master-clock-default.c b/clutter/clutter/clutter-master-clock-default.c
+index 0647c3a7fd..de7a1e2f4d 100644
+--- a/clutter/clutter/clutter-master-clock-default.c
++++ b/clutter/clutter/clutter-master-clock-default.c
+@@ -206,7 +206,7 @@ master_clock_schedule_stage_updates (ClutterMasterClockDefault *master_clock)
+   stages = clutter_stage_manager_peek_stages (stage_manager);
+ 
+   for (l = stages; l != NULL; l = l->next)
+-    _clutter_stage_schedule_update (l->data);
++    clutter_stage_schedule_update (l->data);
+ }
+ 
+ static GSList *
+@@ -259,7 +259,7 @@ master_clock_reschedule_stage_updates (ClutterMasterClockDefault *master_clock,
+       if (master_clock->timelines ||
+           _clutter_stage_has_queued_events (l->data) ||
+           _clutter_stage_needs_update (l->data))
+-        _clutter_stage_schedule_update (l->data);
++        clutter_stage_schedule_update (l->data);
+     }
+ }
+ 
+diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h
+index 42474687ad..ea0ce5ec72 100644
+--- a/clutter/clutter/clutter-stage-private.h
++++ b/clutter/clutter/clutter-stage-private.h
+@@ -70,7 +70,6 @@ void     _clutter_stage_queue_event                       (ClutterStage *stage,
+ gboolean _clutter_stage_has_queued_events                 (ClutterStage *stage);
+ void     _clutter_stage_process_queued_events             (ClutterStage *stage);
+ void     _clutter_stage_update_input_devices              (ClutterStage *stage);
+-void     _clutter_stage_schedule_update                   (ClutterStage *stage);
+ gint64    _clutter_stage_get_update_time                  (ClutterStage *stage);
+ void     _clutter_stage_clear_update_time                 (ClutterStage *stage);
+ gboolean _clutter_stage_has_full_redraw_queued            (ClutterStage *stage);
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index 5a914a3d0b..74ff8b1337 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -921,7 +921,7 @@ _clutter_stage_queue_event (ClutterStage *stage,
+     {
+       ClutterMasterClock *master_clock = _clutter_master_clock_get_default ();
+       _clutter_master_clock_start_running (master_clock);
+-      _clutter_stage_schedule_update (stage);
++      clutter_stage_schedule_update (stage);
+     }
+ 
+   /* if needed, update the state of the input device of the event.
+@@ -1295,7 +1295,7 @@ clutter_stage_real_queue_relayout (ClutterActor *self)
+ 
+   if (!priv->relayout_pending)
+     {
+-      _clutter_stage_schedule_update (stage);
++      clutter_stage_schedule_update (stage);
+       priv->relayout_pending = TRUE;
+     }
+ 
+@@ -3788,7 +3788,7 @@ clutter_stage_ensure_redraw (ClutterStage *stage)
+   priv = stage->priv;
+ 
+   if (!priv->relayout_pending && !priv->redraw_pending)
+-    _clutter_stage_schedule_update (stage);
++    clutter_stage_schedule_update (stage);
+ 
+   priv->relayout_pending = TRUE;
+   priv->redraw_pending = TRUE;
+@@ -4069,13 +4069,13 @@ clutter_stage_get_minimum_size (ClutterStage *stage,
+ }
+ 
+ /**
+- * _clutter_stage_schedule_update:
+- * @window: a #ClutterStage actor
++ * clutter_stage_schedule_update:
++ * @stage: a #ClutterStage actor
+  *
+  * Schedules a redraw of the #ClutterStage at the next optimal timestamp.
+  */
+ void
+-_clutter_stage_schedule_update (ClutterStage *stage)
++clutter_stage_schedule_update (ClutterStage *stage)
+ {
+   ClutterStageWindow *stage_window;
+ 
+@@ -4097,7 +4097,7 @@ _clutter_stage_schedule_update (ClutterStage *stage)
+  * @stage: a #ClutterStage actor
+  *
+  * Returns the earliest time in which the stage is ready to update. The update
+- * time is set when _clutter_stage_schedule_update() is called. This can then
++ * time is set when clutter_stage_schedule_update() is called. This can then
+  * be used by e.g. the #ClutterMasterClock to know when the stage needs to be
+  * redrawn.
+  *
+@@ -4267,7 +4267,7 @@ _clutter_stage_queue_actor_redraw (ClutterStage                 *stage,
+ 
+       CLUTTER_NOTE (PAINT, "First redraw request");
+ 
+-      _clutter_stage_schedule_update (stage);
++      clutter_stage_schedule_update (stage);
+       priv->redraw_pending = TRUE;
+ 
+       master_clock = _clutter_master_clock_get_default ();
+diff --git a/clutter/clutter/clutter-stage.h b/clutter/clutter/clutter-stage.h
+index 9da63d211d..53e37ae3bc 100644
+--- a/clutter/clutter/clutter-stage.h
++++ b/clutter/clutter/clutter-stage.h
+@@ -265,6 +265,9 @@ CLUTTER_EXPORT
+ void            clutter_stage_skip_sync_delay                   (ClutterStage          *stage);
+ #endif
+ 
++CLUTTER_EXPORT
++void clutter_stage_schedule_update (ClutterStage *stage);
++
+ CLUTTER_EXPORT
+ gboolean clutter_stage_get_capture_final_size (ClutterStage          *stage,
+                                                cairo_rectangle_int_t *rect,
+-- 
+2.31.1
+
+
+From 1c4db591b6bb2ae9d649e8157eae24b875e7a22b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Mon, 28 Oct 2019 18:20:31 +0100
+Subject: [PATCH 3/5] wayland/actor-surface: Always store away frame callbacks
+ on commit
+
+We're expected by MetaWaylandSurface to always pick the frame callbacks
+out from the pending state when committing (applying) so that no frame
+callbacks are unaccounted for. We failed to do this if our actor for
+some reason (e.g. associated window was unmanaged) was destroyed. To
+handle this situation better, store away the frame callbacks until we
+some later point in time need to pass them on forward.
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/893
+---
+ src/wayland/meta-wayland-actor-surface.c | 25 +++++++++++++++++++++++-
+ 1 file changed, 24 insertions(+), 1 deletion(-)
+
+diff --git a/src/wayland/meta-wayland-actor-surface.c b/src/wayland/meta-wayland-actor-surface.c
+index 2471de0a92..264565c575 100644
+--- a/src/wayland/meta-wayland-actor-surface.c
++++ b/src/wayland/meta-wayland-actor-surface.c
+@@ -35,6 +35,8 @@ typedef struct _MetaWaylandActorSurfacePrivate MetaWaylandActorSurfacePrivate;
+ struct _MetaWaylandActorSurfacePrivate
+ {
+   MetaSurfaceActor *actor;
++
++  struct wl_list frame_callback_list;
+ };
+ 
+ G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (MetaWaylandActorSurface,
+@@ -56,6 +58,7 @@ meta_wayland_actor_surface_dispose (GObject *object)
+     meta_wayland_actor_surface_get_instance_private (META_WAYLAND_ACTOR_SURFACE (object));
+   MetaWaylandSurface *surface =
+     meta_wayland_surface_role_get_surface (META_WAYLAND_SURFACE_ROLE (object));
++  MetaWaylandFrameCallback *cb, *next;
+ 
+   if (priv->actor)
+     {
+@@ -66,6 +69,9 @@ meta_wayland_actor_surface_dispose (GObject *object)
+       g_clear_object (&priv->actor);
+     }
+ 
++  wl_list_for_each_safe (cb, next, &priv->frame_callback_list, link)
++    wl_resource_destroy (cb->resource);
++
+   G_OBJECT_CLASS (meta_wayland_actor_surface_parent_class)->dispose (object);
+ }
+ 
+@@ -99,6 +105,9 @@ meta_wayland_actor_surface_queue_frame_callbacks (MetaWaylandActorSurface *actor
+   MetaSurfaceActorWayland *surface_actor_wayland =
+     META_SURFACE_ACTOR_WAYLAND (priv->actor);
+ 
++  meta_surface_actor_wayland_add_frame_callbacks (surface_actor_wayland,
++                                                  &priv->frame_callback_list);
++  wl_list_init (&priv->frame_callback_list);
+   meta_surface_actor_wayland_add_frame_callbacks (surface_actor_wayland,
+                                                   &pending->frame_callback_list);
+   wl_list_init (&pending->frame_callback_list);
+@@ -253,10 +262,20 @@ meta_wayland_actor_surface_commit (MetaWaylandSurfaceRole  *surface_role,
+ {
+   MetaWaylandActorSurface *actor_surface =
+     META_WAYLAND_ACTOR_SURFACE (surface_role);
++  MetaWaylandActorSurfacePrivate *priv =
++    meta_wayland_actor_surface_get_instance_private (actor_surface);
+   MetaWaylandSurface *surface =
+     meta_wayland_surface_role_get_surface (surface_role);
+   MetaWaylandSurface *toplevel_surface;
+ 
++  if (!priv->actor)
++    {
++      wl_list_insert_list (&priv->frame_callback_list,
++                           &pending->frame_callback_list);
++      wl_list_init (&pending->frame_callback_list);
++      return;
++    }
++
+   meta_wayland_actor_surface_queue_frame_callbacks (actor_surface, pending);
+ 
+   toplevel_surface = meta_wayland_surface_get_toplevel (surface);
+@@ -307,8 +326,12 @@ meta_wayland_actor_surface_is_on_logical_monitor (MetaWaylandSurfaceRole *surfac
+ }
+ 
+ static void
+-meta_wayland_actor_surface_init (MetaWaylandActorSurface *role)
++meta_wayland_actor_surface_init (MetaWaylandActorSurface *actor_surface)
+ {
++  MetaWaylandActorSurfacePrivate *priv =
++    meta_wayland_actor_surface_get_instance_private (actor_surface);
++
++  wl_list_init (&priv->frame_callback_list);
+ }
+ 
+ static void
+-- 
+2.31.1
+
+
+From 54e5cee1a8d1a94fb19b438a3c80fe72179a3c80 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 6 Aug 2021 19:09:24 +0200
+Subject: [PATCH 4/5] wayland/actor-surface: Always store away frame callbacks
+ on commit
+
+We're expected by MetaWaylandSurface to always pick the frame callbacks
+out from the pending state when committing (applying) so that no frame
+callbacks are unaccounted for. We failed to do this if our actor for
+some reason (e.g. associated window was unmanaged) was destroyed. To
+handle this situation better, store away the frame callbacks until we
+some later point in time need to pass them on forward.
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/893
+---
+ src/compositor/meta-surface-actor-wayland.c | 29 ---------
+ src/wayland/meta-wayland-actor-surface.c    | 65 ++++++++++++++++-----
+ src/wayland/meta-wayland-actor-surface.h    |  3 +
+ src/wayland/meta-wayland-cursor-surface.c   |  4 +-
+ src/wayland/meta-wayland-dnd-surface.c      | 11 +++-
+ src/wayland/meta-wayland-legacy-xdg-shell.c |  8 +--
+ src/wayland/meta-wayland-private.h          |  2 +-
+ src/wayland/meta-wayland-subsurface.c       |  2 -
+ src/wayland/meta-wayland-surface.c          | 38 +++---------
+ src/wayland/meta-wayland-surface.h          |  9 +--
+ src/wayland/meta-wayland-wl-shell.c         |  3 -
+ src/wayland/meta-wayland-xdg-shell.c        | 15 ++---
+ src/wayland/meta-wayland.c                  | 56 +++++++++++++-----
+ src/wayland/meta-wayland.h                  |  8 ++-
+ src/wayland/meta-xwayland.c                 | 45 --------------
+ 15 files changed, 127 insertions(+), 171 deletions(-)
+
+diff --git a/src/compositor/meta-surface-actor-wayland.c b/src/compositor/meta-surface-actor-wayland.c
+index a75c4dd096..480b51c61a 100644
+--- a/src/compositor/meta-surface-actor-wayland.c
++++ b/src/compositor/meta-surface-actor-wayland.c
+@@ -42,7 +42,6 @@ struct _MetaSurfaceActorWayland
+   MetaSurfaceActor parent;
+ 
+   MetaWaylandSurface *surface;
+-  struct wl_list frame_callback_list;
+ };
+ 
+ G_DEFINE_TYPE (MetaSurfaceActorWayland,
+@@ -91,13 +90,6 @@ meta_surface_actor_wayland_is_unredirected (MetaSurfaceActor *actor)
+   return FALSE;
+ }
+ 
+-void
+-meta_surface_actor_wayland_add_frame_callbacks (MetaSurfaceActorWayland *self,
+-                                                struct wl_list *frame_callbacks)
+-{
+-  wl_list_insert_list (&self->frame_callback_list, frame_callbacks);
+-}
+-
+ static MetaWindow *
+ meta_surface_actor_wayland_get_window (MetaSurfaceActor *actor)
+ {
+@@ -158,22 +150,6 @@ meta_surface_actor_wayland_get_preferred_height  (ClutterActor *actor,
+     *natural_height_p *= scale;
+ }
+ 
+-static void
+-meta_surface_actor_wayland_paint (ClutterActor *actor)
+-{
+-  MetaSurfaceActorWayland *self = META_SURFACE_ACTOR_WAYLAND (actor);
+-
+-  if (self->surface)
+-    {
+-      MetaWaylandCompositor *compositor = self->surface->compositor;
+-
+-      wl_list_insert_list (&compositor->frame_callbacks, &self->frame_callback_list);
+-      wl_list_init (&self->frame_callback_list);
+-    }
+-
+-  CLUTTER_ACTOR_CLASS (meta_surface_actor_wayland_parent_class)->paint (actor);
+-}
+-
+ static void
+ meta_surface_actor_wayland_dispose (GObject *object)
+ {
+@@ -190,9 +166,6 @@ meta_surface_actor_wayland_dispose (GObject *object)
+       self->surface = NULL;
+     }
+ 
+-  wl_list_for_each_safe (cb, next, &self->frame_callback_list, link)
+-    wl_resource_destroy (cb->resource);
+-
+   G_OBJECT_CLASS (meta_surface_actor_wayland_parent_class)->dispose (object);
+ }
+ 
+@@ -205,7 +178,6 @@ meta_surface_actor_wayland_class_init (MetaSurfaceActorWaylandClass *klass)
+ 
+   actor_class->get_preferred_width = meta_surface_actor_wayland_get_preferred_width;
+   actor_class->get_preferred_height = meta_surface_actor_wayland_get_preferred_height;
+-  actor_class->paint = meta_surface_actor_wayland_paint;
+ 
+   surface_actor_class->process_damage = meta_surface_actor_wayland_process_damage;
+   surface_actor_class->pre_paint = meta_surface_actor_wayland_pre_paint;
+@@ -232,7 +204,6 @@ meta_surface_actor_wayland_new (MetaWaylandSurface *surface)
+ 
+   g_assert (meta_is_wayland_compositor ());
+ 
+-  wl_list_init (&self->frame_callback_list);
+   self->surface = surface;
+   g_object_add_weak_pointer (G_OBJECT (self->surface),
+                              (gpointer *) &self->surface);
+diff --git a/src/wayland/meta-wayland-actor-surface.c b/src/wayland/meta-wayland-actor-surface.c
+index 264565c575..037dd901ab 100644
+--- a/src/wayland/meta-wayland-actor-surface.c
++++ b/src/wayland/meta-wayland-actor-surface.c
+@@ -84,16 +84,22 @@ meta_wayland_actor_surface_assigned (MetaWaylandSurfaceRole *surface_role)
+     meta_wayland_surface_role_get_surface (surface_role);
+   GList *l;
+ 
+-  meta_surface_actor_wayland_add_frame_callbacks (META_SURFACE_ACTOR_WAYLAND (priv->actor),
+-                                                  &surface->pending_frame_callback_list);
+-  wl_list_init (&surface->pending_frame_callback_list);
+-
+   for (l = surface->subsurfaces; l; l = l->next)
+     {
+       ClutterActor *subsurface_actor =
+         CLUTTER_ACTOR (meta_wayland_surface_get_actor (l->data));
+       clutter_actor_add_child (CLUTTER_ACTOR (priv->actor), subsurface_actor);
+     }
++
++  if (wl_list_empty (&surface->unassigned.pending_frame_callback_list))
++    return;
++
++  wl_list_insert_list (priv->frame_callback_list.prev,
++                       &surface->unassigned.pending_frame_callback_list);
++  wl_list_init (&surface->unassigned.pending_frame_callback_list);
++
++  meta_wayland_compositor_add_frame_callback_surface (surface->compositor,
++                                                      surface);
+ }
+ 
+ void
+@@ -102,15 +108,40 @@ meta_wayland_actor_surface_queue_frame_callbacks (MetaWaylandActorSurface *actor
+ {
+   MetaWaylandActorSurfacePrivate *priv =
+     meta_wayland_actor_surface_get_instance_private (actor_surface);
+-  MetaSurfaceActorWayland *surface_actor_wayland =
+-    META_SURFACE_ACTOR_WAYLAND (priv->actor);
++  MetaWaylandSurfaceRole *surface_role =
++    META_WAYLAND_SURFACE_ROLE (actor_surface);
++  MetaWaylandSurface *surface =
++    meta_wayland_surface_role_get_surface (surface_role);
+ 
+-  meta_surface_actor_wayland_add_frame_callbacks (surface_actor_wayland,
+-                                                  &priv->frame_callback_list);
+-  wl_list_init (&priv->frame_callback_list);
+-  meta_surface_actor_wayland_add_frame_callbacks (surface_actor_wayland,
+-                                                  &pending->frame_callback_list);
++  if (!priv->actor)
++    return;
++
++  if (wl_list_empty (&pending->frame_callback_list))
++    return;
++
++  wl_list_insert_list (priv->frame_callback_list.prev,
++                       &pending->frame_callback_list);
+   wl_list_init (&pending->frame_callback_list);
++
++  meta_wayland_compositor_add_frame_callback_surface (surface->compositor,
++                                                      surface);
++}
++
++void
++meta_wayland_actor_surface_emit_frame_callbacks (MetaWaylandActorSurface *actor_surface,
++                                                 uint32_t                 timestamp_ms)
++{
++  MetaWaylandActorSurfacePrivate *priv =
++    meta_wayland_actor_surface_get_instance_private (actor_surface);
++
++  while (!wl_list_empty (&priv->frame_callback_list))
++    {
++      MetaWaylandFrameCallback *callback =
++        wl_container_of (priv->frame_callback_list.next, callback, link);
++
++      wl_callback_send_done (callback->resource, timestamp_ms);
++      wl_resource_destroy (callback->resource);
++    }
+ }
+ 
+ static double
+@@ -268,12 +299,14 @@ meta_wayland_actor_surface_commit (MetaWaylandSurfaceRole  *surface_role,
+     meta_wayland_surface_role_get_surface (surface_role);
+   MetaWaylandSurface *toplevel_surface;
+ 
+-  if (!priv->actor)
++  if (!wl_list_empty (&pending->frame_callback_list) &&
++      priv->actor &&
++      !meta_surface_actor_is_obscured (priv->actor))
+     {
+-      wl_list_insert_list (&priv->frame_callback_list,
+-                           &pending->frame_callback_list);
+-      wl_list_init (&pending->frame_callback_list);
+-      return;
++      MetaBackend *backend = meta_get_backend ();
++      ClutterActor *stage = meta_backend_get_stage (backend);
++
++      clutter_stage_schedule_update (CLUTTER_STAGE (stage));
+     }
+ 
+   meta_wayland_actor_surface_queue_frame_callbacks (actor_surface, pending);
+diff --git a/src/wayland/meta-wayland-actor-surface.h b/src/wayland/meta-wayland-actor-surface.h
+index 444b3b1785..e79f1caff5 100644
+--- a/src/wayland/meta-wayland-actor-surface.h
++++ b/src/wayland/meta-wayland-actor-surface.h
+@@ -46,4 +46,7 @@ void meta_wayland_actor_surface_reset_actor (MetaWaylandActorSurface *actor_surf
+ void meta_wayland_actor_surface_queue_frame_callbacks (MetaWaylandActorSurface *actor_surface,
+                                                        MetaWaylandPendingState *pending);
+ 
++void meta_wayland_actor_surface_emit_frame_callbacks (MetaWaylandActorSurface *actor_surface,
++                                                      uint32_t                 timestamp_ms);
++
+ #endif /* META_WAYLAND_ACTOR_SURFACE_H */
+diff --git a/src/wayland/meta-wayland-cursor-surface.c b/src/wayland/meta-wayland-cursor-surface.c
+index d46b3511fa..6b791eb378 100644
+--- a/src/wayland/meta-wayland-cursor-surface.c
++++ b/src/wayland/meta-wayland-cursor-surface.c
+@@ -124,8 +124,8 @@ meta_wayland_cursor_surface_assigned (MetaWaylandSurfaceRole *surface_role)
+     meta_wayland_cursor_surface_get_instance_private (cursor_surface);
+ 
+   wl_list_insert_list (&priv->frame_callbacks,
+-                       &surface->pending_frame_callback_list);
+-  wl_list_init (&surface->pending_frame_callback_list);
++                       &surface->unassigned.pending_frame_callback_list);
++  wl_list_init (&surface->unassigned.pending_frame_callback_list);
+ }
+ 
+ static void
+diff --git a/src/wayland/meta-wayland-dnd-surface.c b/src/wayland/meta-wayland-dnd-surface.c
+index 2aad6dcd5d..8ddeb2a7bd 100644
+--- a/src/wayland/meta-wayland-dnd-surface.c
++++ b/src/wayland/meta-wayland-dnd-surface.c
+@@ -21,6 +21,8 @@
+ 
+ #include "wayland/meta-wayland-dnd-surface.h"
+ 
++#include "wayland/meta-wayland.h"
++
+ struct _MetaWaylandSurfaceRoleDND
+ {
+   MetaWaylandActorSurface parent;
+@@ -36,7 +38,11 @@ dnd_surface_assigned (MetaWaylandSurfaceRole *surface_role)
+   MetaWaylandSurface *surface =
+     meta_wayland_surface_role_get_surface (surface_role);
+ 
+-  meta_wayland_surface_queue_pending_frame_callbacks (surface);
++  if (wl_list_empty (&surface->unassigned.pending_frame_callback_list))
++    return;
++
++  meta_wayland_compositor_add_frame_callback_surface (surface->compositor,
++                                                      surface);
+ }
+ 
+ static void
+@@ -46,7 +52,8 @@ dnd_surface_commit (MetaWaylandSurfaceRole  *surface_role,
+   MetaWaylandSurface *surface =
+     meta_wayland_surface_role_get_surface (surface_role);
+ 
+-  meta_wayland_surface_queue_pending_state_frame_callbacks (surface, pending);
++  meta_wayland_compositor_add_frame_callback_surface (surface->compositor,
++                                                      surface);
+ }
+ 
+ static void
+diff --git a/src/wayland/meta-wayland-legacy-xdg-shell.c b/src/wayland/meta-wayland-legacy-xdg-shell.c
+index 8230641770..b78552f31b 100644
+--- a/src/wayland/meta-wayland-legacy-xdg-shell.c
++++ b/src/wayland/meta-wayland-legacy-xdg-shell.c
+@@ -659,6 +659,8 @@ meta_wayland_zxdg_toplevel_v6_commit (MetaWaylandSurfaceRole  *surface_role,
+     META_WAYLAND_ZXDG_SURFACE_V6 (xdg_toplevel);
+   MetaWaylandZxdgSurfaceV6Private *xdg_surface_priv =
+     meta_wayland_zxdg_surface_v6_get_instance_private (xdg_surface);
++  MetaWaylandActorSurface *actor_surface =
++    META_WAYLAND_ACTOR_SURFACE (xdg_surface);
+   MetaWaylandSurfaceRoleClass *surface_role_class;
+   MetaWaylandSurface *surface =
+     meta_wayland_surface_role_get_surface (surface_role);
+@@ -670,7 +672,7 @@ meta_wayland_zxdg_toplevel_v6_commit (MetaWaylandSurfaceRole  *surface_role,
+   window = surface->window;
+   if (!window)
+     {
+-      meta_wayland_surface_cache_pending_frame_callbacks (surface, pending);
++      meta_wayland_actor_surface_queue_frame_callbacks (actor_surface, pending);
+       return;
+     }
+ 
+@@ -1220,14 +1222,10 @@ meta_wayland_zxdg_surface_v6_send_configure (MetaWaylandZxdgSurfaceV6 *xdg_surfa
+ static void
+ zxdg_surface_v6_destructor (struct wl_resource *resource)
+ {
+-  MetaWaylandSurface *surface = surface_from_xdg_surface_resource (resource);
+   MetaWaylandZxdgSurfaceV6 *xdg_surface = wl_resource_get_user_data (resource);
+   MetaWaylandZxdgSurfaceV6Private *priv =
+     meta_wayland_zxdg_surface_v6_get_instance_private (xdg_surface);
+ 
+-  meta_wayland_compositor_destroy_frame_callbacks (surface->compositor,
+-                                                   surface);
+-
+   priv->shell_client->surfaces = g_list_remove (priv->shell_client->surfaces,
+                                                 xdg_surface);
+ 
+diff --git a/src/wayland/meta-wayland-private.h b/src/wayland/meta-wayland-private.h
+index 5bcb0ea4f9..215d0967f6 100644
+--- a/src/wayland/meta-wayland-private.h
++++ b/src/wayland/meta-wayland-private.h
+@@ -67,7 +67,7 @@ struct _MetaWaylandCompositor
+   struct wl_display *wayland_display;
+   char *display_name;
+   GHashTable *outputs;
+-  struct wl_list frame_callbacks;
++  GList *frame_callback_surfaces;
+ 
+   MetaXWaylandManager xwayland_manager;
+ 
+diff --git a/src/wayland/meta-wayland-subsurface.c b/src/wayland/meta-wayland-subsurface.c
+index e0fa0a48b2..c7059b99a2 100644
+--- a/src/wayland/meta-wayland-subsurface.c
++++ b/src/wayland/meta-wayland-subsurface.c
+@@ -239,8 +239,6 @@ wl_subsurface_destructor (struct wl_resource *resource)
+ {
+   MetaWaylandSurface *surface = wl_resource_get_user_data (resource);
+ 
+-  meta_wayland_compositor_destroy_frame_callbacks (surface->compositor,
+-                                                   surface);
+   if (surface->sub.parent)
+     {
+       wl_list_remove (&surface->sub.parent_destroy_listener.link);
+diff --git a/src/wayland/meta-wayland-surface.c b/src/wayland/meta-wayland-surface.c
+index 6ffcd6a7fb..a76ab28c24 100644
+--- a/src/wayland/meta-wayland-surface.c
++++ b/src/wayland/meta-wayland-surface.c
+@@ -358,15 +358,6 @@ surface_process_damage (MetaWaylandSurface *surface,
+   cairo_region_destroy (transformed_region);
+ }
+ 
+-void
+-meta_wayland_surface_queue_pending_state_frame_callbacks (MetaWaylandSurface      *surface,
+-                                                          MetaWaylandPendingState *pending)
+-{
+-  wl_list_insert_list (&surface->compositor->frame_callbacks,
+-                       &pending->frame_callback_list);
+-  wl_list_init (&pending->frame_callback_list);
+-}
+-
+ void
+ meta_wayland_surface_destroy_window (MetaWaylandSurface *surface)
+ {
+@@ -656,15 +647,6 @@ parent_surface_state_applied (gpointer data,
+   meta_wayland_subsurface_parent_state_applied (subsurface);
+ }
+ 
+-void
+-meta_wayland_surface_cache_pending_frame_callbacks (MetaWaylandSurface      *surface,
+-                                                    MetaWaylandPendingState *pending)
+-{
+-  wl_list_insert_list (&surface->pending_frame_callback_list,
+-                       &pending->frame_callback_list);
+-  wl_list_init (&pending->frame_callback_list);
+-}
+-
+ void
+ meta_wayland_surface_apply_pending_state (MetaWaylandSurface      *surface,
+                                           MetaWaylandPendingState *pending)
+@@ -810,7 +792,9 @@ meta_wayland_surface_apply_pending_state (MetaWaylandSurface      *surface,
+     }
+   else
+     {
+-      meta_wayland_surface_cache_pending_frame_callbacks (surface, pending);
++      wl_list_insert_list (surface->unassigned.pending_frame_callback_list.prev,
++                           &pending->frame_callback_list);
++      wl_list_init (&pending->frame_callback_list);
+ 
+       if (pending->newly_attached)
+         {
+@@ -1352,12 +1336,14 @@ wl_surface_destructor (struct wl_resource *resource)
+   if (surface->input_region)
+     cairo_region_destroy (surface->input_region);
+ 
+-  meta_wayland_compositor_destroy_frame_callbacks (compositor, surface);
++  meta_wayland_compositor_remove_frame_callback_surface (compositor, surface);
+ 
+   g_hash_table_foreach (surface->outputs_to_destroy_notify_id, surface_output_disconnect_signal, surface);
+   g_hash_table_unref (surface->outputs_to_destroy_notify_id);
+ 
+-  wl_list_for_each_safe (cb, next, &surface->pending_frame_callback_list, link)
++  wl_list_for_each_safe (cb, next,
++                         &surface->unassigned.pending_frame_callback_list,
++                         link)
+     wl_resource_destroy (cb->resource);
+ 
+   if (surface->resource)
+@@ -1401,7 +1387,7 @@ meta_wayland_surface_create (MetaWaylandCompositor *compositor,
+   surface->resource = wl_resource_create (client, &wl_surface_interface, wl_resource_get_version (compositor_resource), id);
+   wl_resource_set_implementation (surface->resource, &meta_wayland_wl_surface_interface, surface, wl_surface_destructor);
+ 
+-  wl_list_init (&surface->pending_frame_callback_list);
++  wl_list_init (&surface->unassigned.pending_frame_callback_list);
+ 
+   sync_drag_dest_funcs (surface);
+ 
+@@ -1809,14 +1795,6 @@ meta_wayland_surface_role_get_surface (MetaWaylandSurfaceRole *role)
+   return priv->surface;
+ }
+ 
+-void
+-meta_wayland_surface_queue_pending_frame_callbacks (MetaWaylandSurface *surface)
+-{
+-  wl_list_insert_list (&surface->compositor->frame_callbacks,
+-                       &surface->pending_frame_callback_list);
+-  wl_list_init (&surface->pending_frame_callback_list);
+-}
+-
+ cairo_region_t *
+ meta_wayland_surface_calculate_input_region (MetaWaylandSurface *surface)
+ {
+diff --git a/src/wayland/meta-wayland-surface.h b/src/wayland/meta-wayland-surface.h
+index e244a3fdf7..776431fca2 100644
+--- a/src/wayland/meta-wayland-surface.h
++++ b/src/wayland/meta-wayland-surface.h
+@@ -160,13 +160,9 @@ struct _MetaWaylandSurface
+   /* Buffer renderer state. */
+   gboolean buffer_held;
+ 
+-  /* List of pending frame callbacks that needs to stay queued longer than one
+-   * commit sequence, such as when it has not yet been assigned a role.
+-   */
+-  struct wl_list pending_frame_callback_list;
+-
+   /* Intermediate state for when no role has been assigned. */
+   struct {
++    struct wl_list pending_frame_callback_list;
+     MetaWaylandBuffer *buffer;
+   } unassigned;
+ 
+@@ -274,9 +270,6 @@ MetaWaylandSurface *meta_wayland_surface_get_toplevel (MetaWaylandSurface *surfa
+ 
+ MetaWindow *        meta_wayland_surface_get_toplevel_window (MetaWaylandSurface *surface);
+ 
+-void                meta_wayland_surface_cache_pending_frame_callbacks (MetaWaylandSurface      *surface,
+-                                                                        MetaWaylandPendingState *pending);
+-
+ void                meta_wayland_surface_queue_pending_frame_callbacks (MetaWaylandSurface *surface);
+ 
+ void                meta_wayland_surface_queue_pending_state_frame_callbacks (MetaWaylandSurface      *surface,
+diff --git a/src/wayland/meta-wayland-wl-shell.c b/src/wayland/meta-wayland-wl-shell.c
+index 539fb9858e..e80db17e78 100644
+--- a/src/wayland/meta-wayland-wl-shell.c
++++ b/src/wayland/meta-wayland-wl-shell.c
+@@ -100,9 +100,6 @@ wl_shell_surface_destructor (struct wl_resource *resource)
+     surface_from_wl_shell_surface_resource (resource);
+   GList *l;
+ 
+-  meta_wayland_compositor_destroy_frame_callbacks (surface->compositor,
+-                                                   surface);
+-
+   if (wl_shell_surface->popup)
+     meta_wayland_popup_dismiss (wl_shell_surface->popup);
+ 
+diff --git a/src/wayland/meta-wayland-xdg-shell.c b/src/wayland/meta-wayland-xdg-shell.c
+index fa0207a03c..4a4995c425 100644
+--- a/src/wayland/meta-wayland-xdg-shell.c
++++ b/src/wayland/meta-wayland-xdg-shell.c
+@@ -684,6 +684,8 @@ meta_wayland_xdg_toplevel_commit (MetaWaylandSurfaceRole  *surface_role,
+   MetaWaylandXdgSurface *xdg_surface = META_WAYLAND_XDG_SURFACE (xdg_toplevel);
+   MetaWaylandXdgSurfacePrivate *xdg_surface_priv =
+     meta_wayland_xdg_surface_get_instance_private (xdg_surface);
++  MetaWaylandActorSurface *actor_surface =
++    META_WAYLAND_ACTOR_SURFACE (xdg_toplevel);
+   MetaWaylandSurfaceRoleClass *surface_role_class;
+   MetaWaylandSurface *surface =
+     meta_wayland_surface_role_get_surface (surface_role);
+@@ -695,15 +697,12 @@ meta_wayland_xdg_toplevel_commit (MetaWaylandSurfaceRole  *surface_role,
+   window = surface->window;
+   if (!window)
+     {
+-      meta_wayland_surface_cache_pending_frame_callbacks (surface, pending);
++      meta_wayland_actor_surface_queue_frame_callbacks (actor_surface, pending);
+       return;
+     }
+ 
+   if (!surface->buffer_ref.buffer && xdg_surface_priv->first_buffer_attached)
+     {
+-      MetaWaylandActorSurface *actor_surface =
+-        META_WAYLAND_ACTOR_SURFACE (xdg_toplevel);
+-
+       meta_wayland_xdg_surface_reset (xdg_surface);
+       meta_wayland_actor_surface_queue_frame_callbacks (actor_surface,
+                                                         pending);
+@@ -1037,6 +1036,8 @@ meta_wayland_xdg_popup_commit (MetaWaylandSurfaceRole  *surface_role,
+   MetaWaylandXdgSurface *xdg_surface = META_WAYLAND_XDG_SURFACE (surface_role);
+   MetaWaylandXdgSurfacePrivate *xdg_surface_priv =
+     meta_wayland_xdg_surface_get_instance_private (xdg_surface);
++  MetaWaylandActorSurface *actor_surface =
++    META_WAYLAND_ACTOR_SURFACE (xdg_popup);
+   MetaWaylandSurfaceRoleClass *surface_role_class;
+   MetaWaylandSurface *surface =
+     meta_wayland_surface_role_get_surface (surface_role);
+@@ -1048,7 +1049,7 @@ meta_wayland_xdg_popup_commit (MetaWaylandSurfaceRole  *surface_role,
+   if (!surface->buffer_ref.buffer && xdg_surface_priv->first_buffer_attached)
+     {
+       meta_wayland_xdg_surface_reset (xdg_surface);
+-      meta_wayland_surface_cache_pending_frame_callbacks (surface, pending);
++      meta_wayland_actor_surface_queue_frame_callbacks (actor_surface, pending);
+       return;
+     }
+ 
+@@ -1313,14 +1314,10 @@ meta_wayland_xdg_surface_send_configure (MetaWaylandXdgSurface *xdg_surface)
+ static void
+ xdg_surface_destructor (struct wl_resource *resource)
+ {
+-  MetaWaylandSurface *surface = surface_from_xdg_surface_resource (resource);
+   MetaWaylandXdgSurface *xdg_surface = wl_resource_get_user_data (resource);
+   MetaWaylandXdgSurfacePrivate *priv =
+     meta_wayland_xdg_surface_get_instance_private (xdg_surface);
+ 
+-  meta_wayland_compositor_destroy_frame_callbacks (surface->compositor,
+-                                                   surface);
+-
+   priv->shell_client->surfaces = g_list_remove (priv->shell_client->surfaces,
+                                                 xdg_surface);
+ 
+diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c
+index 129da8e20d..4cb9ca650d 100644
+--- a/src/wayland/meta-wayland.c
++++ b/src/wayland/meta-wayland.c
+@@ -194,15 +194,35 @@ meta_wayland_compositor_update (MetaWaylandCompositor *compositor,
+ void
+ meta_wayland_compositor_paint_finished (MetaWaylandCompositor *compositor)
+ {
+-  gint64 current_time = g_get_monotonic_time ();
++  GList *l;
++  int64_t now_us;
+ 
+-  while (!wl_list_empty (&compositor->frame_callbacks))
++  now_us = g_get_monotonic_time ();
++
++  l = compositor->frame_callback_surfaces;
++  while (l)
+     {
+-      MetaWaylandFrameCallback *callback =
+-        wl_container_of (compositor->frame_callbacks.next, callback, link);
++      GList *l_cur = l;
++      MetaWaylandSurface *surface = l->data;
++      MetaSurfaceActor *actor;
++      MetaWaylandActorSurface *actor_surface;
++
++      l = l->next;
++
++      actor = meta_wayland_surface_get_actor (surface);
++      if (!actor)
++        continue;
++
++      if (!clutter_actor_has_mapped_clones (CLUTTER_ACTOR (actor)) &&
++          meta_surface_actor_is_obscured (actor))
++        continue;
+ 
+-      wl_callback_send_done (callback->resource, current_time / 1000);
+-      wl_resource_destroy (callback->resource);
++      actor_surface = META_WAYLAND_ACTOR_SURFACE (surface->role);
++      meta_wayland_actor_surface_emit_frame_callbacks (actor_surface,
++                                                       now_us / 1000);
++
++      compositor->frame_callback_surfaces =
++        g_list_delete_link (compositor->frame_callback_surfaces, l_cur);
+     }
+ }
+ 
+@@ -249,16 +269,22 @@ meta_wayland_compositor_update_key_state (MetaWaylandCompositor *compositor,
+ }
+ 
+ void
+-meta_wayland_compositor_destroy_frame_callbacks (MetaWaylandCompositor *compositor,
+-                                                 MetaWaylandSurface    *surface)
++meta_wayland_compositor_add_frame_callback_surface (MetaWaylandCompositor *compositor,
++                                                    MetaWaylandSurface    *surface)
+ {
+-  MetaWaylandFrameCallback *callback, *next;
++  if (g_list_find (compositor->frame_callback_surfaces, surface))
++    return;
+ 
+-  wl_list_for_each_safe (callback, next, &compositor->frame_callbacks, link)
+-    {
+-      if (callback->surface == surface)
+-        wl_resource_destroy (callback->resource);
+-    }
++  compositor->frame_callback_surfaces =
++    g_list_prepend (compositor->frame_callback_surfaces, surface);
++}
++
++void
++meta_wayland_compositor_remove_frame_callback_surface (MetaWaylandCompositor *compositor,
++                                                       MetaWaylandSurface    *surface)
++{
++  compositor->frame_callback_surfaces =
++    g_list_remove (compositor->frame_callback_surfaces, surface);
+ }
+ 
+ static void
+@@ -309,8 +335,6 @@ meta_wayland_log_func (const char *fmt,
+ static void
+ meta_wayland_compositor_init (MetaWaylandCompositor *compositor)
+ {
+-  wl_list_init (&compositor->frame_callbacks);
+-
+   compositor->scheduled_surface_associations = g_hash_table_new (NULL, NULL);
+ 
+   wl_log_set_handler_server (meta_wayland_log_func);
+diff --git a/src/wayland/meta-wayland.h b/src/wayland/meta-wayland.h
+index 2a0aa11400..c5e5924891 100644
+--- a/src/wayland/meta-wayland.h
++++ b/src/wayland/meta-wayland.h
+@@ -64,9 +64,11 @@ void                    meta_wayland_compositor_set_input_focus (MetaWaylandComp
+ META_EXPORT_TEST
+ void                    meta_wayland_compositor_paint_finished  (MetaWaylandCompositor *compositor);
+ 
+-META_EXPORT_TEST
+-void                    meta_wayland_compositor_destroy_frame_callbacks (MetaWaylandCompositor *compositor,
+-                                                                         MetaWaylandSurface    *surface);
++void                    meta_wayland_compositor_add_frame_callback_surface (MetaWaylandCompositor *compositor,
++                                                                            MetaWaylandSurface    *surface);
++
++void                    meta_wayland_compositor_remove_frame_callback_surface (MetaWaylandCompositor *compositor,
++                                                                               MetaWaylandSurface    *surface);
+ 
+ META_EXPORT_TEST
+ const char             *meta_wayland_get_wayland_display_name   (MetaWaylandCompositor *compositor);
+diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c
+index 275aeb78cb..6e4b9a8ffd 100644
+--- a/src/wayland/meta-xwayland.c
++++ b/src/wayland/meta-xwayland.c
+@@ -788,49 +788,6 @@ meta_xwayland_stop (MetaXWaylandManager *manager)
+     }
+ }
+ 
+-static void
+-xwayland_surface_assigned (MetaWaylandSurfaceRole *surface_role)
+-{
+-  MetaWaylandSurface *surface =
+-    meta_wayland_surface_role_get_surface (surface_role);
+-  MetaWaylandSurfaceRoleClass *surface_role_class =
+-    META_WAYLAND_SURFACE_ROLE_CLASS (meta_wayland_surface_role_xwayland_parent_class);
+-
+-  /* See comment in xwayland_surface_commit for why we reply even though the
+-   * surface may not be drawn the next frame.
+-   */
+-  wl_list_insert_list (&surface->compositor->frame_callbacks,
+-                       &surface->pending_frame_callback_list);
+-  wl_list_init (&surface->pending_frame_callback_list);
+-
+-  surface_role_class->assigned (surface_role);
+-}
+-
+-static void
+-xwayland_surface_commit (MetaWaylandSurfaceRole  *surface_role,
+-                         MetaWaylandPendingState *pending)
+-{
+-  MetaWaylandSurface *surface =
+-    meta_wayland_surface_role_get_surface (surface_role);
+-  MetaWaylandSurfaceRoleClass *surface_role_class =
+-    META_WAYLAND_SURFACE_ROLE_CLASS (meta_wayland_surface_role_xwayland_parent_class);
+-
+-  /* For Xwayland windows, throttling frames when the window isn't actually
+-   * drawn is less useful, because Xwayland still has to do the drawing sent
+-   * from the application - the throttling would only be of sending us damage
+-   * messages, so we simplify and send frame callbacks after the next paint of
+-   * the screen, whether the window was drawn or not.
+-   *
+-   * Currently it may take a few frames before we draw the window, for not
+-   * completely understood reasons, and in that case, not thottling frame
+-   * callbacks to drawing has the happy side effect that we avoid showing the
+-   * user the initial black frame from when the window is mapped empty.
+-   */
+-  meta_wayland_surface_queue_pending_state_frame_callbacks (surface, pending);
+-
+-  surface_role_class->commit (surface_role, pending);
+-}
+-
+ static MetaWaylandSurface *
+ xwayland_surface_get_toplevel (MetaWaylandSurfaceRole *surface_role)
+ {
+@@ -848,8 +805,6 @@ meta_wayland_surface_role_xwayland_class_init (MetaWaylandSurfaceRoleXWaylandCla
+   MetaWaylandSurfaceRoleClass *surface_role_class =
+     META_WAYLAND_SURFACE_ROLE_CLASS (klass);
+ 
+-  surface_role_class->assigned = xwayland_surface_assigned;
+-  surface_role_class->commit = xwayland_surface_commit;
+   surface_role_class->get_toplevel = xwayland_surface_get_toplevel;
+ 
+   xwayland_surface_signals[XWAYLAND_SURFACE_WINDOW_ASSOCIATED] =
+-- 
+2.31.1
+
+
+From 076ac20d34db128aea8ffe0dc3c2791918667c43 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 6 Aug 2021 19:46:06 +0200
+Subject: [PATCH 5/5] wayland: Respond to frame callbacks even if the paint was
+ empty
+
+---
+ src/compositor/compositor.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c
+index ce2c1b8a3b..8331737d1a 100644
+--- a/src/compositor/compositor.c
++++ b/src/compositor/compositor.c
+@@ -467,11 +467,6 @@ after_stage_paint (ClutterStage *stage,
+ 
+   for (l = compositor->windows; l; l = l->next)
+     meta_window_actor_post_paint (l->data);
+-
+-#ifdef HAVE_WAYLAND
+-  if (meta_is_wayland_compositor ())
+-    meta_wayland_compositor_paint_finished (meta_wayland_compositor_get_default ());
+-#endif
+ }
+ 
+ static void
+@@ -1404,6 +1399,11 @@ meta_post_paint_func (gpointer data)
+       break;
+     }
+ 
++#ifdef HAVE_WAYLAND
++  if (meta_is_wayland_compositor ())
++    meta_wayland_compositor_paint_finished (meta_wayland_compositor_get_default ());
++#endif
++
+   return TRUE;
+ }
+ 
+-- 
+2.31.1
+
diff --git a/SOURCES/xwayland-xauth-xhost-user.patch b/SOURCES/xwayland-xauth-xhost-user.patch
new file mode 100644
index 0000000..519bb3b
--- /dev/null
+++ b/SOURCES/xwayland-xauth-xhost-user.patch
@@ -0,0 +1,479 @@
+From 8e756d48ed31bcacf12b99cbd82fb2052503f51e Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Tue, 18 Jun 2019 16:12:46 +0200
+Subject: [PATCH 1/4] xwayland: Generate a Xauth file and pass this to Xwayland
+ when starting it
+
+Before this commit, sudo x11-app, e.g. sudo gvim /etc/some-file, fails
+when running a Wayland session. Where as doing this under a "GNOME on Xorg"
+session works fine. For a user switching from the Xorg session to the
+Wayland session, this is regression, which we want to avoid.
+
+This commit fixes this by creating and passing an xauth file to Xwayland when
+mutter starts it. Just like gdm or startx pass a xauth file to Xorg when they
+start Xorg.
+
+Fixes #643
+
+https://gitlab.gnome.org/GNOME/mutter/issues/643
+---
+ meson.build                        |  1 +
+ src/meson.build                    |  1 +
+ src/wayland/meta-wayland-private.h |  1 +
+ src/wayland/meta-wayland.c         | 11 +++-
+ src/wayland/meta-xwayland.c        | 81 ++++++++++++++++++++++++++++++
+ 5 files changed, 94 insertions(+), 1 deletion(-)
+
+diff --git a/meson.build b/meson.build
+index 8ef592bc58..2a404857ce 100644
+--- a/meson.build
++++ b/meson.build
+@@ -117,6 +117,7 @@ xrandr_dep = dependency('xrandr', version: xrandr_req)
+ xcb_randr_dep = dependency('xcb-randr')
+ xcb_res_dep = dependency('xcb-res')
+ xinerama_dep = dependency('xinerama')
++xau_dep = dependency('xau')
+ ice_dep = dependency('ice')
+ atk_dep = dependency('atk', version: atk_req)
+ libcanberra_dep = dependency('libcanberra', version: libcanberra_req)
+diff --git a/src/meson.build b/src/meson.build
+index 7cced8f534..91fe74b99a 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -101,6 +101,7 @@ if have_x11
+     x11_xcb_dep,
+     xcb_randr_dep,
+     xcb_res_dep,
++    xau_dep,
+   ]
+ 
+   if have_sm
+diff --git a/src/wayland/meta-wayland-private.h b/src/wayland/meta-wayland-private.h
+index 07a71f82b1..5bcb0ea4f9 100644
+--- a/src/wayland/meta-wayland-private.h
++++ b/src/wayland/meta-wayland-private.h
+@@ -51,6 +51,7 @@ typedef struct
+   struct wl_client *client;
+   struct wl_resource *xserver_resource;
+   char *display_name;
++  char *auth_file;
+ 
+   GCancellable *xserver_died_cancellable;
+   GSubprocess *proc;
+diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c
+index a593f0a7b7..129da8e20d 100644
+--- a/src/wayland/meta-wayland.c
++++ b/src/wayland/meta-wayland.c
+@@ -362,6 +362,12 @@ meta_wayland_override_display_name (const char *display_name)
+   _display_name_override = g_strdup (display_name);
+ }
+ 
++static const char *
++meta_wayland_get_xwayland_auth_file (MetaWaylandCompositor *compositor)
++{
++  return compositor->xwayland_manager.auth_file;
++}
++
+ void
+ meta_wayland_init (void)
+ {
+@@ -439,7 +445,10 @@ meta_wayland_init (void)
+     }
+ 
+   if (meta_should_autostart_x11_display ())
+-    set_gnome_env ("DISPLAY", meta_wayland_get_xwayland_display_name (compositor));
++    {
++      set_gnome_env ("DISPLAY", meta_wayland_get_xwayland_display_name (compositor));
++      set_gnome_env ("XAUTHORITY", meta_wayland_get_xwayland_auth_file (compositor));
++    }
+ 
+   set_gnome_env ("WAYLAND_DISPLAY", meta_wayland_get_wayland_display_name (compositor));
+ }
+diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c
+index f3df9766ee..c883eb3d6f 100644
+--- a/src/wayland/meta-xwayland.c
++++ b/src/wayland/meta-xwayland.c
+@@ -32,6 +32,9 @@
+ #include <sys/socket.h>
+ #include <sys/stat.h>
+ #include <sys/un.h>
++#include <sys/random.h>
++#include <unistd.h>
++#include <X11/Xauth.h>
+ 
+ #include "compositor/meta-surface-actor-wayland.h"
+ #include "meta/main.h"
+@@ -525,6 +528,75 @@ choose_xdisplay (MetaXWaylandManager *manager)
+   return TRUE;
+ }
+ 
++G_DEFINE_AUTOPTR_CLEANUP_FUNC (FILE, fclose)
++
++static gboolean
++prepare_auth_file (MetaXWaylandManager *manager)
++{
++  Xauth auth_entry = { 0 };
++  g_autoptr (FILE) fp = NULL;
++  char hostname[HOST_NAME_MAX + 1];
++  char auth_data[16];
++  int fd;
++
++  manager->auth_file = g_build_filename (g_get_user_runtime_dir (),
++                                         ".mutter-Xwaylandauth.XXXXXX",
++                                         NULL);
++
++  if (gethostname (hostname, HOST_NAME_MAX) < 0)
++    g_strlcpy (hostname, "localhost", HOST_NAME_MAX);
++
++  if (getrandom (auth_data, sizeof (auth_data), 0) != sizeof (auth_data))
++    {
++      g_warning ("Failed to get random data: %s", g_strerror (errno));
++      return FALSE;
++    }
++
++  auth_entry.family = FamilyLocal;
++  auth_entry.address = hostname;
++  auth_entry.address_length = strlen (auth_entry.address);
++  auth_entry.name = (char *) "MIT-MAGIC-COOKIE-1";
++  auth_entry.name_length = strlen (auth_entry.name);
++  auth_entry.data = auth_data;
++  auth_entry.data_length = sizeof (auth_data);
++
++  fd = g_mkstemp (manager->auth_file);
++  if (fd < 0)
++    {
++      g_warning ("Failed to open Xauthority file: %s", g_strerror (errno));
++      return FALSE;
++    }
++
++  fp = fdopen (fd, "w+");
++  if (!fp)
++    {
++      g_warning ("Failed to open Xauthority stream: %s", g_strerror (errno));
++      close (fd);
++      return FALSE;
++    }
++
++  if (!XauWriteAuth (fp, &auth_entry))
++    {
++      g_warning ("Error writing to Xauthority file: %s", g_strerror (errno));
++      return FALSE;
++    }
++
++  auth_entry.family = FamilyWild;
++  if (!XauWriteAuth (fp, &auth_entry))
++    {
++      g_warning ("Error writing to Xauthority file: %s", g_strerror (errno));
++      return FALSE;
++    }
++
++  if (fflush (fp) == EOF)
++    {
++      g_warning ("Error writing to Xauthority file: %s", g_strerror (errno));
++      return FALSE;
++    }
++
++  return TRUE;
++}
++
+ static void
+ xserver_finished_init (MetaXWaylandManager *manager)
+ {
+@@ -566,6 +638,9 @@ meta_xwayland_start (MetaXWaylandManager *manager,
+   if (!choose_xdisplay (manager))
+     goto out;
+ 
++  if (!prepare_auth_file (manager))
++    goto out;
++
+   /* We want xwayland to be a wayland client so we make a socketpair to setup a
+    * wayland protocol connection. */
+   if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, xwayland_client_fd) < 0)
+@@ -610,6 +685,7 @@ meta_xwayland_start (MetaXWaylandManager *manager,
+                                                "-terminate",
+                                                "-accessx",
+                                                "-core",
++                                               "-auth", manager->auth_file,
+                                                "-listen", "4",
+                                                "-listen", "5",
+                                                "-displayfd", "6",
+@@ -678,6 +754,11 @@ meta_xwayland_stop (MetaXWaylandManager *manager)
+   unlink (path);
+ 
+   g_clear_pointer (&manager->display_name, g_free);
++  if (manager->auth_file)
++    {
++      unlink (manager->auth_file);
++      g_clear_pointer (&manager->auth_file, g_free);
++    }
+   if (manager->lock_file)
+     {
+       unlink (manager->lock_file);
+-- 
+2.31.1
+
+
+From fdf6969cf89dc9127fc9f4d03d9408e54ccd1b40 Mon Sep 17 00:00:00 2001
+From: Olivier Fourdan <ofourdan@redhat.com>
+Date: Mon, 19 Aug 2019 15:36:32 +0200
+Subject: [PATCH 2/4] xwayland: pass the X11 display
+
+Pass the X11 display to `meta_xwayland_complete_init()` so that it can
+be used without poking into GDK.
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/735
+---
+ src/wayland/meta-xwayland-private.h | 3 ++-
+ src/wayland/meta-xwayland.c         | 3 ++-
+ src/x11/meta-x11-display.c          | 5 ++---
+ 3 files changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/src/wayland/meta-xwayland-private.h b/src/wayland/meta-xwayland-private.h
+index 38874eda3f..abcb09e49b 100644
+--- a/src/wayland/meta-xwayland-private.h
++++ b/src/wayland/meta-xwayland-private.h
+@@ -29,7 +29,8 @@ meta_xwayland_start (MetaXWaylandManager *manager,
+                      struct wl_display   *display);
+ 
+ void
+-meta_xwayland_complete_init (MetaDisplay *display);
++meta_xwayland_complete_init (MetaDisplay *display,
++                             Display     *xdisplay);
+ 
+ void
+ meta_xwayland_stop (MetaXWaylandManager *manager);
+diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c
+index c883eb3d6f..350626dfdb 100644
+--- a/src/wayland/meta-xwayland.c
++++ b/src/wayland/meta-xwayland.c
+@@ -727,7 +727,8 @@ on_x11_display_closing (MetaDisplay *display)
+ 
+ /* To be called right after connecting */
+ void
+-meta_xwayland_complete_init (MetaDisplay *display)
++meta_xwayland_complete_init (MetaDisplay *display,
++                             Display     *xdisplay)
+ {
+   /* We install an X IO error handler in addition to the child watch,
+      because after Xlib connects our child watch may not be called soon
+diff --git a/src/x11/meta-x11-display.c b/src/x11/meta-x11-display.c
+index 065ffcdda5..d40dcfa3f8 100644
+--- a/src/x11/meta-x11-display.c
++++ b/src/x11/meta-x11-display.c
+@@ -1066,14 +1066,13 @@ meta_x11_display_new (MetaDisplay *display, GError **error)
+ 
+   g_assert (prepared_gdk_display);
+   gdk_display = g_steal_pointer (&prepared_gdk_display);
++  xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display);
+ 
+ #ifdef HAVE_WAYLAND
+   if (meta_is_wayland_compositor ())
+-    meta_xwayland_complete_init (display);
++    meta_xwayland_complete_init (display, xdisplay);
+ #endif
+ 
+-  xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display);
+-
+   if (meta_is_syncing ())
+     XSynchronize (xdisplay, True);
+ 
+-- 
+2.31.1
+
+
+From 25a0945aa69c479d6356a970b39e6ae42e43c877 Mon Sep 17 00:00:00 2001
+From: Olivier Fourdan <ofourdan@redhat.com>
+Date: Mon, 19 Aug 2019 15:48:17 +0200
+Subject: [PATCH 3/4] xwayland: Use given X11 display for DnD setup
+
+Use the provided X11 display instead of poking into GDK to get the X11
+display.
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/735
+---
+ src/wayland/meta-xwayland-private.h   |  4 ++--
+ src/wayland/meta-xwayland-selection.c | 18 +++++++++---------
+ src/wayland/meta-xwayland.c           |  7 +++++--
+ 3 files changed, 16 insertions(+), 13 deletions(-)
+
+diff --git a/src/wayland/meta-xwayland-private.h b/src/wayland/meta-xwayland-private.h
+index abcb09e49b..f562d7c96d 100644
+--- a/src/wayland/meta-xwayland-private.h
++++ b/src/wayland/meta-xwayland-private.h
+@@ -36,8 +36,8 @@ void
+ meta_xwayland_stop (MetaXWaylandManager *manager);
+ 
+ /* wl_data_device/X11 selection interoperation */
+-void     meta_xwayland_init_selection         (void);
+-void     meta_xwayland_shutdown_selection     (void);
++void     meta_xwayland_init_selection         (Display *xdisplay);
++void     meta_xwayland_shutdown_selection     (Display *xdisplay);
+ gboolean meta_xwayland_selection_handle_event (XEvent *xevent);
+ 
+ const MetaWaylandDragDestFuncs * meta_xwayland_selection_get_drag_dest_funcs (void);
+diff --git a/src/wayland/meta-xwayland-selection.c b/src/wayland/meta-xwayland-selection.c
+index 808f913339..122bb76e1c 100644
+--- a/src/wayland/meta-xwayland-selection.c
++++ b/src/wayland/meta-xwayland-selection.c
+@@ -353,9 +353,9 @@ xdnd_send_status (MetaXWaylandSelection *selection_data,
+ }
+ 
+ static void
+-meta_xwayland_init_dnd (MetaXWaylandManager *manager)
++meta_xwayland_init_dnd (MetaXWaylandManager *manager,
++                        Display             *xdisplay)
+ {
+-  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+   MetaDndBridge *dnd = &manager->selection_data->dnd;
+   XSetWindowAttributes attributes;
+   guint32 i, version = XDND_VERSION;
+@@ -382,12 +382,12 @@ meta_xwayland_init_dnd (MetaXWaylandManager *manager)
+ }
+ 
+ static void
+-meta_xwayland_shutdown_dnd (MetaXWaylandManager *manager)
++meta_xwayland_shutdown_dnd (MetaXWaylandManager *manager,
++                            Display             *xdisplay)
+ {
+   MetaDndBridge *dnd = &manager->selection_data->dnd;
+ 
+-  XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+-                  dnd->dnd_window);
++  XDestroyWindow (xdisplay, dnd->dnd_window);
+   dnd->dnd_window = None;
+ }
+ 
+@@ -1755,7 +1755,7 @@ shutdown_selection_bridge (MetaSelectionBridge *selection)
+ }
+ 
+ void
+-meta_xwayland_init_selection (void)
++meta_xwayland_init_selection (Display *xdisplay)
+ {
+   MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
+   MetaXWaylandManager *manager = &compositor->xwayland_manager;
+@@ -1764,7 +1764,7 @@ meta_xwayland_init_selection (void)
+ 
+   manager->selection_data = g_slice_new0 (MetaXWaylandSelection);
+ 
+-  meta_xwayland_init_dnd (manager);
++  meta_xwayland_init_dnd (manager, xdisplay);
+   init_selection_bridge (&manager->selection_data->clipboard,
+                          gdk_x11_get_xatom_by_name ("CLIPBOARD"),
+                          &compositor->seat->data_device.selection_ownership_signal);
+@@ -1777,7 +1777,7 @@ meta_xwayland_init_selection (void)
+ }
+ 
+ void
+-meta_xwayland_shutdown_selection (void)
++meta_xwayland_shutdown_selection (Display *xdisplay)
+ {
+   MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
+   MetaXWaylandManager *manager = &compositor->xwayland_manager;
+@@ -1787,7 +1787,7 @@ meta_xwayland_shutdown_selection (void)
+ 
+   g_clear_object (&selection->clipboard.source);
+ 
+-  meta_xwayland_shutdown_dnd (manager);
++  meta_xwayland_shutdown_dnd (manager, xdisplay);
+   shutdown_selection_bridge (&selection->clipboard);
+   shutdown_selection_bridge (&selection->primary);
+   shutdown_selection_bridge (&selection->dnd.selection);
+diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c
+index 350626dfdb..3236711482 100644
+--- a/src/wayland/meta-xwayland.c
++++ b/src/wayland/meta-xwayland.c
+@@ -38,6 +38,7 @@
+ 
+ #include "compositor/meta-surface-actor-wayland.h"
+ #include "meta/main.h"
++#include "meta/meta-x11-display.h"
+ #include "wayland/meta-wayland-actor-surface.h"
+ 
+ enum
+@@ -722,7 +723,9 @@ out:
+ static void
+ on_x11_display_closing (MetaDisplay *display)
+ {
+-  meta_xwayland_shutdown_selection ();
++  Display *xdisplay = meta_x11_display_get_xdisplay (display->x11_display);
++
++  meta_xwayland_shutdown_selection (xdisplay);
+ }
+ 
+ /* To be called right after connecting */
+@@ -739,7 +742,7 @@ meta_xwayland_complete_init (MetaDisplay *display,
+ 
+   g_signal_connect (display, "x11-display-closing",
+                     G_CALLBACK (on_x11_display_closing), NULL);
+-  meta_xwayland_init_selection ();
++  meta_xwayland_init_selection (xdisplay);
+ }
+ 
+ void
+-- 
+2.31.1
+
+
+From a398699a53b9cc6efda4aa8abe0e3176bab80e92 Mon Sep 17 00:00:00 2001
+From: Olivier Fourdan <ofourdan@redhat.com>
+Date: Mon, 19 Aug 2019 15:50:54 +0200
+Subject: [PATCH 4/4] xwayland: Add local user to xhost
+
+With the addition of xauth support (commit a8984a81c), Xwayland would
+rely only on the provided cookies for authentication.
+
+As a result, running an Xclient from another VT (hence without the
+XAUTHORITY environment variable set) would result in an access denied.
+
+The same on X11 is granted because the local user is automatically
+granted access to Xserver by the startup scripts.
+
+Add the local user to xhost at startup on Xwayland so that the user can
+still run a client by setting the DISPLAY as long as it's the same user
+on the same host.
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/735
+---
+ src/wayland/meta-xwayland.c | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c
+index 3236711482..275aeb78cb 100644
+--- a/src/wayland/meta-xwayland.c
++++ b/src/wayland/meta-xwayland.c
+@@ -598,6 +598,23 @@ prepare_auth_file (MetaXWaylandManager *manager)
+   return TRUE;
+ }
+ 
++static void
++add_local_user_to_xhost (Display *xdisplay)
++{
++  XHostAddress host_entry;
++  XServerInterpretedAddress siaddr;
++
++  siaddr.type = (char *) "localuser";
++  siaddr.typelength = strlen (siaddr.type);
++  siaddr.value = (char *) g_get_user_name();
++  siaddr.valuelength = strlen (siaddr.value);
++
++  host_entry.family = FamilyServerInterpreted;
++  host_entry.address = (char *) &siaddr;
++
++  XAddHost (xdisplay, &host_entry);
++}
++
+ static void
+ xserver_finished_init (MetaXWaylandManager *manager)
+ {
+@@ -743,6 +760,7 @@ meta_xwayland_complete_init (MetaDisplay *display,
+   g_signal_connect (display, "x11-display-closing",
+                     G_CALLBACK (on_x11_display_closing), NULL);
+   meta_xwayland_init_selection (xdisplay);
++  add_local_user_to_xhost (xdisplay);
+ }
+ 
+ void
+-- 
+2.31.1
+
diff --git a/SPECS/mutter.spec b/SPECS/mutter.spec
index 45e87b0..ee389e0 100644
--- a/SPECS/mutter.spec
+++ b/SPECS/mutter.spec
@@ -8,7 +8,7 @@
 
 Name:          mutter
 Version:       3.32.2
-Release:       55%{?dist}
+Release:       59%{?dist}
 Summary:       Window and compositing manager based on Clutter
 
 License:       GPLv2+
@@ -177,6 +177,18 @@ Patch508: 0001-monitor-config-manager-Handle-multiple-builtin-panel.patch
 Patch509: 0001-clutter-stage-view-Hide-double-buffered-shadowfb-beh.patch
 Patch510: 0002-cogl-gpu-info-Fix-software-acceleration-detection.patch
 
+# Backport of geometric picking, improving performance and fixing picking
+# 10bpc pixel formats (#1919467)
+Patch511: geometric-picking.patch
+
+Patch520: 0001-clutter-Backport-of-touch-mode.patch
+
+# Backport passing -xauth and adding local user to xhost (#1949176)
+Patch521: xwayland-xauth-xhost-user.patch
+
+# Backport fixes avoiding frozen partly off-screen clients (#1989035)
+Patch522: wayland-frame-callback-rework.patch
+
 BuildRequires: chrpath
 BuildRequires: pango-devel
 BuildRequires: startup-notification-devel
@@ -318,6 +330,22 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop
 %{_datadir}/mutter-%{mutter_api_version}/tests
 
 %changelog
+* Fri Aug 06 2021 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-59
+- Backport fixes avoiding frozen partly off-screen clients
+  Resolves: #1989035
+
+* Mon Jul 05 2021 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-58
+- Backport xauth and xhost patches
+  Resolves: #1949176
+
+* Mon Feb 22 2021 Carlos Garnacho <cgarnach@redhat.com> - 3.32.2-57
+- Backport touch-mode
+  Resolves: #1833787
+
+* Tue Feb 09 2021 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-56
+- Backport geometric picking patches
+  Resolves: #1919467
+
 * Tue Feb 09 2021 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-55
 - Fix slow nouveau with llvmpipe
   Resolves: #1921151